2
0
mirror of https://github.com/telegramdesktop/tdesktop synced 2025-08-31 14:38:15 +00:00

Renamed / moved a bunch of files.

Next commit fixes the build.
This commit is contained in:
John Preston
2017-03-04 12:56:07 +03:00
parent 08167a6a91
commit b0dbe9d353
22 changed files with 83 additions and 83 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,388 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/observer.h"
namespace MTP {
void clearLoaderPriorities();
}
enum LocationType {
UnknownFileLocation = 0,
// 1, 2, etc are used as "version" value in mediaKey() method.
DocumentFileLocation = 0x4e45abe9, // mtpc_inputDocumentFileLocation
AudioFileLocation = 0x74dc404d, // mtpc_inputAudioFileLocation
VideoFileLocation = 0x3d0364ec, // mtpc_inputVideoFileLocation
};
enum StorageFileType {
StorageFileUnknown = 0xaa963b05, // mtpc_storage_fileUnknown
StorageFileJpeg = 0x7efe0e, // mtpc_storage_fileJpeg
StorageFileGif = 0xcae1aadf, // mtpc_storage_fileGif
StorageFilePng = 0xa4f63c0, // mtpc_storage_filePng
StorageFilePdf = 0xae1e508d, // mtpc_storage_filePdf
StorageFileMp3 = 0x528a0677, // mtpc_storage_fileMp3
StorageFileMov = 0x4b09ebbc, // mtpc_storage_fileMov
StorageFilePartial = 0x40bc6f52, // mtpc_storage_filePartial
StorageFileMp4 = 0xb3cea0e4, // mtpc_storage_fileMp4
StorageFileWebp = 0x1081464c, // mtpc_storage_fileWebp
};
inline StorageFileType mtpToStorageType(mtpTypeId type) {
switch (type) {
case mtpc_storage_fileJpeg: return StorageFileJpeg;
case mtpc_storage_fileGif: return StorageFileGif;
case mtpc_storage_filePng: return StorageFilePng;
case mtpc_storage_filePdf: return StorageFilePdf;
case mtpc_storage_fileMp3: return StorageFileMp3;
case mtpc_storage_fileMov: return StorageFileMov;
case mtpc_storage_filePartial: return StorageFilePartial;
case mtpc_storage_fileMp4: return StorageFileMp4;
case mtpc_storage_fileWebp: return StorageFileWebp;
case mtpc_storage_fileUnknown:
default: return StorageFileUnknown;
}
}
inline mtpTypeId mtpFromStorageType(StorageFileType type) {
switch (type) {
case StorageFileJpeg: return mtpc_storage_fileJpeg;
case StorageFileGif: return mtpc_storage_fileGif;
case StorageFilePng: return mtpc_storage_filePng;
case StorageFilePdf: return mtpc_storage_filePdf;
case StorageFileMp3: return mtpc_storage_fileMp3;
case StorageFileMov: return mtpc_storage_fileMov;
case StorageFilePartial: return mtpc_storage_filePartial;
case StorageFileMp4: return mtpc_storage_fileMp4;
case StorageFileWebp: return mtpc_storage_fileWebp;
case StorageFileUnknown:
default: return mtpc_storage_fileUnknown;
}
}
struct StorageImageSaved {
StorageImageSaved() : type(StorageFileUnknown) {
}
StorageImageSaved(StorageFileType type, const QByteArray &data) : type(type), data(data) {
}
StorageFileType type;
QByteArray data;
};
enum LocalLoadStatus {
LocalNotTried,
LocalNotFound,
LocalLoading,
LocalLoaded,
LocalFailed,
};
using TaskId = void*; // no interface, just id
enum LoadFromCloudSetting {
LoadFromCloudOrLocal,
LoadFromLocalOnly,
};
enum LoadToCacheSetting {
LoadToFileOnly,
LoadToCacheAsWell,
};
class mtpFileLoader;
class webFileLoader;
struct FileLoaderQueue;
class FileLoader : public QObject {
Q_OBJECT
public:
FileLoader(const QString &toFile, int32 size, LocationType locationType, LoadToCacheSetting, LoadFromCloudSetting fromCloud, bool autoLoading);
bool done() const {
return _complete;
}
mtpTypeId fileType() const {
return _type;
}
const QByteArray &bytes() const {
return _data;
}
virtual uint64 objId() const {
return 0;
}
QByteArray imageFormat(const QSize &shrinkBox = QSize()) const;
QPixmap imagePixmap(const QSize &shrinkBox = QSize()) const;
QString fileName() const {
return _fname;
}
float64 currentProgress() const;
virtual int32 currentOffset(bool includeSkipped = false) const = 0;
int32 fullSize() const;
bool setFileName(const QString &filename); // set filename for loaders to cache
void permitLoadFromCloud();
void pause();
void start(bool loadFirst = false, bool prior = true);
void cancel();
bool loading() const {
return _inQueue;
}
bool paused() const {
return _paused;
}
bool started() const {
return _inQueue || _paused;
}
bool loadingLocal() const {
return (_localStatus == LocalLoading);
}
bool autoLoading() const {
return _autoLoading;
}
virtual void stop() {
}
virtual ~FileLoader();
void localLoaded(const StorageImageSaved &result, const QByteArray &imageFormat = QByteArray(), const QPixmap &imagePixmap = QPixmap());
signals:
void progress(FileLoader *loader);
void failed(FileLoader *loader, bool started);
protected:
void readImage(const QSize &shrinkBox) const;
FileLoader *_prev = nullptr;
FileLoader *_next = nullptr;
int _priority = 0;
FileLoaderQueue *_queue;
bool _paused = false;
bool _autoLoading = false;
bool _inQueue = false;
bool _complete = false;
mutable LocalLoadStatus _localStatus = LocalNotTried;
virtual bool tryLoadLocal() = 0;
virtual void cancelRequests() = 0;
void startLoading(bool loadFirst, bool prior);
void removeFromQueue();
void cancel(bool failed);
void loadNext();
virtual bool loadPart() = 0;
QFile _file;
QString _fname;
bool _fileIsOpen = false;
LoadToCacheSetting _toCache;
LoadFromCloudSetting _fromCloud;
QByteArray _data;
int32 _size;
mtpTypeId _type;
LocationType _locationType;
TaskId _localTaskId = 0;
mutable QByteArray _imageFormat;
mutable QPixmap _imagePixmap;
};
class StorageImageLocation;
class mtpFileLoader : public FileLoader, public RPCSender {
Q_OBJECT
public:
mtpFileLoader(const StorageImageLocation *location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading);
mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, int32 version, LocationType type, const QString &toFile, int32 size, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading);
int32 currentOffset(bool includeSkipped = false) const override;
uint64 objId() const override {
return _id;
}
void stop() override {
rpcClear();
}
~mtpFileLoader();
protected:
bool tryLoadLocal() override;
void cancelRequests() override;
typedef QMap<mtpRequestId, int32> Requests;
Requests _requests;
bool loadPart() override;
void partLoaded(int32 offset, const MTPupload_File &result, mtpRequestId req);
bool partFailed(const RPCError &error);
bool _lastComplete = false;
int32 _skippedBytes = 0;
int32 _nextRequestOffset = 0;
int32 _dc;
const StorageImageLocation *_location = nullptr;
uint64 _id = 0; // for other locations
uint64 _access = 0;
int32 _version = 0;
};
class webFileLoaderPrivate;
class webFileLoader : public FileLoader {
Q_OBJECT
public:
webFileLoader(const QString &url, const QString &to, LoadFromCloudSetting fromCloud, bool autoLoading);
virtual int32 currentOffset(bool includeSkipped = false) const;
virtual webFileLoader *webLoader() {
return this;
}
virtual const webFileLoader *webLoader() const {
return this;
}
void onProgress(qint64 already, qint64 size);
void onFinished(const QByteArray &data);
void onError();
virtual void stop() {
cancelRequests();
}
~webFileLoader();
protected:
virtual void cancelRequests();
virtual bool tryLoadLocal();
virtual bool loadPart();
QString _url;
bool _requestSent;
int32 _already;
friend class WebLoadManager;
webFileLoaderPrivate *_private;
};
enum WebReplyProcessResult {
WebReplyProcessError,
WebReplyProcessProgress,
WebReplyProcessFinished,
};
class WebLoadManager : public QObject {
Q_OBJECT
public:
WebLoadManager(QThread *thread);
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
void setProxySettings(const QNetworkProxy &proxy);
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
void append(webFileLoader *loader, const QString &url);
void stop(webFileLoader *reader);
bool carries(webFileLoader *reader) const;
~WebLoadManager();
signals:
void processDelayed();
void proxyApplyDelayed();
void progress(webFileLoader *loader, qint64 already, qint64 size);
void finished(webFileLoader *loader, QByteArray data);
void error(webFileLoader *loader);
public slots:
void onFailed(QNetworkReply *reply);
void onFailed(QNetworkReply::NetworkError error);
void onProgress(qint64 already, qint64 size);
void onMeta();
void process();
void proxyApply();
void finish();
private:
void clear();
void sendRequest(webFileLoaderPrivate *loader, const QString &redirect = QString());
bool handleReplyResult(webFileLoaderPrivate *loader, WebReplyProcessResult result);
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
QNetworkProxy _proxySettings;
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
QNetworkAccessManager _manager;
typedef QMap<webFileLoader*, webFileLoaderPrivate*> LoaderPointers;
LoaderPointers _loaderPointers;
mutable QMutex _loaderPointersMutex;
typedef OrderedSet<webFileLoaderPrivate*> Loaders;
Loaders _loaders;
typedef QMap<QNetworkReply*, webFileLoaderPrivate*> Replies;
Replies _replies;
};
class WebLoadMainManager : public QObject {
Q_OBJECT
public:
public slots:
void progress(webFileLoader *loader, qint64 already, qint64 size);
void finished(webFileLoader *loader, QByteArray data);
void error(webFileLoader *loader);
};
static FileLoader * const CancelledFileLoader = SharedMemoryLocation<FileLoader, 0>();
static mtpFileLoader * const CancelledMtpFileLoader = static_cast<mtpFileLoader*>(CancelledFileLoader);
static webFileLoader * const CancelledWebFileLoader = static_cast<webFileLoader*>(CancelledFileLoader);
static WebLoadManager * const FinishedWebLoadManager = SharedMemoryLocation<WebLoadManager, 0>();
void reinitWebLoadManager();
void stopWebLoadManager();
namespace FileDownload {
base::Observable<void> &ImageLoaded();
} // namespace FileDownload

