1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00
qTox/src/filetransferinstance.cpp

506 lines
16 KiB
C++
Raw Normal View History

2014-09-06 03:27:26 +08:00
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre 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.
This program 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 COPYING file for more details.
*/
#include "filetransferinstance.h"
#include "core.h"
2014-10-18 11:30:04 +08:00
#include "misc/settings.h"
#include "misc/style.h"
2014-09-11 21:44:34 +08:00
#include <math.h>
2014-09-06 03:27:26 +08:00
#include <QFileDialog>
#include <QMessageBox>
#include <QBuffer>
2014-09-11 21:44:34 +08:00
#include <QDebug>
#include <QPainter>
2014-09-06 03:27:26 +08:00
2014-10-12 23:02:55 +08:00
#define MAX_CONTENT_WIDTH 250
#define MAX_PREVIEW_SIZE 25*1024*1024
2014-09-28 23:57:17 +08:00
2014-09-06 03:27:26 +08:00
uint FileTransferInstance::Idconter = 0;
FileTransferInstance::FileTransferInstance(ToxFile File)
: lastBytesSent{0},
2014-09-06 03:27:26 +08:00
fileNum{File.fileNum}, friendId{File.friendId}, direction{File.direction}
{
id = Idconter++;
state = tsPending;
remotePaused = false;
2014-10-01 01:35:27 +08:00
lastUpdateTime = QDateTime::currentDateTime();
2014-09-06 03:27:26 +08:00
filename = File.fileName;
// update this whenever you change the font in innerStyle.css
QFontMetrics fm(Style::getFont(Style::Small));
2014-10-12 23:02:55 +08:00
filenameElided = fm.elidedText(filename, Qt::ElideRight, MAX_CONTENT_WIDTH);
2014-09-06 03:27:26 +08:00
size = getHumanReadableSize(File.filesize);
2014-10-23 23:39:55 +08:00
contentPrefWidth = std::max(fm.width(filenameElided), fm.width(size)) + fm.leading();
2014-10-12 23:02:55 +08:00
2014-09-06 03:27:26 +08:00
speed = "0B/s";
eta = "00:00";
2014-09-06 03:27:26 +08:00
if (File.direction == ToxFile::SENDING)
{
if (File.file->size() <= MAX_PREVIEW_SIZE)
2014-09-06 03:27:26 +08:00
{
QImage preview;
File.file->seek(0);
if (preview.loadFromData(File.file->readAll()))
{
2014-10-12 23:02:55 +08:00
pic = preview.scaled(100, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
2014-09-06 03:27:26 +08:00
}
File.file->seek(0);
}
}
QString FileTransferInstance::getHumanReadableSize(unsigned long long size)
{
static const char* suffix[] = {"B","kiB","MiB","GiB","TiB"};
int exp = 0;
if (size)
exp = std::min( (int) (log(size) / log(1024)), (int) (sizeof(suffix) / sizeof(suffix[0]) - 1));
return QString().setNum(size / pow(1024, exp),'f',2).append(suffix[exp]);
}
void FileTransferInstance::onFileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection Direction)
{
if (FileNum != fileNum || FriendId != friendId || Direction != direction)
return;
// state = tsProcessing;
2014-10-01 01:35:27 +08:00
QDateTime now = QDateTime::currentDateTime();
long recenttimediff = lastUpdateTime.msecsTo(now);
if (recenttimediff < 1000) //update every 1s
2014-10-01 01:35:27 +08:00
return;
long timediff = effStartTime.msecsTo(now);
2014-09-06 03:27:26 +08:00
if (timediff <= 0)
return;
2014-10-01 01:35:27 +08:00
long avgspeed = (BytesSent - previousBytesSent) / timediff * 1000;
long recentspeed = (BytesSent - lastBytesSent) / recenttimediff * 1000;
2014-10-01 01:35:27 +08:00
speed = getHumanReadableSize(recentspeed)+"/s";
2014-09-06 03:27:26 +08:00
size = getHumanReadableSize(Filesize);
totalBytes = Filesize;
if (!avgspeed)
2014-09-06 03:27:26 +08:00
return;
int etaSecs = (Filesize - BytesSent) / avgspeed;
2014-09-06 03:27:26 +08:00
QTime etaTime(0,0);
etaTime = etaTime.addSecs(etaSecs);
eta = etaTime.toString("mm:ss");
2014-10-01 01:35:27 +08:00
lastBytesSent = BytesSent;
lastUpdateTime = now;
2014-09-06 03:27:26 +08:00
emit stateUpdated();
}
void FileTransferInstance::onFileTransferCancelled(int FriendId, int FileNum, ToxFile::FileDirection Direction)
{
if (FileNum != fileNum || FriendId != friendId || Direction != direction)
return;
2014-09-11 21:44:34 +08:00
disconnect(Core::getInstance(),0,this,0);
2014-09-06 03:27:26 +08:00
state = tsCanceled;
emit stateUpdated();
}
void FileTransferInstance::onFileTransferFinished(ToxFile File)
{
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction)
return;
2014-09-11 21:44:34 +08:00
disconnect(Core::getInstance(),0,this,0);
2014-09-06 03:27:26 +08:00
if (File.direction == ToxFile::RECEIVING)
{
QImage preview;
2014-09-06 03:27:26 +08:00
QFile previewFile(File.filePath);
if (previewFile.open(QIODevice::ReadOnly) && previewFile.size() <= MAX_PREVIEW_SIZE) // Don't preview big (>25MiB) images
2014-09-06 03:27:26 +08:00
{
if (preview.loadFromData(previewFile.readAll()))
{
2014-10-12 23:02:55 +08:00
pic = preview.scaled(100, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation);
2014-09-06 03:27:26 +08:00
}
previewFile.close();
}
}
state = tsFinished;
emit stateUpdated();
}
void FileTransferInstance::onFileTransferAccepted(ToxFile File)
{
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction)
return;
remotePaused = false;
state = tsProcessing;
2014-10-18 10:15:54 +08:00
effStartTime = QDateTime::currentDateTime();
emit stateUpdated();
}
void FileTransferInstance::onFileTransferRemotePausedUnpaused(ToxFile File, bool paused)
{
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction)
return;
remotePaused = paused;
emit stateUpdated();
}
void FileTransferInstance::onFileTransferPaused(int FriendId, int FileNum, ToxFile::FileDirection Direction)
{
if (FileNum != fileNum || FriendId != friendId || Direction != direction)
return;
state = tsPaused;
emit stateUpdated();
}
2014-09-06 03:27:26 +08:00
void FileTransferInstance::cancelTransfer()
{
2014-09-11 21:44:34 +08:00
Core::getInstance()->cancelFileSend(friendId, fileNum);
2014-09-06 03:27:26 +08:00
state = tsCanceled;
emit stateUpdated();
}
void FileTransferInstance::rejectRecvRequest()
{
2014-09-11 21:44:34 +08:00
Core::getInstance()->rejectFileRecvRequest(friendId, fileNum);
2014-09-06 03:27:26 +08:00
onFileTransferCancelled(friendId, fileNum, direction);
state = tsCanceled;
emit stateUpdated();
}
// for whatever the fuck reason, QFileInfo::isWritable() always fails for files that don't exist
// which makes it useless for our case
// since QDir doesn't have an isWritable(), the only option I can think of is to make/delete the file
// surely this is a common problem that has a qt-implemented solution?
bool isFileWritable(QString& path)
{
QFile file(path);
bool exists = file.exists();
bool out = file.open(QIODevice::WriteOnly);
file.close();
if (!exists)
file.remove();
return out;
}
void FileTransferInstance::acceptRecvRequest()
{
2014-10-18 12:53:29 +08:00
QString path = Settings::getInstance().getAutoAcceptDir(Core::getInstance()->getFriendAddress(friendId));
2014-11-06 05:59:29 +08:00
if (path.isEmpty())
path = Settings::getInstance().getGlobalAutoAcceptDir();
2014-10-18 12:53:29 +08:00
if (!path.isEmpty())
2014-09-06 03:27:26 +08:00
{
2014-10-18 11:30:04 +08:00
QDir dir(path);
path = dir.filePath(filename);
QFileInfo info(path);
if (info.exists()) // emulate chrome
2014-09-06 03:27:26 +08:00
{
2014-10-18 11:30:04 +08:00
QString name = info.baseName(), ext = info.completeSuffix();
int i = 0;
do
{
path = dir.filePath(name + QString(" (%1)").arg(++i) + "." + ext);
}
while (QFileInfo(path).exists());
}
qDebug() << "File: auto saving to" << path;
}
else
{
while (true)
{
path = QFileDialog::getSaveFileName(0, tr("Save a file","Title of the file saving dialog"), QDir::home().filePath(filename));
if (path.isEmpty())
return;
2014-09-06 03:27:26 +08:00
else
2014-10-18 11:30:04 +08:00
{
if (isFileWritable(path))
break;
else
QMessageBox::warning(0,
tr("Location not writable","Title of permissions popup"),
tr("You do not have permission to write that location. Choose another, or cancel the save dialog.", "text of permissions popup"));
2014-10-18 11:30:04 +08:00
}
2014-09-06 03:27:26 +08:00
}
}
savePath = path;
2014-09-11 21:44:34 +08:00
Core::getInstance()->acceptFileRecvRequest(friendId, fileNum, path);
2014-09-06 03:27:26 +08:00
state = tsProcessing;
2014-10-18 10:15:54 +08:00
effStartTime = QDateTime::currentDateTime();
2014-09-06 03:27:26 +08:00
emit stateUpdated();
}
void FileTransferInstance::pauseResumeRecv()
{
if (!(state == tsProcessing || state == tsPaused))
return;
if (remotePaused)
return;
2014-09-11 21:44:34 +08:00
Core::getInstance()->pauseResumeFileRecv(friendId, fileNum);
2014-10-18 10:15:54 +08:00
if (state == tsPaused)
{
effStartTime = QDateTime::currentDateTime();
previousBytesSent = lastBytesSent;
}
2014-09-06 03:27:26 +08:00
emit stateUpdated();
}
void FileTransferInstance::pauseResumeSend()
{
if (!(state == tsProcessing || state == tsPaused))
return;
if (remotePaused)
return;
2014-09-11 21:44:34 +08:00
Core::getInstance()->pauseResumeFileSend(friendId, fileNum);
2014-10-18 10:15:54 +08:00
if (state == tsPaused)
{
effStartTime = QDateTime::currentDateTime();
previousBytesSent = lastBytesSent;
}
2014-09-06 03:27:26 +08:00
emit stateUpdated();
}
QString FileTransferInstance::QImage2base64(const QImage &img)
{
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
img.save(&buffer, "PNG"); // writes image into ba in PNG format
return ba.toBase64();
}
2014-09-06 03:27:26 +08:00
QString FileTransferInstance::getHtmlImage()
{
2014-09-07 00:43:29 +08:00
2014-09-06 03:27:26 +08:00
QString res;
if (state == tsPending || state == tsProcessing || state == tsPaused)
{
QImage leftBtnImg(":/ui/fileTransferInstance/stopFileButton.png");
QImage rightBtnImg;
2014-09-06 21:13:06 +08:00
if (state == tsProcessing)
rightBtnImg = QImage(":/ui/fileTransferInstance/pauseFileButton.png");
else if (state == tsPaused)
rightBtnImg = QImage(":/ui/fileTransferInstance/resumeFileButton.png");
2014-09-06 21:13:06 +08:00
else
{
if (direction == ToxFile::SENDING)
rightBtnImg = QImage(":/ui/fileTransferInstance/pauseGreyFileButton.png");
else
rightBtnImg = QImage(":/ui/fileTransferInstance/acceptFileButton.png");
}
if (remotePaused)
rightBtnImg = QImage(":/ui/fileTransferInstance/pauseGreyFileButton.png");
2014-09-06 03:27:26 +08:00
2014-09-07 19:42:06 +08:00
res = draw2ButtonsForm("silver", leftBtnImg, rightBtnImg);
} else if (state == tsBroken)
{
QImage leftBtnImg(":/ui/fileTransferInstance/stopFileButton.png");
QImage rightBtnImg(":/ui/fileTransferInstance/pauseGreyFileButton.png");
res = draw2ButtonsForm("red", leftBtnImg, rightBtnImg);
2014-09-06 03:27:26 +08:00
} else if (state == tsCanceled)
{
res = drawButtonlessForm("red");
2014-09-06 03:27:26 +08:00
} else if (state == tsFinished)
{
res = drawButtonlessForm("green");
2014-09-06 03:27:26 +08:00
}
return res;
}
void FileTransferInstance::pressFromHtml(QString code)
{
if (state == tsFinished || state == tsCanceled)
return;
if (direction == ToxFile::SENDING)
{
if (code == "btnA")
2014-09-06 03:27:26 +08:00
cancelTransfer();
else if (code == "btnB")
2014-09-06 03:27:26 +08:00
pauseResumeSend();
} else {
if (code == "btnA")
2014-09-06 03:27:26 +08:00
rejectRecvRequest();
else if (code == "btnB")
2014-09-06 03:27:26 +08:00
{
if (state == tsPending)
acceptRecvRequest();
else
pauseResumeRecv();
}
}
}
QString FileTransferInstance::drawButtonlessForm(const QString &type)
{
QString imgAStr;
QString imgBStr;
if (type == "red")
{
imgAStr = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLRedFileButton.png")) + "\">";
imgBStr = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyRRedFileButton.png")) + "\">";
} else {
imgAStr = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLGreenFileButton.png")) + "\">";
imgBStr = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyRGreenFileButton.png")) + "\">";
}
2014-09-28 23:57:17 +08:00
QString content = "<p>" + filenameElided + "</p><p>" + size + "</p>";
return wrapIntoForm(content, type, imgAStr, imgBStr);
}
QString FileTransferInstance::insertMiniature(const QString &type)
{
if (pic == QImage())
return QString();
QString widgetId = QString::number(getId());
QString res;
res = "<td><div class=" + type + ">\n";
res += "<img src=\"data:mini." + widgetId + "/png;base64," + QImage2base64(pic) + "\">";
res += "</div></td>\n";
2014-10-23 23:39:55 +08:00
res += "<td width=5><div class=" + type + ">\n";
res += "</div></td>\n";
return res;
}
QString FileTransferInstance::draw2ButtonsForm(const QString &type, const QImage &imgA, const QImage &imgB)
{
QString widgetId = QString::number(getId());
QString imgAstr = "<img src=\"data:ftrans." + widgetId + ".btnA/png;base64," + QImage2base64(imgA) + "\">";
QString imgBstr = "<img src=\"data:ftrans." + widgetId + ".btnB/png;base64," + QImage2base64(imgB) + "\">";
QString content;
2014-10-12 23:02:55 +08:00
QString progrBar = "<img src=\"data:progressbar." + widgetId + "/png;base64," +
QImage2base64(drawProgressBarImg(double(lastBytesSent)/totalBytes, MAX_CONTENT_WIDTH, 9)) + "\">";
2014-09-28 23:57:17 +08:00
content = "<p>" + filenameElided + "</p>";
content += "<table cellspacing=\"0\"><tr>";
content += "<td>" + size + "</td>";
content += "<td align=center>" + speed + "</td>";
2014-10-30 00:25:47 +08:00
content += "<td align=right>" + tr("ETA") + ": " + eta + "</td>";
content += "</tr><tr><td colspan=3>";
content += progrBar;
content += "</td></tr></table>";
return wrapIntoForm(content, type, imgAstr, imgBstr);
}
QString FileTransferInstance::wrapIntoForm(const QString& content, const QString &type, const QString &imgAstr, const QString &imgBstr)
{
QString w = QString::number(QImage(":/ui/fileTransferInstance/emptyLRedFileButton.png").size().width());
QString imgLeftA, imgLeftB;
if (type == "green")
{
imgLeftA = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLGreenFileButton.png").mirrored(true,false)) + "\">";
imgLeftB = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLGreenFileButton.png").mirrored(true,true)) + "\">";
}
if (type == "silver")
{
imgLeftA = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/sliverRTEdge.png").mirrored(true,false)) + "\">";
imgLeftB = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/sliverRTEdge.png").mirrored(true,true)) + "\">";
}
if (type == "red")
{
imgLeftA = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLRedFileButton.png").mirrored(true,false)) + "\">";
imgLeftB = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLRedFileButton.png").mirrored(true,true)) + "\">";
}
QString res;
res = "<table cellspacing=\"0\">\n";
res += "<tr valign=middle>\n";
res += "<td width=" + w + ">\n";
res += "<div class=button>" + imgLeftA + "<br>" + imgLeftB + "</div>\n";
res += "</td>\n";
res += insertMiniature(type);
res += "<td width=" + QString::number(contentPrefWidth) + ">\n";
res += "<div class=" + type + ">";
res += content;
res += "</div>\n";
res += "</td>\n";
res += "<td width=3>\n";
res += "<div class=" + type + "></div>\n";
res += "</td>\n";
res += "<td>\n";
res += "<div class=button>" + imgAstr + "<br>" + imgBstr + "</div>\n";
res += "</td>\n";
res += "</tr>\n";
res += "</table>\n";
return res;
}
QImage FileTransferInstance::drawProgressBarImg(const double &part, int w, int h)
{
QImage progressBar(w, h, QImage::Format_Mono);
QPainter qPainter(&progressBar);
qPainter.setBrush(Qt::NoBrush);
qPainter.setPen(Qt::black);
qPainter.drawRect(0, 0, w - 1, h - 1);
qPainter.setBrush(Qt::SolidPattern);
qPainter.setPen(Qt::black);
qPainter.drawRect(1, 0, (w - 2) * (part), h - 1);
return progressBar;
}
void FileTransferInstance::onFileTransferBrokenUnbroken(ToxFile File, bool broken)
{
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction)
return;
if (broken)
state = tsBroken;
else
state = tsProcessing;
emit stateUpdated();
}