mirror of
https://github.com/kotatogram/kotatogram-desktop
synced 2025-08-31 06:35:14 +00:00
Renamed / moved a bunch of files.
Next commit fixes the build.
This commit is contained in:
1134
Telegram/SourceFiles/storage/file_download.cpp
Normal file
1134
Telegram/SourceFiles/storage/file_download.cpp
Normal file
File diff suppressed because it is too large
Load Diff
388
Telegram/SourceFiles/storage/file_download.h
Normal file
388
Telegram/SourceFiles/storage/file_download.h
Normal 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
|
316
Telegram/SourceFiles/storage/file_upload.cpp
Normal file
316
Telegram/SourceFiles/storage/file_upload.cpp
Normal 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;
|
||||
}
|
140
Telegram/SourceFiles/storage/file_upload.h
Normal file
140
Telegram/SourceFiles/storage/file_upload.h
Normal 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;
|
||||
|
||||
};
|
446
Telegram/SourceFiles/storage/localimageloader.cpp
Normal file
446
Telegram/SourceFiles/storage/localimageloader.cpp
Normal 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);
|
||||
}
|
||||
}
|
262
Telegram/SourceFiles/storage/localimageloader.h
Normal file
262
Telegram/SourceFiles/storage/localimageloader.h
Normal 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;
|
||||
|
||||
};
|
4595
Telegram/SourceFiles/storage/localstorage.cpp
Normal file
4595
Telegram/SourceFiles/storage/localstorage.cpp
Normal file
File diff suppressed because it is too large
Load Diff
199
Telegram/SourceFiles/storage/localstorage.h
Normal file
199
Telegram/SourceFiles/storage/localstorage.h
Normal 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
|
42
Telegram/SourceFiles/storage/serialize_common.cpp
Normal file
42
Telegram/SourceFiles/storage/serialize_common.cpp
Normal 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
|
57
Telegram/SourceFiles/storage/serialize_common.h
Normal file
57
Telegram/SourceFiles/storage/serialize_common.h
Normal 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
|
169
Telegram/SourceFiles/storage/serialize_document.cpp
Normal file
169
Telegram/SourceFiles/storage/serialize_document.cpp
Normal 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
|
51
Telegram/SourceFiles/storage/serialize_document.h
Normal file
51
Telegram/SourceFiles/storage/serialize_document.h
Normal 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
|
Reference in New Issue
Block a user