View File

@@ -0,0 +1,316 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#include "fileuploader.h"
FileUploader::FileUploader() : sentSize(0) {
memset(sentSizes, 0, sizeof(sentSizes));
nextTimer.setSingleShot(true);
connect(&nextTimer, SIGNAL(timeout()), this, SLOT(sendNext()));
killSessionsTimer.setSingleShot(true);
connect(&killSessionsTimer, SIGNAL(timeout()), this, SLOT(killSessions()));
}
void FileUploader::uploadMedia(const FullMsgId &msgId, const SendMediaReady &media) {
if (media.type == SendMediaType::Photo) {
App::feedPhoto(media.photo, media.photoThumbs);
} else if (media.type == SendMediaType::File || media.type == SendMediaType::Audio) {
DocumentData *document;
if (media.photoThumbs.isEmpty()) {
document = App::feedDocument(media.document);
} else {
document = App::feedDocument(media.document, media.photoThumbs.begin().value());
}
document->status = FileUploading;
if (!media.data.isEmpty()) {
document->setData(media.data);
}
if (!media.file.isEmpty()) {
document->setLocation(FileLocation(StorageFilePartial, media.file));
}
}
queue.insert(msgId, File(media));
sendNext();
}
void FileUploader::upload(const FullMsgId &msgId, const FileLoadResultPtr &file) {
if (file->type == SendMediaType::Photo) {
PhotoData *photo = App::feedPhoto(file->photo, file->photoThumbs);
photo->uploadingData = new PhotoData::UploadingData(file->partssize);
} else if (file->type == SendMediaType::File || file->type == SendMediaType::Audio) {
DocumentData *document;
if (file->thumb.isNull()) {
document = App::feedDocument(file->document);
} else {
document = App::feedDocument(file->document, file->thumb);
}
document->status = FileUploading;
if (!file->content.isEmpty()) {
document->setData(file->content);
}
if (!file->filepath.isEmpty()) {
document->setLocation(FileLocation(StorageFilePartial, file->filepath));
}
}
queue.insert(msgId, File(file));
sendNext();
}
void FileUploader::currentFailed() {
Queue::iterator j = queue.find(uploading);
if (j != queue.end()) {
if (j->type() == SendMediaType::Photo) {
emit photoFailed(j.key());
} else if (j->type() == SendMediaType::File) {
DocumentData *doc = App::document(j->id());
if (doc->status == FileUploading) {
doc->status = FileUploadFailed;
}
emit documentFailed(j.key());
}
queue.erase(j);
}
requestsSent.clear();
docRequestsSent.clear();
dcMap.clear();
uploading = FullMsgId();
sentSize = 0;
for (int i = 0; i < MTPUploadSessionsCount; ++i) {
sentSizes[i] = 0;
}
sendNext();
}
void FileUploader::killSessions() {
for (int i = 0; i < MTPUploadSessionsCount; ++i) {
MTP::stopSession(MTP::uploadDcId(i));
}
}
void FileUploader::sendNext() {
if (sentSize >= MaxUploadFileParallelSize || _paused.msg) return;
bool killing = killSessionsTimer.isActive();
if (queue.isEmpty()) {
if (!killing) {
killSessionsTimer.start(MTPAckSendWaiting + MTPKillFileSessionTimeout);
}
return;
}
if (killing) {
killSessionsTimer.stop();
}
Queue::iterator i = uploading.msg ? queue.find(uploading) : queue.begin();
if (!uploading.msg) {
uploading = i.key();
} else if (i == queue.end()) {
i = queue.begin();
uploading = i.key();
}
int todc = 0;
for (int dc = 1; dc < MTPUploadSessionsCount; ++dc) {
if (sentSizes[dc] < sentSizes[todc]) {
todc = dc;
}
}
UploadFileParts &parts(i->file ? (i->type() == SendMediaType::Photo ? i->file->fileparts : i->file->thumbparts) : i->media.parts);
uint64 partsOfId(i->file ? (i->type() == SendMediaType::Photo ? i->file->id : i->file->thumbId) : i->media.thumbId);
if (parts.isEmpty()) {
if (i->docSentParts >= i->docPartsCount) {
if (requestsSent.isEmpty() && docRequestsSent.isEmpty()) {
bool silent = i->file && i->file->to.silent;
if (i->type() == SendMediaType::Photo) {
emit photoReady(uploading, silent, MTP_inputFile(MTP_long(i->id()), MTP_int(i->partsCount), MTP_string(i->filename()), MTP_bytes(i->file ? i->file->filemd5 : i->media.jpeg_md5)));
} else if (i->type() == SendMediaType::File || i->type() == SendMediaType::Audio) {
QByteArray docMd5(32, Qt::Uninitialized);
hashMd5Hex(i->md5Hash.result(), docMd5.data());
MTPInputFile doc = (i->docSize > UseBigFilesFrom) ? MTP_inputFileBig(MTP_long(i->id()), MTP_int(i->docPartsCount), MTP_string(i->filename())) : MTP_inputFile(MTP_long(i->id()), MTP_int(i->docPartsCount), MTP_string(i->filename()), MTP_bytes(docMd5));
if (i->partsCount) {
emit thumbDocumentReady(uploading, silent, doc, MTP_inputFile(MTP_long(i->thumbId()), MTP_int(i->partsCount), MTP_string(i->file ? i->file->thumbname : (qsl("thumb.") + i->media.thumbExt)), MTP_bytes(i->file ? i->file->thumbmd5 : i->media.jpeg_md5)));
} else {
emit documentReady(uploading, silent, doc);
}
}
queue.remove(uploading);
uploading = FullMsgId();
sendNext();
}
return;
}
QByteArray &content(i->file ? i->file->content : i->media.data);
QByteArray toSend;
if (content.isEmpty()) {
if (!i->docFile) {
i->docFile.reset(new QFile(i->file ? i->file->filepath : i->media.file));
if (!i->docFile->open(QIODevice::ReadOnly)) {
currentFailed();
return;
}
}
toSend = i->docFile->read(i->docPartSize);
if (i->docSize <= UseBigFilesFrom) {
i->md5Hash.feed(toSend.constData(), toSend.size());
}
} else {
toSend = content.mid(i->docSentParts * i->docPartSize, i->docPartSize);
if ((i->type() == SendMediaType::File || i->type() == SendMediaType::Audio) && i->docSentParts <= UseBigFilesFrom) {
i->md5Hash.feed(toSend.constData(), toSend.size());
}
}
if (toSend.size() > i->docPartSize || (toSend.size() < i->docPartSize && i->docSentParts + 1 != i->docPartsCount)) {
currentFailed();
return;
}
mtpRequestId requestId;
if (i->docSize > UseBigFilesFrom) {
requestId = MTP::send(MTPupload_SaveBigFilePart(MTP_long(i->id()), MTP_int(i->docSentParts), MTP_int(i->docPartsCount), MTP_bytes(toSend)), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::uploadDcId(todc));
} else {
requestId = MTP::send(MTPupload_SaveFilePart(MTP_long(i->id()), MTP_int(i->docSentParts), MTP_bytes(toSend)), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::uploadDcId(todc));
}
docRequestsSent.insert(requestId, i->docSentParts);
dcMap.insert(requestId, todc);
sentSize += i->docPartSize;
sentSizes[todc] += i->docPartSize;
i->docSentParts++;
} else {
UploadFileParts::iterator part = parts.begin();
mtpRequestId requestId = MTP::send(MTPupload_SaveFilePart(MTP_long(partsOfId), MTP_int(part.key()), MTP_bytes(part.value())), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::uploadDcId(todc));
requestsSent.insert(requestId, part.value());
dcMap.insert(requestId, todc);
sentSize += part.value().size();
sentSizes[todc] += part.value().size();
parts.erase(part);
}
nextTimer.start(UploadRequestInterval);
}
void FileUploader::cancel(const FullMsgId &msgId) {
uploaded.remove(msgId);
if (uploading == msgId) {
currentFailed();
} else {
queue.remove(msgId);
}
}
void FileUploader::pause(const FullMsgId &msgId) {
_paused = msgId;
}
void FileUploader::unpause() {
_paused = FullMsgId();
sendNext();
}
void FileUploader::confirm(const FullMsgId &msgId) {
}
void FileUploader::clear() {
uploaded.clear();
queue.clear();
for (QMap<mtpRequestId, QByteArray>::const_iterator i = requestsSent.cbegin(), e = requestsSent.cend(); i != e; ++i) {
MTP::cancel(i.key());
}
requestsSent.clear();
for (QMap<mtpRequestId, int32>::const_iterator i = docRequestsSent.cbegin(), e = docRequestsSent.cend(); i != e; ++i) {
MTP::cancel(i.key());
}
docRequestsSent.clear();
dcMap.clear();
sentSize = 0;
for (int32 i = 0; i < MTPUploadSessionsCount; ++i) {
MTP::stopSession(MTP::uploadDcId(i));
sentSizes[i] = 0;
}
killSessionsTimer.stop();
}
void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
QMap<mtpRequestId, int32>::iterator j = docRequestsSent.end();
QMap<mtpRequestId, QByteArray>::iterator i = requestsSent.find(requestId);
if (i == requestsSent.cend()) {
j = docRequestsSent.find(requestId);
}
if (i != requestsSent.cend() || j != docRequestsSent.cend()) {
if (mtpIsFalse(result)) { // failed to upload current file
currentFailed();
return;
} else {
QMap<mtpRequestId, int32>::iterator dcIt = dcMap.find(requestId);
if (dcIt == dcMap.cend()) { // must not happen
currentFailed();
return;
}
int32 dc = dcIt.value();
dcMap.erase(dcIt);
int32 sentPartSize = 0;
Queue::const_iterator k = queue.constFind(uploading);
if (i != requestsSent.cend()) {
sentPartSize = i.value().size();
requestsSent.erase(i);
} else {
sentPartSize = k->docPartSize;
docRequestsSent.erase(j);
}
sentSize -= sentPartSize;
sentSizes[dc] -= sentPartSize;
if (k->type() == SendMediaType::Photo) {
k->fileSentSize += sentPartSize;
PhotoData *photo = App::photo(k->id());
if (photo->uploading() && k->file) {
photo->uploadingData->size = k->file->partssize;
photo->uploadingData->offset = k->fileSentSize;
}
emit photoProgress(k.key());
} else if (k->type() == SendMediaType::File || k->type() == SendMediaType::Audio) {
DocumentData *doc = App::document(k->id());
if (doc->uploading()) {
doc->uploadOffset = (k->docSentParts - docRequestsSent.size()) * k->docPartSize;
if (doc->uploadOffset > doc->size) {
doc->uploadOffset = doc->size;
}
}
emit documentProgress(k.key());
}
}
}
sendNext();
}
bool FileUploader::partFailed(const RPCError &error, mtpRequestId requestId) {
if (MTP::isDefaultHandledError(error)) return false;
if (requestsSent.constFind(requestId) != requestsSent.cend() || docRequestsSent.constFind(requestId) != docRequestsSent.cend()) { // failed to upload current file
currentFailed();
}
sendNext();
return true;
}

