/* This file is part of Telegram Desktop, the official desktop version of Telegram messaging app, see https://telegram.org Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "mainwidget.h" #include "window.h" #include "application.h" #include "localstorage.h" namespace { int32 GlobalPriority = 1; struct DataRequested { DataRequested() { memset(v, 0, sizeof(v)); } int64 v[MTPDownloadSessionsCount]; }; QMap DataRequestedMap; } struct mtpFileLoaderQueue { mtpFileLoaderQueue() : queries(0), start(0), end(0) { } int32 queries; mtpFileLoader *start, *end; }; namespace { typedef QMap LoaderQueues; LoaderQueues queues; } mtpFileLoader::mtpFileLoader(const StorageImageLocation *location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading) : prev(0) , next(0) , priority(0) , _paused(false) , _autoLoading(autoLoading) , _inQueue(false) , _complete(false) , _localStatus(LocalNotTried) , _skippedBytes(0) , _nextRequestOffset(0) , _lastComplete(false) , _dc(location->dc()) , _locationType(UnknownFileLocation) , _location(location) , _id(0) , _access(0) , _fileIsOpen(false) , _toCache(LoadToCacheAsWell) , _fromCloud(fromCloud) , _size(size) , _type(mtpc_storage_fileUnknown) , _localTaskId(0) { LoaderQueues::iterator i = queues.find(_dc); if (i == queues.cend()) { i = queues.insert(_dc, mtpFileLoaderQueue()); } queue = &i.value(); } mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, LocationType type, const QString &to, int32 size, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading) : prev(0) , next(0) , priority(0) , _paused(false) , _autoLoading(autoLoading) , _inQueue(false) , _complete(false) , _localStatus(LocalNotTried) , _skippedBytes(0) , _nextRequestOffset(0) , _lastComplete(false) , _dc(dc) , _locationType(type) , _location(0) , _id(id) , _access(access) , _file(to) , _fname(to) , _fileIsOpen(false) , _toCache(toCache) , _fromCloud(fromCloud) , _size(size) , _type(mtpc_storage_fileUnknown) , _localTaskId(0) { LoaderQueues::iterator i = queues.find(MTP::dld[0] + _dc); if (i == queues.cend()) { i = queues.insert(MTP::dld[0] + _dc, mtpFileLoaderQueue()); } queue = &i.value(); } QByteArray mtpFileLoader::imageFormat() const { if (_imageFormat.isEmpty() && _locationType == UnknownFileLocation) { readImage(); } return _imageFormat; } QPixmap mtpFileLoader::imagePixmap() const { if (_imagePixmap.isNull() && _locationType == UnknownFileLocation) { readImage(); } return _imagePixmap; } void mtpFileLoader::readImage() const { QByteArray format; switch (_type) { case mtpc_storage_fileGif: format = "GIF"; break; case mtpc_storage_fileJpeg: format = "JPG"; break; case mtpc_storage_filePng: format = "PNG"; break; default: format = QByteArray(); break; } _imagePixmap = QPixmap::fromImage(App::readImage(_data, &format, false), Qt::ColorOnly); if (!_imagePixmap.isNull()) { _imageFormat = format; } } float64 mtpFileLoader::currentProgress() const { if (_complete) return 1; if (!fullSize()) return 0; return float64(currentOffset()) / fullSize(); } int32 mtpFileLoader::currentOffset(bool includeSkipped) const { return (_fileIsOpen ? _file.size() : _data.size()) - (includeSkipped ? 0 : _skippedBytes); } int32 mtpFileLoader::fullSize() const { return _size; } bool mtpFileLoader::setFileName(const QString &fileName) { if (_toCache != LoadToCacheAsWell || !_fname.isEmpty()) return fileName.isEmpty(); _fname = fileName; _file.setFileName(_fname); return true; } void mtpFileLoader::permitLoadFromCloud() { _fromCloud = LoadFromCloudOrLocal; } uint64 mtpFileLoader::objId() const { return _id; } void mtpFileLoader::loadNext() { if (queue->queries >= MaxFileQueries) return; for (mtpFileLoader *i = queue->start; i;) { if (i->loadPart()) { if (queue->queries >= MaxFileQueries) return; } else { i = i->next; } } } bool mtpFileLoader::loadPart() { if (_complete || _lastComplete || (!_requests.isEmpty() && !_size)) return false; if (_size && _nextRequestOffset >= _size) return false; int32 limit = DocumentDownloadPartSize; MTPInputFileLocation loc; if (_location) { loc = MTP_inputFileLocation(MTP_long(_location->volume()), MTP_int(_location->local()), MTP_long(_location->secret())); limit = DownloadPartSize; } else { switch (_locationType) { case VideoFileLocation: loc = MTP_inputVideoFileLocation(MTP_long(_id), MTP_long(_access)); break; case AudioFileLocation: loc = MTP_inputAudioFileLocation(MTP_long(_id), MTP_long(_access)); break; case DocumentFileLocation: loc = MTP_inputDocumentFileLocation(MTP_long(_id), MTP_long(_access)); break; default: cancel(true); return false; break; } } int32 offset = _nextRequestOffset, dcIndex = 0; DataRequested &dr(DataRequestedMap[_dc]); if (_size) { for (int32 i = 1; i < MTPDownloadSessionsCount; ++i) { if (dr.v[i] < dr.v[dcIndex]) { dcIndex = i; } } } App::app()->killDownloadSessionsStop(_dc); mtpRequestId reqId = MTP::send(MTPupload_GetFile(MTPupload_getFile(loc, MTP_int(offset), MTP_int(limit))), rpcDone(&mtpFileLoader::partLoaded, offset), rpcFail(&mtpFileLoader::partFailed), MTP::dld[dcIndex] + _dc, 50); ++queue->queries; dr.v[dcIndex] += limit; _requests.insert(reqId, dcIndex); _nextRequestOffset += limit; return true; } void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRequestId req) { // uint64 ms = getms(); Requests::iterator i = _requests.find(req); if (i == _requests.cend()) return loadNext(); int32 limit = (_locationType == UnknownFileLocation) ? DownloadPartSize : DocumentDownloadPartSize; int32 dcIndex = i.value(); DataRequestedMap[_dc].v[dcIndex] -= limit; --queue->queries; _requests.erase(i); const MTPDupload_file &d(result.c_upload_file()); const string &bytes(d.vbytes.c_string().v); if (bytes.size()) { if (_fileIsOpen) { int64 fsize = _file.size(); if (offset < fsize) { _skippedBytes -= bytes.size(); } else if (offset > fsize) { _skippedBytes += offset - fsize; } _file.seek(offset); if (_file.write(bytes.data(), bytes.size()) != qint64(bytes.size())) { return cancel(true); } } else { _data.reserve(offset + bytes.size()); if (offset > _data.size()) { _skippedBytes += offset - _data.size(); _data.resize(offset); } if (offset == _data.size()) { _data.append(bytes.data(), bytes.size()); } else { _skippedBytes -= bytes.size(); if (int64(offset + bytes.size()) > _data.size()) { _data.resize(offset + bytes.size()); } memcpy(_data.data() + offset, bytes.data(), bytes.size()); } } } if (!bytes.size() || (bytes.size() % 1024)) { // bad next offset _lastComplete = true; } if (_requests.isEmpty() && (_lastComplete || (_size && _nextRequestOffset >= _size))) { if (!_fname.isEmpty() && (_toCache == LoadToCacheAsWell)) { if (!_fileIsOpen) _fileIsOpen = _file.open(QIODevice::WriteOnly); if (!_fileIsOpen) { return cancel(true); } if (_file.write(_data) != qint64(_data.size())) { return cancel(true); } } _type = d.vtype.type(); _complete = true; if (_fileIsOpen) { _file.close(); _fileIsOpen = false; psPostprocessFile(QFileInfo(_file).absoluteFilePath()); } removeFromQueue(); emit App::wnd()->imageLoaded(); if (!queue->queries) { App::app()->killDownloadSessionsStart(_dc); } if (_localStatus == LocalNotFound || _localStatus == LocalFailed) { if (_locationType != UnknownFileLocation) { // audio, video, document MediaKey mkey = mediaKey(_locationType, _dc, _id); if (!_fname.isEmpty()) { Local::writeFileLocation(mkey, FileLocation(mtpToStorageType(_type), _fname)); } if (_toCache == LoadToCacheAsWell) { if (_locationType == DocumentFileLocation) { Local::writeStickerImage(mkey, _data); } else if (_locationType == AudioFileLocation) { Local::writeAudio(mkey, _data); } } } else { Local::writeImage(storageKey(*_location), StorageImageSaved(mtpToStorageType(_type), _data)); } } } emit progress(this); loadNext(); } bool mtpFileLoader::partFailed(const RPCError &error) { if (mtpIsFlood(error)) return false; cancel(true); return true; } void mtpFileLoader::removeFromQueue() { if (!_inQueue) return; if (next) { next->prev = prev; } if (prev) { prev->next = next; } if (queue->end == this) { queue->end = prev; } if (queue->start == this) { queue->start = next; } next = prev = 0; _inQueue = false; } void mtpFileLoader::pause() { removeFromQueue(); _paused = true; } bool mtpFileLoader::tryLoadLocal() { if (_localStatus == LocalNotFound || _localStatus == LocalLoaded || _localStatus == LocalFailed) { return false; } if (_localStatus == LocalLoading) { return true; } if (_location) { _localTaskId = Local::startImageLoad(storageKey(*_location), this); } else { if (_toCache == LoadToCacheAsWell) { MediaKey mkey = mediaKey(_locationType, _dc, _id); if (_locationType == DocumentFileLocation) { _localTaskId = Local::startStickerImageLoad(mkey, this); } else if (_locationType == AudioFileLocation) { _localTaskId = Local::startAudioLoad(mkey, this); } } } if (_localStatus != LocalNotTried) { return _complete; } else if (_localTaskId) { _localStatus = LocalLoading; return true; } _localStatus = LocalNotFound; return false; } void mtpFileLoader::localLoaded(const StorageImageSaved &result, const QByteArray &imageFormat, const QPixmap &imagePixmap) { _localTaskId = 0; if (result.type == StorageFileUnknown) { _localStatus = LocalFailed; start(true); return; } _data = result.data; _type = mtpFromStorageType(result.type); if (!imagePixmap.isNull()) { _imageFormat = imageFormat; _imagePixmap = imagePixmap; } _localStatus = LocalLoaded; if (!_fname.isEmpty() && _toCache == LoadToCacheAsWell) { if (!_fileIsOpen) _fileIsOpen = _file.open(QIODevice::WriteOnly); if (!_fileIsOpen) { cancel(true); return; } if (_file.write(_data) != qint64(_data.size())) { cancel(true); return; } } _complete = true; if (_fileIsOpen) { _file.close(); _fileIsOpen = false; psPostprocessFile(QFileInfo(_file).absoluteFilePath()); } emit App::wnd()->imageLoaded(); emit progress(this); loadNext(); } void mtpFileLoader::start(bool loadFirst, bool prior) { if (_paused) { _paused = false; } if (_complete || tryLoadLocal()) return; if (_fromCloud == LoadFromLocalOnly) { cancel(); return; } if (!_fname.isEmpty() && _toCache == LoadToFileOnly && !_fileIsOpen) { _fileIsOpen = _file.open(QIODevice::WriteOnly); if (!_fileIsOpen) { return cancel(true); } } mtpFileLoader *before = 0, *after = 0; if (prior) { if (_inQueue && priority == GlobalPriority) { if (loadFirst) { if (!prev) return startLoading(loadFirst, prior); before = queue->start; } else { if (!next || next->priority < GlobalPriority) return startLoading(loadFirst, prior); after = next; while (after->next && after->next->priority == GlobalPriority) { after = after->next; } } } else { priority = GlobalPriority; if (loadFirst) { if (_inQueue && !prev) return startLoading(loadFirst, prior); before = queue->start; } else { if (_inQueue) { if (next && next->priority == GlobalPriority) { after = next; } else if (prev && prev->priority < GlobalPriority) { before = prev; while (before->prev && before->prev->priority < GlobalPriority) { before = before->prev; } } else { return startLoading(loadFirst, prior); } } else { if (queue->start && queue->start->priority == GlobalPriority) { after = queue->start; } else { before = queue->start; } } if (after) { while (after->next && after->next->priority == GlobalPriority) { after = after->next; } } } } } else { if (loadFirst) { if (_inQueue && (!prev || prev->priority == GlobalPriority)) return startLoading(loadFirst, prior); before = prev; while (before->prev && before->prev->priority != GlobalPriority) { before = before->prev; } } else { if (_inQueue && !next) return startLoading(loadFirst, prior); after = queue->end; } } removeFromQueue(); _inQueue = true; if (!queue->start) { queue->start = queue->end = this; } else if (before) { if (before != next) { prev = before->prev; next = before; next->prev = this; if (prev) { prev->next = this; } if (queue->start->prev) queue->start = queue->start->prev; } } else if (after) { if (after != prev) { next = after->next; prev = after; after->next = this; if (next) { next->prev = this; } if (queue->end->next) queue->end = queue->end->next; } } else { LOG(("Queue Error: _start && !before && !after")); } return startLoading(loadFirst, prior); } void mtpFileLoader::cancel() { cancel(false); } void mtpFileLoader::cancel(bool fail) { bool started = currentOffset(true) > 0; cancelRequests(); _type = mtpc_storage_fileUnknown; _complete = true; if (_fileIsOpen) { _file.close(); _fileIsOpen = false; _file.remove(); } _data = QByteArray(); if (fail) { emit failed(this, started); } else { emit progress(this); } _fname = QString(); _file.setFileName(_fname); loadNext(); } void mtpFileLoader::cancelRequests() { if (_requests.isEmpty()) return; int32 limit = (_locationType == UnknownFileLocation) ? DownloadPartSize : DocumentDownloadPartSize; DataRequested &dr(DataRequestedMap[_dc]); for (Requests::const_iterator i = _requests.cbegin(), e = _requests.cend(); i != e; ++i) { MTP::cancel(i.key()); int32 dcIndex = i.value(); dr.v[dcIndex] -= limit; } queue->queries -= _requests.size(); _requests.clear(); if (!queue->queries) { App::app()->killDownloadSessionsStart(_dc); } } void mtpFileLoader::startLoading(bool loadFirst, bool prior) { if ((queue->queries >= MaxFileQueries && (!loadFirst || !prior)) || _complete) return; loadPart(); } mtpFileLoader::~mtpFileLoader() { if (_localTaskId) { Local::cancelTask(_localTaskId); } removeFromQueue(); cancelRequests(); } namespace MTP { void clearLoaderPriorities() { ++GlobalPriority; } }