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-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>
2014-09-26 01:30:23 +08:00
# include <QPainter>
2014-09-06 03:27:26 +08:00
2014-09-28 23:57:17 +08:00
# define CONTENT_WIDTH 250
2014-09-29 20:43:14 +08:00
# 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 )
2014-09-29 12:10:04 +08:00
: lastBytesSent { 0 } ,
2014-09-06 03:27:26 +08:00
fileNum { File . fileNum } , friendId { File . friendId } , direction { File . direction }
{
id = Idconter + + ;
state = tsPending ;
2014-09-11 21:36:57 +08:00
remotePaused = false ;
2014-10-01 01:35:27 +08:00
lastUpdateTime = QDateTime : : currentDateTime ( ) ;
2014-09-06 03:27:26 +08:00
filename = File . fileName ;
2014-09-28 23:57:17 +08:00
QFont font ;
font . setPixelSize ( 10 ) ;
QFontMetrics fm ( font ) ;
filenameElided = fm . elidedText ( filename , Qt : : ElideRight , CONTENT_WIDTH ) ;
2014-09-06 03:27:26 +08:00
size = getHumanReadableSize ( File . filesize ) ;
speed = " 0B/s " ;
eta = " 00:00 " ;
2014-09-29 20:43:14 +08:00
2014-09-06 03:27:26 +08:00
if ( File . direction = = ToxFile : : SENDING )
{
2014-09-29 20:43:14 +08:00
if ( File . file - > size ( ) < = MAX_PREVIEW_SIZE )
2014-09-06 03:27:26 +08:00
{
2014-09-29 20:43:14 +08:00
QImage preview ;
File . file - > seek ( 0 ) ;
if ( preview . loadFromData ( File . file - > readAll ( ) ) )
{
pic = preview . scaledToHeight ( 50 ) ;
}
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 ;
2014-09-11 21:36:57 +08:00
// state = tsProcessing;
2014-10-01 01:35:27 +08:00
QDateTime now = QDateTime : : currentDateTime ( ) ;
if ( lastUpdateTime . secsTo ( now ) < 1 ) //update every 1s
return ;
int timediff = startTime . secsTo ( now ) ;
2014-09-06 03:27:26 +08:00
if ( timediff < = 0 )
return ;
2014-10-01 01:35:27 +08:00
long rawspeed = BytesSent / timediff ;
2014-09-06 03:27:26 +08:00
speed = getHumanReadableSize ( rawspeed ) + " /s " ;
size = getHumanReadableSize ( Filesize ) ;
2014-09-26 01:30:23 +08:00
totalBytes = Filesize ;
2014-09-06 03:27:26 +08:00
if ( ! rawspeed )
return ;
int etaSecs = ( Filesize - BytesSent ) / rawspeed ;
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 )
{
2014-09-07 16:51:54 +08:00
QImage preview ;
2014-09-06 03:27:26 +08:00
QFile previewFile ( File . filePath ) ;
2014-09-29 20:43:14 +08:00
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-09-08 00:14:06 +08:00
pic = preview . scaledToHeight ( 50 ) ;
2014-09-06 03:27:26 +08:00
}
previewFile . close ( ) ;
}
}
state = tsFinished ;
emit stateUpdated ( ) ;
}
2014-09-11 21:36:57 +08:00
void FileTransferInstance : : onFileTransferAccepted ( ToxFile File )
{
if ( File . fileNum ! = fileNum | | File . friendId ! = friendId | | File . direction ! = direction )
return ;
remotePaused = false ;
state = tsProcessing ;
2014-10-01 01:35:27 +08:00
startTime = QDateTime : : currentDateTime ( ) ;
2014-09-11 21:36:57 +08:00
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 ( )
{
QString path ;
while ( true )
{
2014-09-29 12:10:04 +08:00
path = QFileDialog : : getSaveFileName ( 0 , tr ( " Save a file " , " Title of the file saving dialog " ) , QDir : : home ( ) . filePath ( filename ) ) ;
2014-09-06 03:27:26 +08:00
if ( path . isEmpty ( ) )
return ;
else
{
//bool savable = QFileInfo(path).isWritable();
//qDebug() << path << " is writable: " << savable;
//qDebug() << "/home/bill/bliss.pdf writable: " << QFileInfo("/home/bill/bliss.pdf").isWritable();
if ( isFileWritable ( path ) )
break ;
else
2014-09-11 21:44:34 +08:00
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-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-01 01:35:27 +08:00
startTime = QDateTime : : currentDateTime ( ) ;
2014-09-29 12:10:04 +08:00
2014-09-06 03:27:26 +08:00
emit stateUpdated ( ) ;
}
void FileTransferInstance : : pauseResumeRecv ( )
{
2014-09-11 21:36:57 +08:00
if ( ! ( state = = tsProcessing | | state = = tsPaused ) )
return ;
if ( remotePaused )
return ;
2014-09-11 21:44:34 +08:00
Core : : getInstance ( ) - > pauseResumeFileRecv ( friendId , fileNum ) ;
2014-09-11 21:36:57 +08:00
// if (state == tsProcessing)
// state = tsPaused;
// else state = tsProcessing;
2014-09-06 03:27:26 +08:00
emit stateUpdated ( ) ;
}
void FileTransferInstance : : pauseResumeSend ( )
{
2014-09-11 21:36:57 +08:00
if ( ! ( state = = tsProcessing | | state = = tsPaused ) )
return ;
if ( remotePaused )
return ;
2014-09-11 21:44:34 +08:00
Core : : getInstance ( ) - > pauseResumeFileSend ( friendId , fileNum ) ;
2014-09-11 21:36:57 +08:00
// if (state == tsProcessing)
// state = tsPaused;
// else state = tsProcessing;
2014-09-06 03:27:26 +08:00
emit stateUpdated ( ) ;
}
2014-09-07 13:52:01 +08:00
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 17:14:50 +08:00
qDebug ( ) < < " QString FileTransferInstance::getHtmlImage() " < < state ;
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 )
{
2014-09-07 16:04:42 +08:00
QImage leftBtnImg ( " :/ui/fileTransferInstance/stopFileButton.png " ) ;
QImage rightBtnImg ;
2014-09-06 21:13:06 +08:00
if ( state = = tsProcessing )
2014-09-07 16:04:42 +08:00
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
2014-09-11 21:36:57 +08:00
{
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 ) ;
2014-09-30 01:22:19 +08:00
} 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 )
{
2014-09-07 13:52:01 +08:00
res = drawButtonlessForm ( " red " ) ;
2014-09-06 03:27:26 +08:00
} else if ( state = = tsFinished )
{
2014-09-07 13:52:01 +08:00
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 )
{
2014-09-07 13:52:01 +08:00
if ( code = = " btnA " )
2014-09-06 03:27:26 +08:00
cancelTransfer ( ) ;
2014-09-07 13:52:01 +08:00
else if ( code = = " btnB " )
2014-09-06 03:27:26 +08:00
pauseResumeSend ( ) ;
} else {
2014-09-07 13:52:01 +08:00
if ( code = = " btnA " )
2014-09-06 03:27:26 +08:00
rejectRecvRequest ( ) ;
2014-09-07 13:52:01 +08:00
else if ( code = = " btnB " )
2014-09-06 03:27:26 +08:00
{
if ( state = = tsPending )
acceptRecvRequest ( ) ;
else
pauseResumeRecv ( ) ;
}
}
}
2014-09-07 13:52:01 +08:00
QString FileTransferInstance : : drawButtonlessForm ( const QString & type )
{
2014-09-07 16:04:42 +08:00
QString imgAStr ;
QString imgBStr ;
2014-09-07 13:52:01 +08:00
2014-09-07 16:04:42 +08:00
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-07 13:52:01 +08:00
2014-09-28 23:57:17 +08:00
QString content = " <p> " + filenameElided + " </p><p> " + size + " </p> " ;
2014-09-07 16:04:42 +08:00
return wrapIntoForm ( content , type , imgAStr , imgBStr ) ;
2014-09-07 13:52:01 +08:00
}
2014-09-08 00:14:06 +08:00
QString FileTransferInstance : : insertMiniature ( const QString & type )
2014-09-07 13:52:01 +08:00
{
if ( pic = = QImage ( ) )
return QString ( ) ;
QString widgetId = QString : : number ( getId ( ) ) ;
QString res ;
2014-09-08 00:14:06 +08:00
res = " <td><div class= " + type + " > \n " ;
2014-09-07 13:52:01 +08:00
res + = " <img src= \" data:mini. " + widgetId + " /png;base64, " + QImage2base64 ( pic ) + " \" > " ;
2014-09-08 00:14:06 +08:00
res + = " </div></td> \n " ;
2014-09-07 13:52:01 +08:00
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 ) + " \" > " ;
2014-09-07 16:04:42 +08:00
QString content ;
2014-09-28 23:57:17 +08:00
QString progrBar = " <img src= \" data:progressbar. " + widgetId + " /png;base64, " + QImage2base64 ( drawProgressBarImg ( double ( lastBytesSent ) / totalBytes , CONTENT_WIDTH , 9 ) ) + " \" > " ;
2014-09-26 01:30:23 +08:00
2014-09-28 23:57:17 +08:00
content = " <p> " + filenameElided + " </p> " ;
2014-09-26 01:30:23 +08:00
content + = " <table cellspacing= \" 0 \" ><tr> " ;
content + = " <td> " + size + " </td> " ;
content + = " <td align=center> " + speed + " </td> " ;
content + = " <td align=right>ETA: " + eta + " </td> " ;
content + = " </tr><tr><td colspan=3> " ;
content + = progrBar ;
content + = " </td></tr></table> " ;
2014-09-07 16:04:42 +08:00
return wrapIntoForm ( content , type , imgAstr , imgBstr ) ;
}
QString FileTransferInstance : : wrapIntoForm ( const QString & content , const QString & type , const QString & imgAstr , const QString & imgBstr )
{
QString res ;
2014-09-26 01:30:23 +08:00
res = " <table cellspacing= \" 0 \" > \n " ;
2014-09-07 13:52:01 +08:00
res + = " <tr valign=middle> \n " ;
2014-09-08 00:14:06 +08:00
res + = insertMiniature ( type ) ;
2014-09-28 23:57:17 +08:00
res + = " <td width= " + QString : : number ( CONTENT_WIDTH + 30 ) + " > \n " ;
2014-09-07 13:52:01 +08:00
res + = " <div class= " + type + " > " ;
2014-09-07 16:04:42 +08:00
res + = content ;
2014-09-07 13:52:01 +08:00
res + = " </div> \n " ;
res + = " </td> \n " ;
res + = " <td> \n " ;
2014-09-08 00:14:06 +08:00
res + = " <div class=button> " + imgAstr + " <br> " + imgBstr + " </div> \n " ;
2014-09-07 13:52:01 +08:00
res + = " </td> \n " ;
res + = " </tr> \n " ;
res + = " </table> \n " ;
return res ;
}
2014-09-26 01:30:23 +08:00
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 ;
}
2014-09-30 01:22:19 +08:00
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 ( ) ;
}