View File

@@ -0,0 +1,140 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "localimageloader.h"
class FileUploader : public QObject, public RPCSender {
Q_OBJECT
public:
FileUploader();
void uploadMedia(const FullMsgId &msgId, const SendMediaReady &image);
void upload(const FullMsgId &msgId, const FileLoadResultPtr &file);
int32 currentOffset(const FullMsgId &msgId) const; // -1 means file not found
int32 fullSize(const FullMsgId &msgId) const;
void cancel(const FullMsgId &msgId);
void pause(const FullMsgId &msgId);
void confirm(const FullMsgId &msgId);
void clear();
public slots:
void unpause();
void sendNext();
void killSessions();
signals:
void photoReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
void documentReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
void thumbDocumentReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb);
void photoProgress(const FullMsgId &msgId);
void documentProgress(const FullMsgId &msgId);
void photoFailed(const FullMsgId &msgId);
void documentFailed(const FullMsgId &msgId);
private:
struct File {
File(const SendMediaReady &media) : media(media), docSentParts(0) {
partsCount = media.parts.size();
if (type() == SendMediaType::File || type() == SendMediaType::Audio) {
setDocSize(media.file.isEmpty() ? media.data.size() : media.filesize);
} else {
docSize = docPartSize = docPartsCount = 0;
}
}
File(const FileLoadResultPtr &file) : file(file), docSentParts(0) {
partsCount = (type() == SendMediaType::Photo) ? file->fileparts.size() : file->thumbparts.size();
if (type() == SendMediaType::File || type() == SendMediaType::Audio) {
setDocSize(file->filesize);
} else {
docSize = docPartSize = docPartsCount = 0;
}
}
void setDocSize(int32 size) {
docSize = size;
if (docSize >= 1024 * 1024 || !setPartSize(DocumentUploadPartSize0)) {
if (docSize > 32 * 1024 * 1024 || !setPartSize(DocumentUploadPartSize1)) {
if (!setPartSize(DocumentUploadPartSize2)) {
if (!setPartSize(DocumentUploadPartSize3)) {
if (!setPartSize(DocumentUploadPartSize4)) {
LOG(("Upload Error: bad doc size: %1").arg(docSize));
}
}
}
}
}
}
bool setPartSize(uint32 partSize) {
docPartSize = partSize;
docPartsCount = (docSize / docPartSize) + ((docSize % docPartSize) ? 1 : 0);
return (docPartsCount <= DocumentMaxPartsCount);
}
FileLoadResultPtr file;
SendMediaReady media;
int32 partsCount;
mutable int32 fileSentSize;
uint64 id() const {
return file ? file->id : media.id;
}
SendMediaType type() const {
return file ? file->type : media.type;
}
uint64 thumbId() const {
return file ? file->thumbId : media.thumbId;
}
const QString &filename() const {
return file ? file->filename : media.filename;
}
HashMd5 md5Hash;
QSharedPointer<QFile> docFile;
int32 docSentParts;
int32 docSize;
int32 docPartSize;
int32 docPartsCount;
};
typedef QMap<FullMsgId, File> Queue;
void partLoaded(const MTPBool &result, mtpRequestId requestId);
bool partFailed(const RPCError &err, mtpRequestId requestId);
void currentFailed();
QMap<mtpRequestId, QByteArray> requestsSent;
QMap<mtpRequestId, int32> docRequestsSent;
QMap<mtpRequestId, int32> dcMap;
uint32 sentSize;
uint32 sentSizes[MTPUploadSessionsCount];
FullMsgId uploading, _paused;
Queue queue;
Queue uploaded;
QTimer nextTimer, killSessionsTimer;
};

