28 #include "util/compatiblerecursivemutex.h"
32 #include <QRegularExpression>
44 assert(core !=
nullptr);
45 assert(
tox !=
nullptr);
55 , coreLoopLock{&coreLoopLock}
73 constexpr
unsigned fileInterval = 10, idleInterval = 1000;
96 uint64_t filesize = 0;
97 uint8_t *file_id =
nullptr;
98 uint8_t *file_name =
nullptr;
99 size_t nameLength = 0;
100 uint8_t avatarHash[TOX_HASH_LENGTH];
101 if (!data.isEmpty()) {
102 static_assert(TOX_HASH_LENGTH <= TOX_FILE_ID_LENGTH, "TOX_HASH_LENGTH > TOX_FILE_ID_LENGTH!
");
103 tox_hash(avatarHash, reinterpret_cast<const uint8_t*>(data.data()), data.size());
104 filesize = data.size();
105 file_id = avatarHash;
106 file_name = avatarHash;
107 nameLength = TOX_HASH_LENGTH;
109 Tox_Err_File_Send error;
110 const uint32_t fileNum = tox_file_send(tox, friendId, TOX_FILE_KIND_AVATAR, filesize,
111 file_id, file_name, nameLength, &error);
114 case TOX_ERR_FILE_SEND_OK:
116 case TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED:
117 qCritical() << "Friend not connected
";
119 case TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND:
120 qCritical() << "Friend not found
";
122 case TOX_ERR_FILE_SEND_NAME_TOO_LONG:
123 qCritical() << "Name too
long";
125 case TOX_ERR_FILE_SEND_NULL:
126 qCritical() << "Send
null";
128 case TOX_ERR_FILE_SEND_TOO_MANY:
129 qCritical() << "Too many outgoing transfers
";
135 ToxFile file{fileNum, friendId, "", "", filesize, ToxFile::SENDING};
136 file.fileKind = TOX_FILE_KIND_AVATAR;
137 file.avatarData = data;
138 file.resumeFileId.resize(TOX_FILE_ID_LENGTH);
139 tox_file_get_file_id(tox, friendId, fileNum, reinterpret_cast<uint8_t*>(file.resumeFileId.data()),
141 addFile(friendId, fileNum, file);
144 void CoreFile::sendFile(uint32_t friendId, QString filename, QString filePath,
147 QMutexLocker locker{coreLoopLock};
149 ToxString fileName(filename);
150 Tox_Err_File_Send sendErr;
151 uint32_t fileNum = tox_file_send(tox, friendId, TOX_FILE_KIND_DATA, filesize,
152 nullptr, fileName.data(), fileName.size(), &sendErr);
153 if (sendErr != TOX_ERR_FILE_SEND_OK) {
154 qWarning() << "sendFile: Can
't create the Tox file sender (" << sendErr << ")";
155 emit fileSendFailed(friendId, fileName.getQString());
158 qDebug() << QString("sendFile: Created file sender %1 with friend %2").arg(fileNum).arg(friendId);
160 ToxFile file{fileNum, friendId, fileName.getQString(), filePath, static_cast<uint64_t>(filesize), ToxFile::SENDING};
161 file.resumeFileId.resize(TOX_FILE_ID_LENGTH);
162 tox_file_get_file_id(tox, friendId, fileNum, reinterpret_cast<uint8_t*>(file.resumeFileId.data()),
164 if (!file.open(false)) {
165 qWarning() << QString("sendFile: Can't open
file, error: %1
").arg(file.file->errorString());
168 addFile(friendId, fileNum, file);
170 emit fileSendStarted(file);
173 void CoreFile::pauseResumeFile(uint32_t friendId, uint32_t fileId)
175 QMutexLocker locker{coreLoopLock};
177 ToxFile* file = findFile(friendId, fileId);
179 qWarning("pauseResumeFileSend: No such
file in queue
");
183 if (file->status != ToxFile::TRANSMITTING && file->status != ToxFile::PAUSED) {
184 qWarning() << "pauseResumeFileSend: File is stopped
";
188 file->pauseStatus.localPauseToggle();
190 if (file->pauseStatus.paused()) {
191 file->status = ToxFile::PAUSED;
192 file->progress.resetSpeed();
193 emit fileTransferPaused(*file);
195 file->status = ToxFile::TRANSMITTING;
196 emit fileTransferAccepted(*file);
199 if (file->pauseStatus.localPaused()) {
200 tox_file_control(tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_PAUSE,
203 tox_file_control(tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME,
208 void CoreFile::cancelFileSend(uint32_t friendId, uint32_t fileId)
210 QMutexLocker locker{coreLoopLock};
212 ToxFile* file = findFile(friendId, fileId);
218 file->status = ToxFile::CANCELED;
219 emit fileTransferCancelled(*file);
220 tox_file_control(tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr);
221 removeFile(friendId, fileId);
224 void CoreFile::cancelFileRecv(uint32_t friendId, uint32_t fileId)
226 QMutexLocker locker{coreLoopLock};
228 ToxFile* file = findFile(friendId, fileId);
233 file->status = ToxFile::CANCELED;
234 emit fileTransferCancelled(*file);
235 tox_file_control(tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr);
236 removeFile(friendId, fileId);
239 void CoreFile::rejectFileRecvRequest(uint32_t friendId, uint32_t fileId)
241 QMutexLocker locker{coreLoopLock};
243 ToxFile* file = findFile(friendId, fileId);
248 file->status = ToxFile::CANCELED;
249 emit fileTransferCancelled(*file);
250 tox_file_control(tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr);
251 removeFile(friendId, fileId);
254 void CoreFile::acceptFileRecvRequest(uint32_t friendId, uint32_t fileId, QString path)
256 QMutexLocker locker{coreLoopLock};
258 ToxFile* file = findFile(friendId, fileId);
263 file->setFilePath(path);
264 if (!file->open(true)) {
268 file->status = ToxFile::TRANSMITTING;
269 emit fileTransferAccepted(*file);
270 tox_file_control(tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, nullptr);
273 ToxFile* CoreFile::findFile(uint32_t friendId, uint32_t fileId)
275 QMutexLocker locker{coreLoopLock};
277 uint64_t key = getFriendKey(friendId, fileId);
278 if (fileMap.contains(key)) {
279 return &fileMap[key];
282 qWarning() << "findFile: File transfer with ID
" << friendId << ':' << fileId << "doesn
't exist";
286 void CoreFile::addFile(uint32_t friendId, uint32_t fileId, const ToxFile& file)
288 uint64_t key = getFriendKey(friendId, fileId);
290 if (fileMap.contains(key)) {
291 qWarning() << "addFile: Overwriting existing file transfer with same ID" << friendId << ':
'
295 fileMap.insert(key, file);
298 void CoreFile::removeFile(uint32_t friendId, uint32_t fileId)
300 uint64_t key = getFriendKey(friendId, fileId);
301 if (!fileMap.contains(key)) {
302 qWarning() << "removeFile: No such file in queue";
305 fileMap[key].file->close();
309 QString CoreFile::getCleanFileName(QString filename)
311 QRegularExpression regex{QStringLiteral(R"([<>:"/\\|?])")};
312 filename.replace(regex, "_");
317 void CoreFile::onFileReceiveCallback(Tox* tox, uint32_t friendId, uint32_t fileId, uint32_t kind,
318 uint64_t filesize, const uint8_t* fname, size_t fnameLen,
321 Core* core = static_cast<Core*>(vCore);
322 CoreFile* coreFile = core->getCoreFile();
323 auto filename = ToxString(fname, fnameLen);
324 const ToxPk friendPk = core->getFriendPublicKey(friendId);
326 if (kind == TOX_FILE_KIND_AVATAR) {
328 qDebug() << QString("Received empty avatar request %1:%2").arg(friendId).arg(fileId);
329 // Avatars of size 0 means explicitely no avatar
330 tox_file_control(tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr);
331 emit core->friendAvatarRemoved(core->getFriendPublicKey(friendId));
334 if (!ToxClientStandards::IsValidAvatarSize(filesize)) {
336 QString("Received avatar request from %1 with size %2.").arg(friendId).arg(filesize) +
337 QString(" The max size allowed for avatars is %3. Cancelling transfer.").arg(ToxClientStandards::MaxAvatarSize);
338 tox_file_control(tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr);
341 static_assert(TOX_HASH_LENGTH <= TOX_FILE_ID_LENGTH,
342 "TOX_HASH_LENGTH > TOX_FILE_ID_LENGTH!");
343 uint8_t avatarHash[TOX_FILE_ID_LENGTH];
344 tox_file_get_file_id(tox, friendId, fileId, avatarHash, nullptr);
345 QByteArray avatarBytes{static_cast<const char*>(static_cast<const void*>(avatarHash)),
347 emit core->fileAvatarOfferReceived(friendId, fileId, avatarBytes, filesize);
351 const auto cleanFileName = CoreFile::getCleanFileName(filename.getQString());
352 if (cleanFileName != filename.getQString()) {
353 qDebug() << QStringLiteral("Cleaned filename");
354 filename = ToxString(cleanFileName);
355 emit coreFile->fileNameChanged(friendPk);
357 qDebug() << QStringLiteral("filename already clean");
360 qDebug() << QString("Received file request %1:%2 kind %3").arg(friendId).arg(fileId).arg(kind);
362 ToxFile file{fileId, friendId, filename.getBytes(), "", filesize, ToxFile::RECEIVING};
363 file.fileKind = kind;
364 file.resumeFileId.resize(TOX_FILE_ID_LENGTH);
365 tox_file_get_file_id(tox, friendId, fileId, reinterpret_cast<uint8_t*>(file.resumeFileId.data()),
367 coreFile->addFile(friendId, fileId, file);
368 if (kind != TOX_FILE_KIND_AVATAR) {
369 emit coreFile->fileReceiveRequested(file);
373 // TODO(sudden6): This whole method is a mess but needed to get stuff working for now
374 void CoreFile::handleAvatarOffer(uint32_t friendId, uint32_t fileId, bool accept, uint64_t filesize)
377 // If it's an avatar but we already have it cached, cancel
378 qDebug() << QString(
"Received avatar request %1:%2. Rejected since it is in cache.")
381 tox_file_control(
tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL,
nullptr);
386 qDebug() << QString(
"Received avatar request %1:%2. Accepted.")
389 tox_file_control(
tox, friendId, fileId, TOX_FILE_CONTROL_RESUME,
nullptr);
392 file.fileKind = TOX_FILE_KIND_AVATAR;
393 file.resumeFileId.resize(TOX_FILE_ID_LENGTH);
394 tox_file_get_file_id(
tox, friendId, fileId,
reinterpret_cast<uint8_t*
>(
file.resumeFileId.data()),
400 Tox_File_Control control,
void* vCore)
402 Core* core =
static_cast<Core*
>(vCore);
406 qWarning(
"onFileControlCallback: No such file in queue");
410 if (control == TOX_FILE_CONTROL_CANCEL) {
411 if (
file->fileKind != TOX_FILE_KIND_AVATAR)
412 qDebug() <<
"File transfer" << friendId <<
":" << fileId <<
"cancelled by friend";
416 }
else if (control == TOX_FILE_CONTROL_PAUSE) {
417 qDebug() <<
"onFileControlCallback: Received pause for file " << friendId <<
":" << fileId;
418 file->pauseStatus.remotePause();
421 }
else if (control == TOX_FILE_CONTROL_RESUME) {
423 qDebug() <<
"Avatar transfer" << fileId <<
"to friend" << friendId <<
"accepted";
425 qDebug() <<
"onFileControlCallback: Received resume for file " << friendId <<
":" << fileId;
426 file->pauseStatus.remoteResume();
430 qWarning() <<
"Unhandled file control " <<
control <<
" for file " << friendId <<
':' << fileId;
435 size_t length,
void* vCore)
438 Core* core =
static_cast<Core*
>(vCore);
442 qWarning(
"onFileDataCallback: No such file in queue");
449 if (
file->fileKind != TOX_FILE_KIND_AVATAR) {
456 std::unique_ptr<uint8_t[]> data(
new uint8_t[length]);
459 if (
file->fileKind == TOX_FILE_KIND_AVATAR) {
460 QByteArray chunk =
file->avatarData.mid(pos, length);
461 nread = chunk.size();
462 memcpy(data.get(), chunk.data(), nread);
464 file->file->seek(pos);
465 nread =
file->file->read(
reinterpret_cast<char*
>(data.get()), length);
467 qWarning(
"onFileDataCallback: Failed to read from file");
470 tox_file_send_chunk(
tox, friendId, fileId, pos,
nullptr, 0,
nullptr);
474 file->progress.addSample(
file->progress.getBytesSent() + length);
475 file->hashGenerator->addData(
reinterpret_cast<const char*
>(data.get()), length);
478 if (!tox_file_send_chunk(
tox, friendId, fileId, pos, data.get(), nread,
nullptr)) {
479 qWarning(
"onFileDataCallback: Failed to send data chunk");
482 if (
file->fileKind != TOX_FILE_KIND_AVATAR) {
488 const uint8_t* data,
size_t length,
void* vCore)
490 Core* core =
static_cast<Core*
>(vCore);
494 qWarning(
"onFileRecvChunkCallback: No such file in queue");
495 tox_file_control(
tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL,
nullptr);
499 if (
file->progress.getBytesSent() != position) {
500 qWarning(
"onFileRecvChunkCallback: Received a chunk out-of-order, aborting transfer");
501 if (
file->fileKind != TOX_FILE_KIND_AVATAR) {
505 tox_file_control(
tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL,
nullptr);
512 if (
file->fileKind == TOX_FILE_KIND_AVATAR) {
514 pic.loadFromData(
file->avatarData);
516 qDebug() <<
"Got" <<
file->avatarData.size() <<
"bytes of avatar data from" << friendId;
526 if (
file->fileKind == TOX_FILE_KIND_AVATAR) {
527 file->avatarData.append(
reinterpret_cast<const char*
>(data), length);
529 file->file->write(
reinterpret_cast<const char*
>(data), length);
531 file->progress.addSample(
file->progress.getBytesSent() + length);
532 file->hashGenerator->addData(
reinterpret_cast<const char*
>(data), length);
534 if (
file->fileKind != TOX_FILE_KIND_AVATAR) {
550 for (uint64_t key :
fileMap.keys()) {
551 if (key >> 32 != friendId)