View File

@@ -0,0 +1,446 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#include "localimageloader.h"
#include "core/file_utilities.h"
#include "media/media_audio.h"
#include "boxes/send_files_box.h"
#include "media/media_clip_reader.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "lang.h"
#include "boxes/confirmbox.h"
TaskQueue::TaskQueue(QObject *parent, int32 stopTimeoutMs) : QObject(parent), _thread(0), _worker(0), _stopTimer(0) {
if (stopTimeoutMs > 0) {
_stopTimer = new QTimer(this);
connect(_stopTimer, SIGNAL(timeout()), this, SLOT(stop()));
_stopTimer->setSingleShot(true);
_stopTimer->setInterval(stopTimeoutMs);
}
}
TaskId TaskQueue::addTask(TaskPtr task) {
{
QMutexLocker lock(&_tasksToProcessMutex);
_tasksToProcess.push_back(task);
}
wakeThread();
return task->id();
}
void TaskQueue::addTasks(const TasksList &tasks) {
{
QMutexLocker lock(&_tasksToProcessMutex);
_tasksToProcess.append(tasks);
}
wakeThread();
}
void TaskQueue::wakeThread() {
if (!_thread) {
_thread = new QThread();
_worker = new TaskQueueWorker(this);
_worker->moveToThread(_thread);
connect(this, SIGNAL(taskAdded()), _worker, SLOT(onTaskAdded()));
connect(_worker, SIGNAL(taskProcessed()), this, SLOT(onTaskProcessed()));
_thread->start();
}
if (_stopTimer) _stopTimer->stop();
emit taskAdded();
}
void TaskQueue::cancelTask(TaskId id) {
{
QMutexLocker lock(&_tasksToProcessMutex);
for (int32 i = 0, l = _tasksToProcess.size(); i != l; ++i) {
if (_tasksToProcess.at(i)->id() == id) {
_tasksToProcess.removeAt(i);
return;
}
}
}
QMutexLocker lock(&_tasksToFinishMutex);
for (int32 i = 0, l = _tasksToFinish.size(); i != l; ++i) {
if (_tasksToFinish.at(i)->id() == id) {
_tasksToFinish.removeAt(i);
return;
}
}
}
void TaskQueue::onTaskProcessed() {
do {
TaskPtr task;
{
QMutexLocker lock(&_tasksToFinishMutex);
if (_tasksToFinish.isEmpty()) break;
task = _tasksToFinish.front();
_tasksToFinish.pop_front();
}
task->finish();
} while (true);
if (_stopTimer) {
QMutexLocker lock(&_tasksToProcessMutex);
if (_tasksToProcess.isEmpty()) {
_stopTimer->start();
}
}
}
void TaskQueue::stop() {
if (_thread) {
_thread->requestInterruption();
_thread->quit();
DEBUG_LOG(("Waiting for taskThread to finish"));
_thread->wait();
delete _worker;
delete _thread;
_worker = 0;
_thread = 0;
}
_tasksToProcess.clear();
_tasksToFinish.clear();
}
TaskQueue::~TaskQueue() {
stop();
delete _stopTimer;
}
void TaskQueueWorker::onTaskAdded() {
if (_inTaskAdded) return;
_inTaskAdded = true;
bool someTasksLeft = false;
do {
TaskPtr task;
{
QMutexLocker lock(&_queue->_tasksToProcessMutex);
if (!_queue->_tasksToProcess.isEmpty()) {
task = _queue->_tasksToProcess.front();
}
}
if (task) {
task->process();
bool emitTaskProcessed = false;
{
QMutexLocker lockToProcess(&_queue->_tasksToProcessMutex);
if (!_queue->_tasksToProcess.isEmpty() && _queue->_tasksToProcess.front() == task) {
_queue->_tasksToProcess.pop_front();
someTasksLeft = !_queue->_tasksToProcess.isEmpty();
QMutexLocker lockToFinish(&_queue->_tasksToFinishMutex);
emitTaskProcessed = _queue->_tasksToFinish.isEmpty();
_queue->_tasksToFinish.push_back(task);
}
}
if (emitTaskProcessed) {
emit taskProcessed();
}
}
QCoreApplication::processEvents();
} while (someTasksLeft && !thread()->isInterruptionRequested());
_inTaskAdded = false;
}
FileLoadTask::FileLoadTask(const QString &filepath, const QImage &image, SendMediaType type, const FileLoadTo &to, const QString &caption) : _id(rand_value<uint64>())
, _to(to)
, _filepath(filepath)
, _image(image)
, _type(type)
, _caption(caption) {
}
FileLoadTask::FileLoadTask(const QByteArray &content, const QImage &image, SendMediaType type, const FileLoadTo &to, const QString &caption) : _id(rand_value<uint64>())
, _to(to)
, _content(content)
, _image(image)
, _type(type)
, _caption(caption) {
}
FileLoadTask::FileLoadTask(const QByteArray &voice, int32 duration, const VoiceWaveform &waveform, const FileLoadTo &to, const QString &caption) : _id(rand_value<uint64>())
, _to(to)
, _content(voice)
, _duration(duration)
, _waveform(waveform)
, _type(SendMediaType::Audio)
, _caption(caption) {
}
void FileLoadTask::process() {
const QString stickerMime = qsl("image/webp");
_result = MakeShared<FileLoadResult>(_id, _to, _caption);
QString filename, filemime;
qint64 filesize = 0;
QByteArray filedata;
uint64 thumbId = 0;
QString thumbname = "thumb.jpg";
QByteArray thumbdata;
auto animated = false;
auto song = false;
auto gif = false;
auto voice = (_type == SendMediaType::Audio);
auto fullimage = base::take(_image);
auto info = _filepath.isEmpty() ? QFileInfo() : QFileInfo(_filepath);
if (info.exists()) {
if (info.isDir()) {
_result->filesize = -1;
return;
}
filesize = info.size();
filemime = mimeTypeForFile(info).name();
filename = info.fileName();
auto opaque = (filemime != stickerMime);
fullimage = App::readImage(_filepath, 0, opaque, &animated);
} else if (!_content.isEmpty()) {
filesize = _content.size();
if (voice) {
filename = filedialogDefaultName(qsl("audio"), qsl(".ogg"), QString(), true);
filemime = "audio/ogg";
} else {
auto mimeType = mimeTypeForData(_content);
filemime = mimeType.name();
if (filemime != stickerMime) {
fullimage = Images::prepareOpaque(std::move(fullimage));
}
if (filemime == "image/jpeg") {
filename = filedialogDefaultName(qsl("photo"), qsl(".jpg"), QString(), true);
} else if (filemime == "image/png") {
filename = filedialogDefaultName(qsl("image"), qsl(".png"), QString(), true);
} else {
QString ext;
QStringList patterns = mimeType.globPatterns();
if (!patterns.isEmpty()) {
ext = patterns.front().replace('*', QString());
}
filename = filedialogDefaultName(qsl("file"), ext, QString(), true);
}
}
} else if (!fullimage.isNull() && fullimage.width() > 0) {
if (_type == SendMediaType::Photo) {
auto w = fullimage.width(), h = fullimage.height();
if (w >= 20 * h || h >= 20 * w) {
_type = SendMediaType::File;
} else {
filesize = -1; // Fill later.
filemime = mimeTypeForName("image/jpeg").name();
filename = filedialogDefaultName(qsl("image"), qsl(".jpg"), QString(), true);
}
}
if (_type == SendMediaType::File) {
filemime = mimeTypeForName("image/png").name();
filename = filedialogDefaultName(qsl("image"), qsl(".png"), QString(), true);
{
QBuffer buffer(&_content);
fullimage.save(&buffer, "PNG");
}
filesize = _content.size();
}
fullimage = Images::prepareOpaque(std::move(fullimage));
}
_result->filesize = (int32)qMin(filesize, qint64(INT_MAX));
if (!filesize || filesize > App::kFileSizeLimit) {
return;
}
PreparedPhotoThumbs photoThumbs;
QVector<MTPPhotoSize> photoSizes;
QPixmap thumb;
QVector<MTPDocumentAttribute> attributes(1, MTP_documentAttributeFilename(MTP_string(filename)));
MTPPhotoSize thumbSize(MTP_photoSizeEmpty(MTP_string("")));
MTPPhoto photo(MTP_photoEmpty(MTP_long(0)));
MTPDocument document(MTP_documentEmpty(MTP_long(0)));
if (!voice) {
if (filemime == qstr("audio/mp3") || filemime == qstr("audio/m4a") || filemime == qstr("audio/aac") || filemime == qstr("audio/ogg") || filemime == qstr("audio/flac") ||
filename.endsWith(qstr(".mp3"), Qt::CaseInsensitive) || filename.endsWith(qstr(".m4a"), Qt::CaseInsensitive) ||
filename.endsWith(qstr(".aac"), Qt::CaseInsensitive) || filename.endsWith(qstr(".ogg"), Qt::CaseInsensitive) ||
filename.endsWith(qstr(".flac"), Qt::CaseInsensitive)) {
QImage cover;
QByteArray coverBytes, coverFormat;
MTPDocumentAttribute audioAttribute = audioReadSongAttributes(_filepath, _content, cover, coverBytes, coverFormat);
if (audioAttribute.type() == mtpc_documentAttributeAudio) {
attributes.push_back(audioAttribute);
song = true;
if (!cover.isNull()) { // cover to thumb
int32 cw = cover.width(), ch = cover.height();
if (cw < 20 * ch && ch < 20 * cw) {
QPixmap full = (cw > 90 || ch > 90) ? App::pixmapFromImageInPlace(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : App::pixmapFromImageInPlace(std::move(cover));
{
QByteArray thumbFormat = "JPG";
int32 thumbQuality = 87;
QBuffer buffer(&thumbdata);
full.save(&buffer, thumbFormat, thumbQuality);
}
thumb = full;
thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0));
thumbId = rand_value<uint64>();
}
}
}
}
if (filemime == qstr("video/mp4") || filename.endsWith(qstr(".mp4"), Qt::CaseInsensitive) || animated) {
QImage cover;
MTPDocumentAttribute animatedAttribute = Media::Clip::readAttributes(_filepath, _content, cover);
if (animatedAttribute.type() == mtpc_documentAttributeVideo) {
int32 cw = cover.width(), ch = cover.height();
if (cw < 20 * ch && ch < 20 * cw) {
attributes.push_back(MTP_documentAttributeAnimated());
attributes.push_back(animatedAttribute);
gif = true;
QPixmap full = (cw > 90 || ch > 90) ? App::pixmapFromImageInPlace(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : App::pixmapFromImageInPlace(std::move(cover));
{
QByteArray thumbFormat = "JPG";
int32 thumbQuality = 87;
QBuffer buffer(&thumbdata);
full.save(&buffer, thumbFormat, thumbQuality);
}
thumb = full;
thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0));
thumbId = rand_value<uint64>();
if (filename.endsWith(qstr(".mp4"), Qt::CaseInsensitive)) {
filemime = qstr("video/mp4");
}
}
}
}
}
if (!fullimage.isNull() && fullimage.width() > 0 && !song && !gif && !voice) {
auto w = fullimage.width(), h = fullimage.height();
attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h)));
if (w < 20 * h && h < 20 * w) {
if (animated) {
attributes.push_back(MTP_documentAttributeAnimated());
} else if (_type != SendMediaType::File) {
auto thumb = (w > 100 || h > 100) ? App::pixmapFromImageInPlace(fullimage.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage);
photoThumbs.insert('s', thumb);
photoSizes.push_back(MTP_photoSize(MTP_string("s"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0)));
auto medium = (w > 320 || h > 320) ? App::pixmapFromImageInPlace(fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage);
photoThumbs.insert('m', medium);
photoSizes.push_back(MTP_photoSize(MTP_string("m"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0)));
auto full = (w > 1280 || h > 1280) ? App::pixmapFromImageInPlace(fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage);
photoThumbs.insert('y', full);
photoSizes.push_back(MTP_photoSize(MTP_string("y"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0)));
{
QBuffer buffer(&filedata);
full.save(&buffer, "JPG", 87);
}
MTPDphoto::Flags photoFlags = 0;
photo = MTP_photo(MTP_flags(photoFlags), MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_vector<MTPPhotoSize>(photoSizes));
if (filesize < 0) {
filesize = _result->filesize = filedata.size();
}
}
QByteArray thumbFormat = "JPG";
int32 thumbQuality = 87;
if (!animated && filemime == stickerMime && w > 0 && h > 0 && w <= StickerMaxSize && h <= StickerMaxSize && filesize < StickerInMemory) {
MTPDdocumentAttributeSticker::Flags stickerFlags = 0;
attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(""), MTP_inputStickerSetEmpty(), MTPMaskCoords()));
thumbFormat = "webp";
thumbname = qsl("thumb.webp");
}
QPixmap full = (w > 90 || h > 90) ? App::pixmapFromImageInPlace(fullimage.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage, Qt::ColorOnly);
{
QBuffer buffer(&thumbdata);
full.save(&buffer, thumbFormat, thumbQuality);
}
thumb = full;
thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0));
thumbId = rand_value<uint64>();
}
}
if (voice) {
attributes[0] = MTP_documentAttributeAudio(MTP_flags(MTPDdocumentAttributeAudio::Flag::f_voice | MTPDdocumentAttributeAudio::Flag::f_waveform), MTP_int(_duration), MTPstring(), MTPstring(), MTP_bytes(documentWaveformEncode5bit(_waveform)));
attributes.resize(1);
document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_int(0), MTP_vector<MTPDocumentAttribute>(attributes));
} else if (_type != SendMediaType::Photo) {
document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_int(0), MTP_vector<MTPDocumentAttribute>(attributes));
_type = SendMediaType::File;
}
_result->type = _type;
_result->filepath = _filepath;
_result->content = _content;
_result->filename = filename;
_result->filemime = filemime;
_result->setFileData(filedata);
_result->thumbId = thumbId;
_result->thumbname = thumbname;
_result->setThumbData(thumbdata);
_result->thumb = thumb;
_result->photo = photo;
_result->document = document;
_result->photoThumbs = photoThumbs;
}
void FileLoadTask::finish() {
if (!_result || !_result->filesize) {
Ui::show(Box<InformBox>(lng_send_image_empty(lt_name, _filepath)), KeepOtherLayers);
} else if (_result->filesize == -1) { // dir
Ui::show(Box<InformBox>(lng_send_folder(lt_name, QFileInfo(_filepath).dir().dirName())), KeepOtherLayers);
} else if (_result->filesize > App::kFileSizeLimit) {
Ui::show(Box<InformBox>(lng_send_image_too_large(lt_name, _filepath)), KeepOtherLayers);
} else if (App::main()) {
App::main()->onSendFileConfirm(_result);
}
}

View File

@@ -0,0 +1,262 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
enum class CompressConfirm {
Auto,
Yes,
No,
None,
};
enum class SendMediaType {
Photo,
Audio,
File,
};
struct SendMediaPrepare {
SendMediaPrepare(const QString &file, const PeerId &peer, SendMediaType type, MsgId replyTo) : id(rand_value<PhotoId>()), file(file), peer(peer), type(type), replyTo(replyTo) {
}
SendMediaPrepare(const QImage &img, const PeerId &peer, SendMediaType type, MsgId replyTo) : id(rand_value<PhotoId>()), img(img), peer(peer), type(type), replyTo(replyTo) {
}
SendMediaPrepare(const QByteArray &data, const PeerId &peer, SendMediaType type, MsgId replyTo) : id(rand_value<PhotoId>()), data(data), peer(peer), type(type), replyTo(replyTo) {
}
SendMediaPrepare(const QByteArray &data, int duration, const PeerId &peer, SendMediaType type, MsgId replyTo) : id(rand_value<PhotoId>()), data(data), peer(peer), type(type), duration(duration), replyTo(replyTo) {
}
PhotoId id;
QString file;
QImage img;
QByteArray data;
PeerId peer;
SendMediaType type;
int duration = 0;
MsgId replyTo;
};
using SendMediaPrepareList = QList<SendMediaPrepare>;
using UploadFileParts = QMap<int, QByteArray>;
struct SendMediaReady {
SendMediaReady() = default; // temp
SendMediaReady(SendMediaType type, const QString &file, const QString &filename, int32 filesize, const QByteArray &data, const uint64 &id, const uint64 &thumbId, const QString &thumbExt, const PeerId &peer, const MTPPhoto &photo, const PreparedPhotoThumbs &photoThumbs, const MTPDocument &document, const QByteArray &jpeg, MsgId replyTo)
: replyTo(replyTo)
, type(type)
, file(file)
, filename(filename)
, filesize(filesize)
, data(data)
, thumbExt(thumbExt)
, id(id)
, thumbId(thumbId)
, peer(peer)
, photo(photo)
, document(document)
, photoThumbs(photoThumbs) {
if (!jpeg.isEmpty()) {
int32 size = jpeg.size();
for (int32 i = 0, part = 0; i < size; i += UploadPartSize, ++part) {
parts.insert(part, jpeg.mid(i, UploadPartSize));
}
jpeg_md5.resize(32);
hashMd5Hex(jpeg.constData(), jpeg.size(), jpeg_md5.data());
}
}
MsgId replyTo;
SendMediaType type;
QString file, filename;
int32 filesize;
QByteArray data;
QString thumbExt;
uint64 id, thumbId; // id always file-id of media, thumbId is file-id of thumb ( == id for photos)
PeerId peer;
MTPPhoto photo;
MTPDocument document;
PreparedPhotoThumbs photoThumbs;
UploadFileParts parts;
QByteArray jpeg_md5;
QString caption;
};
class Task {
public:
virtual void process() = 0; // is executed in a separate thread
virtual void finish() = 0; // is executed in the same as TaskQueue thread
virtual ~Task() = default;
TaskId id() const {
return static_cast<TaskId>(const_cast<Task*>(this));
}
};
using TaskPtr = QSharedPointer<Task>;
using TasksList = QList<TaskPtr>;
class TaskQueueWorker;
class TaskQueue : public QObject {
Q_OBJECT
public:
TaskQueue(QObject *parent, int32 stopTimeoutMs = 0); // <= 0 - never stop worker
TaskId addTask(TaskPtr task);
void addTasks(const TasksList &tasks);
void cancelTask(TaskId id); // this task finish() won't be called
~TaskQueue();
signals:
void taskAdded();
public slots:
void onTaskProcessed();
void stop();
private:
friend class TaskQueueWorker;
void wakeThread();
TasksList _tasksToProcess, _tasksToFinish;
QMutex _tasksToProcessMutex, _tasksToFinishMutex;
QThread *_thread;
TaskQueueWorker *_worker;
QTimer *_stopTimer;
};
class TaskQueueWorker : public QObject {
Q_OBJECT
public:
TaskQueueWorker(TaskQueue *queue) : _queue(queue) {
}
signals:
void taskProcessed();
public slots:
void onTaskAdded();
private:
TaskQueue *_queue;
bool _inTaskAdded = false;
};
struct FileLoadTo {
FileLoadTo(const PeerId &peer, bool silent, MsgId replyTo)
: peer(peer)
, silent(silent)
, replyTo(replyTo) {
}
PeerId peer;
bool silent;
MsgId replyTo;
};
struct FileLoadResult {
FileLoadResult(const uint64 &id, const FileLoadTo &to, const QString &caption)
: id(id)
, to(to)
, caption(caption) {
}
uint64 id;
FileLoadTo to;
SendMediaType type;
QString filepath;
QByteArray content;
QString filename;
QString filemime;
int32 filesize = 0;
UploadFileParts fileparts;
QByteArray filemd5;
int32 partssize;
uint64 thumbId = 0; // id is always file-id of media, thumbId is file-id of thumb ( == id for photos)
QString thumbname;
UploadFileParts thumbparts;
QByteArray thumbmd5;
QPixmap thumb;
MTPPhoto photo;
MTPDocument document;
PreparedPhotoThumbs photoThumbs;
QString caption;
void setFileData(const QByteArray &filedata) {
if (filedata.isEmpty()) {
partssize = 0;
} else {
partssize = filedata.size();
for (int32 i = 0, part = 0; i < partssize; i += UploadPartSize, ++part) {
fileparts.insert(part, filedata.mid(i, UploadPartSize));
}
filemd5.resize(32);
hashMd5Hex(filedata.constData(), filedata.size(), filemd5.data());
}
}
void setThumbData(const QByteArray &thumbdata) {
if (!thumbdata.isEmpty()) {
int32 size = thumbdata.size();
for (int32 i = 0, part = 0; i < size; i += UploadPartSize, ++part) {
thumbparts.insert(part, thumbdata.mid(i, UploadPartSize));
}
thumbmd5.resize(32);
hashMd5Hex(thumbdata.constData(), thumbdata.size(), thumbmd5.data());
}
}
};
typedef QSharedPointer<FileLoadResult> FileLoadResultPtr;
class FileLoadTask : public Task {
public:
FileLoadTask(const QString &filepath, const QImage &image, SendMediaType type, const FileLoadTo &to, const QString &caption);
FileLoadTask(const QByteArray &content, const QImage &image, SendMediaType type, const FileLoadTo &to, const QString &caption);
FileLoadTask(const QByteArray &voice, int32 duration, const VoiceWaveform &waveform, const FileLoadTo &to, const QString &caption);
uint64 fileid() const {
return _id;
}
void process();
void finish();
protected:
uint64 _id;
FileLoadTo _to;
QString _filepath;
QByteArray _content;
QImage _image;
int32 _duration = 0;
VoiceWaveform _waveform;
SendMediaType _type;
QString _caption;
FileLoadResultPtr _result;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,199 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/basic_types.h"
namespace Window {
namespace Theme {
struct Cached;
} // namespace Theme
} // namespace Window
namespace Local {
void start();
void finish();
void readSettings();
void writeSettings();
void writeUserSettings();
void writeMtpData();
void reset();
bool checkPasscode(const QByteArray &passcode);
void setPasscode(const QByteArray &passcode);
enum ClearManagerTask {
ClearManagerAll = 0xFFFF,
ClearManagerDownloads = 0x01,
ClearManagerStorage = 0x02,
};
struct ClearManagerData;
class ClearManager : public QObject {
Q_OBJECT
public:
ClearManager();
bool addTask(int task);
bool hasTask(ClearManagerTask task);
void start();
void stop();
signals:
void succeed(int task, void *manager);
void failed(int task, void *manager);
private slots:
void onStart();
private:
~ClearManager();
ClearManagerData *data;
};
enum ReadMapState {
ReadMapFailed = 0,
ReadMapDone = 1,
ReadMapPassNeeded = 2,
};
ReadMapState readMap(const QByteArray &pass);
int32 oldMapVersion();
int32 oldSettingsVersion();
struct MessageDraft {
MessageDraft(MsgId msgId = 0, TextWithTags textWithTags = TextWithTags(), bool previewCancelled = false)
: msgId(msgId)
, textWithTags(textWithTags)
, previewCancelled(previewCancelled) {
}
MsgId msgId;
TextWithTags textWithTags;
bool previewCancelled;
};
void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const MessageDraft &editDraft);
void readDraftsWithCursors(History *h);
void writeDraftCursors(const PeerId &peer, const MessageCursor &localCursor, const MessageCursor &editCursor);
bool hasDraftCursors(const PeerId &peer);
bool hasDraft(const PeerId &peer);
void writeFileLocation(MediaKey location, const FileLocation &local);
FileLocation readFileLocation(MediaKey location, bool check = true);
void writeImage(const StorageKey &location, const ImagePtr &img);
void writeImage(const StorageKey &location, const StorageImageSaved &jpeg, bool overwrite = true);
TaskId startImageLoad(const StorageKey &location, mtpFileLoader *loader);
int32 hasImages();
qint64 storageImagesSize();
void writeStickerImage(const StorageKey &location, const QByteArray &data, bool overwrite = true);
TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader);
bool willStickerImageLoad(const StorageKey &location);
bool copyStickerImage(const StorageKey &oldLocation, const StorageKey &newLocation);
int32 hasStickers();
qint64 storageStickersSize();
void writeAudio(const StorageKey &location, const QByteArray &data, bool overwrite = true);
TaskId startAudioLoad(const StorageKey &location, mtpFileLoader *loader);
bool copyAudio(const StorageKey &oldLocation, const StorageKey &newLocation);
int32 hasAudios();
qint64 storageAudiosSize();
void writeWebFile(const QString &url, const QByteArray &data, bool overwrite = true);
TaskId startWebFileLoad(const QString &url, webFileLoader *loader);
int32 hasWebFiles();
qint64 storageWebFilesSize();
void countVoiceWaveform(DocumentData *document);
void cancelTask(TaskId id);
void writeInstalledStickers();
void writeFeaturedStickers();
void writeRecentStickers();
void writeArchivedStickers();
void readInstalledStickers();
void readFeaturedStickers();
void readRecentStickers();
void readArchivedStickers();
int32 countStickersHash(bool checkOutdatedInfo = false);
int32 countRecentStickersHash();
int32 countFeaturedStickersHash();
void writeSavedGifs();
void readSavedGifs();
int32 countSavedGifsHash();
void writeBackground(int32 id, const QImage &img);
bool readBackground();
void writeTheme(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, const Window::Theme::Cached &cache);
void clearTheme();
bool hasTheme();
QString themePaletteAbsolutePath();
bool copyThemeColorsToPalette(const QString &file);
void writeRecentHashtagsAndBots();
void readRecentHashtagsAndBots();
void addSavedPeer(PeerData *peer, const QDateTime &position);
void removeSavedPeer(PeerData *peer);
void readSavedPeers();
void writeReportSpamStatuses();
void makeBotTrusted(UserData *bot);
bool isBotTrusted(UserData *bot);
bool encrypt(const void *src, void *dst, uint32 len, const void *key128);
bool decrypt(const void *src, void *dst, uint32 len, const void *key128);
namespace internal {
class Manager : public QObject {
Q_OBJECT
public:
Manager();
void writeMap(bool fast);
void writingMap();
void writeLocations(bool fast);
void writingLocations();
void finish();
public slots:
void mapWriteTimeout();
void locationsWriteTimeout();
private:
QTimer _mapWriteTimer;
QTimer _locationsWriteTimer;
};
} // namespace internal
} // namespace Local

View File

@@ -0,0 +1,42 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#include "serialize/serialize_common.h"
namespace Serialize {
void writeStorageImageLocation(QDataStream &stream, const StorageImageLocation &loc) {
stream << qint32(loc.width()) << qint32(loc.height());
stream << qint32(loc.dc()) << quint64(loc.volume()) << qint32(loc.local()) << quint64(loc.secret());
}
StorageImageLocation readStorageImageLocation(QDataStream &stream) {
qint32 width, height, dc, local;
quint64 volume, secret;
stream >> width >> height >> dc >> volume >> local >> secret;
return StorageImageLocation(width, height, dc, volume, local, secret);
}
int storageImageLocationSize() {
// width + height + dc + volume + local + secret
return sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(quint64) + sizeof(qint32) + sizeof(quint64);
}
} // namespace Serialize

View File

@@ -0,0 +1,57 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "ui/images.h"
namespace Serialize {
inline int stringSize(const QString &str) {
return sizeof(quint32) + str.size() * sizeof(ushort);
}
inline int bytearraySize(const QByteArray &arr) {
return sizeof(quint32) + arr.size();
}
inline int dateTimeSize() {
return (sizeof(qint64) + sizeof(quint32) + sizeof(qint8));
}
void writeStorageImageLocation(QDataStream &stream, const StorageImageLocation &loc);
StorageImageLocation readStorageImageLocation(QDataStream &stream);
int storageImageLocationSize();
template <typename T>
inline T read(QDataStream &stream) {
auto result = T();
stream >> result;
return result;
}
template <>
inline MTP::AuthKey::Data read<MTP::AuthKey::Data>(QDataStream &stream) {
auto result = MTP::AuthKey::Data();
stream.readRawData(result.data(), result.size());
return result;
}
} // namespace Serialize

View File

@@ -0,0 +1,169 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#include "serialize/serialize_document.h"
#include "serialize/serialize_common.h"
namespace {
enum StickerSetType {
StickerSetTypeEmpty = 0,
StickerSetTypeID = 1,
StickerSetTypeShortName = 2,
};
} // namespace
namespace Serialize {
void Document::writeToStream(QDataStream &stream, DocumentData *document) {
stream << quint64(document->id) << quint64(document->_access) << qint32(document->date);
stream << qint32(document->_version);
stream << document->name << document->mime << qint32(document->_dc) << qint32(document->size);
stream << qint32(document->dimensions.width()) << qint32(document->dimensions.height());
stream << qint32(document->type);
if (auto sticker = document->sticker()) {
stream << document->sticker()->alt;
switch (document->sticker()->set.type()) {
case mtpc_inputStickerSetID: {
stream << qint32(StickerSetTypeID);
} break;
case mtpc_inputStickerSetShortName: {
stream << qint32(StickerSetTypeShortName);
} break;
case mtpc_inputStickerSetEmpty:
default: {
stream << qint32(StickerSetTypeEmpty);
} break;
}
writeStorageImageLocation(stream, document->sticker()->loc);
} else {
stream << qint32(document->duration());
writeStorageImageLocation(stream, document->thumb->location());
}
}
DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream &stream, const StickerSetInfo *info) {
quint64 id, access;
QString name, mime;
qint32 date, dc, size, width, height, type, version;
stream >> id >> access >> date;
if (streamAppVersion >= 9061) {
stream >> version;
} else {
version = 0;
}
stream >> name >> mime >> dc >> size;
stream >> width >> height;
stream >> type;
QVector<MTPDocumentAttribute> attributes;
if (!name.isEmpty()) {
attributes.push_back(MTP_documentAttributeFilename(MTP_string(name)));
}
qint32 duration = -1;
StorageImageLocation thumb;
if (type == StickerDocument) {
QString alt;
qint32 typeOfSet;
stream >> alt >> typeOfSet;
thumb = readStorageImageLocation(stream);
MTPDdocumentAttributeSticker::Flags stickerFlags = 0;
if (typeOfSet == StickerSetTypeEmpty) {
attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(alt), MTP_inputStickerSetEmpty(), MTPMaskCoords()));
} else if (info) {
if (info->setId == Stickers::DefaultSetId || info->setId == Stickers::CloudRecentSetId || info->setId == Stickers::CustomSetId) {
typeOfSet = StickerSetTypeEmpty;
}
switch (typeOfSet) {
case StickerSetTypeID: {
attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(alt), MTP_inputStickerSetID(MTP_long(info->setId), MTP_long(info->accessHash)), MTPMaskCoords()));
} break;
case StickerSetTypeShortName: {
attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(alt), MTP_inputStickerSetShortName(MTP_string(info->shortName)), MTPMaskCoords()));
} break;
case StickerSetTypeEmpty:
default: {
attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(alt), MTP_inputStickerSetEmpty(), MTPMaskCoords()));
} break;
}
}
} else {
stream >> duration;
if (type == AnimatedDocument) {
attributes.push_back(MTP_documentAttributeAnimated());
}
thumb = readStorageImageLocation(stream);
}
if (width > 0 && height > 0) {
if (duration >= 0) {
attributes.push_back(MTP_documentAttributeVideo(MTP_int(duration), MTP_int(width), MTP_int(height)));
} else {
attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height)));
}
}
if (!dc && !access) {
return nullptr;
}
return App::documentSet(id, nullptr, access, version, date, attributes, mime, thumb.isNull() ? ImagePtr() : ImagePtr(thumb), dc, size, thumb);
}
DocumentData *Document::readStickerFromStream(int streamAppVersion, QDataStream &stream, const StickerSetInfo &info) {
return readFromStreamHelper(streamAppVersion, stream, &info);
}
DocumentData *Document::readFromStream(int streamAppVersion, QDataStream &stream) {
return readFromStreamHelper(streamAppVersion, stream, nullptr);
}
int Document::sizeInStream(DocumentData *document) {
int result = 0;
// id + access + date + version
result += sizeof(quint64) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32);
// + namelen + name + mimelen + mime + dc + size
result += stringSize(document->name) + stringSize(document->mime) + sizeof(qint32) + sizeof(qint32);
// + width + height
result += sizeof(qint32) + sizeof(qint32);
// + type
result += sizeof(qint32);
if (auto sticker = document->sticker()) { // type == StickerDocument
// + altlen + alt + type-of-set
result += stringSize(sticker->alt) + sizeof(qint32);
// + thumb loc
result += Serialize::storageImageLocationSize();
} else {
// + duration
result += sizeof(qint32);
// + thumb loc
result += Serialize::storageImageLocationSize();
}
return result;
}
} // namespace Serialize

View File

@@ -0,0 +1,51 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "structs.h"
namespace Serialize {
class Document {
public:
struct StickerSetInfo {
StickerSetInfo(uint64 setId, uint64 accessHash, QString shortName)
: setId(setId)
, accessHash(accessHash)
, shortName(shortName) {
}
uint64 setId;
uint64 accessHash;
QString shortName;
};
static void writeToStream(QDataStream &stream, DocumentData *document);
static DocumentData *readStickerFromStream(int streamAppVersion, QDataStream &stream, const StickerSetInfo &info);
static DocumentData *readFromStream(int streamAppVersion, QDataStream &stream);
static int sizeInStream(DocumentData *document);
private:
static DocumentData *readFromStreamHelper(int streamAppVersion, QDataStream &stream, const StickerSetInfo *info);
};
} // namespace Serialize