1 //////////////////////////////////////////////////////////////////////
2 //
3 // BeeBEEP Copyright (C) 2010-2021 Marco Mastroddi
4 //
5 // BeeBEEP is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published
7 // by the Free Software Foundation, either version 3 of the License,
8 // or (at your option) any later version.
9 //
10 // BeeBEEP is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with BeeBEEP. If not, see <http://www.gnu.org/licenses/>.
17 //
18 // Author: Marco Mastroddi <marco.mastroddi(AT)gmail.com>
19 //
20 // $Id: FileTransferPeer.cpp 1455 2020-12-23 10:17:53Z mastroddi $
21 //
22 //////////////////////////////////////////////////////////////////////
23 
24 #include "BeeUtils.h"
25 #include "FileTransferPeer.h"
26 #include "Protocol.h"
27 #include "Settings.h"
28 #include "UserManager.h"
29 
30 
FileTransferPeer(QObject * parent)31 FileTransferPeer::FileTransferPeer( QObject *parent )
32   : QObject( parent ), m_transferType( FileInfo::Upload ), m_id( ID_INVALID ),
33     m_fileInfo( ID_INVALID, FileInfo::Upload ), m_file(), m_state( FileTransferPeer::Unknown ),
34     m_bytesTransferred( 0 ), m_totalBytesTransferred( 0 ), mp_socket( Q_NULLPTR ),
35     m_socketDescriptor( 0 ), m_remoteUserId( ID_INVALID ), m_serverPort( 0 ), m_startTimestamp(),
36     m_elapsedTime( 0 ), m_isSkipped( false )
37 {
38   setObjectName( "FileTransferPeer" );
39 #ifdef BEEBEEP_DEBUG
40   qDebug() << "Peer created for transfer file";
41 #endif
42   mp_socket = new ConnectionSocket( this );
43   connect( mp_socket, SIGNAL( error( QAbstractSocket::SocketError ) ), this, SLOT( socketError( QAbstractSocket::SocketError ) ) );
44   connect( mp_socket, SIGNAL( authenticationRequested( const QByteArray& ) ), this, SLOT( checkUserAuthentication( const QByteArray& ) ) );
45   connect( mp_socket, SIGNAL( dataReceived( const QByteArray& ) ), this, SLOT( checkTransferData( const QByteArray& ) ) );
46   connect( mp_socket, SIGNAL( abortRequest() ), this, SLOT( cancelTransfer() ) );
47 }
48 
closeAll()49 void FileTransferPeer::closeAll()
50 {
51 #ifdef BEEBEEP_DEBUG
52   qDebug() << qPrintable( name() ) << "cleans up";
53 #endif
54   if( mp_socket->isOpen() )
55   {
56 #ifdef BEEBEEP_DEBUG
57     qDebug() << qPrintable( name() ) << "close socket with descriptor" << mp_socket->socketDescriptor();
58 #endif
59     mp_socket->closeConnection();
60   }
61 
62   if( m_file.isOpen() )
63   {
64 #ifdef BEEBEEP_DEBUG
65     qDebug() << qPrintable( name() ) << "close file" << qPrintable( Bee::convertToNativeFolderSeparator( m_file.fileName() ) );
66 #endif
67     m_file.flush();
68     m_file.close();
69   }
70 
71   if( !isTransferCompleted() && isDownload() && m_file.exists() && m_state != FileTransferPeer::Paused && !Settings::instance().resumeFileTransfer() )
72     m_file.remove();
73 
74   computeElapsedTime();
75 }
76 
setFileInfo(FileInfo::TransferType ftt,const FileInfo & fi)77 void FileTransferPeer::setFileInfo( FileInfo::TransferType ftt, const FileInfo& fi )
78 {
79   m_fileInfo = fi;
80   m_fileInfo.setTransferType( ftt );
81   if( isDownload() )
82     m_file.setFileName( temporaryFilePath() );
83   else
84     m_file.setFileName( m_fileInfo.path() );
85 #ifdef BEEBEEP_DEBUG
86   qDebug() << qPrintable( name() ) << "init the file" << qPrintable( Bee::convertToNativeFolderSeparator( m_fileInfo.path() ) );
87 #endif
88 }
89 
setInQueue()90 void FileTransferPeer::setInQueue()
91 {
92   if( m_state == FileTransferPeer::Queue )
93     return;
94   m_state = FileTransferPeer::Queue;
95   qDebug() << qPrintable( name() ) << "has queued the transfer of file" << qPrintable( m_fileInfo.name() ) << "with user id" << remoteUserId();
96   emit message( id(), remoteUserId(), m_fileInfo, tr( "Transfer queued" ), m_state );
97 }
98 
startConnection()99 void FileTransferPeer::startConnection()
100 {
101   if( m_state >= FileTransferPeer::Request )
102   {
103     qDebug() << qPrintable( name() ) << "is already started and it is in state" << m_state;
104     return;
105   }
106 
107   m_state = FileTransferPeer::Request;
108   m_bytesTransferred = 0;
109   m_totalBytesTransferred = 0;
110   m_elapsedTime = 0;
111   m_startTimestamp = QDateTime();
112   m_isSkipped = false;
113 
114   if( m_socketDescriptor > 0 )
115   {
116 #ifdef BEEBEEP_DEBUG
117     qDebug() << qPrintable( name() ) << "set socket descriptor" << m_socketDescriptor;
118 #endif
119     mp_socket->initSocket( m_socketDescriptor, m_serverPort );
120   }
121   else
122   {
123 #ifdef BEEBEEP_DEBUG
124     qDebug() << qPrintable( name() ) << "is connecting to" << qPrintable( m_fileInfo.networkAddress().toString() );
125 #endif
126     mp_socket->connectToNetworkAddress( m_fileInfo.networkAddress() );
127   }
128 
129   QTimer::singleShot( Settings::instance().fileTransferConfirmTimeout(), this, SLOT( connectionTimeout() ) );
130 }
131 
skipTransfer()132 void FileTransferPeer::skipTransfer()
133 {
134   m_isSkipped = true;
135   setTransferCompleted();
136 }
137 
setTransferCompleted()138 void FileTransferPeer::setTransferCompleted()
139 {
140   if( m_state == FileTransferPeer::Completed )
141     return;
142   qDebug() << qPrintable( name() ) << "has completed the transfer of file" << qPrintable( m_fileInfo.name() ) << "with user id" << remoteUserId();
143   m_state = FileTransferPeer::Completed;
144   closeAll();
145   if( isDownload() && !isSkipped() )
146   {
147     if( m_fileInfo.path() != m_file.fileName() )
148     {
149       QFileInfo file_info_completed_file( m_fileInfo.path() );
150       if( file_info_completed_file.exists() && Settings::instance().removeExistingFileOnDownloadCompleted() )
151       {
152         if( QFile::remove( m_fileInfo.path() ) )
153           qDebug() << qPrintable( name() ) << "removes existing file" << qPrintable( m_fileInfo.path() );
154         else
155           qWarning() << qPrintable( name() ) << "cannot remove existing file" << qPrintable( m_fileInfo.path() );
156       }
157 
158       if( file_info_completed_file.exists() )
159       {
160         QString new_file_path = Bee::uniqueFilePath( m_fileInfo.path(), true );
161         m_fileInfo.setPath( new_file_path );
162       }
163       else
164       {
165         // Check if parent folder exists
166         QString parent_folder_path = Bee::convertToNativeFolderSeparator( file_info_completed_file.absolutePath() );
167         QDir parent_folder( parent_folder_path );
168         if( !parent_folder.exists() )
169         {
170           if( !parent_folder.mkpath( "." ) )
171             qWarning() << "Unable to create parent folder" << qPrintable( parent_folder_path );
172         }
173       }
174 
175       if( m_file.rename( m_fileInfo.path() ) )
176       {
177 #ifdef BEEBEEP_DEBUG
178          qDebug() << qPrintable( name() ) << "renames downloaded file to" << qPrintable( m_fileInfo.path() );
179 #endif
180         if( (m_fileInfo.isInShareBox() || Settings::instance().keepModificationDateOnFileTransferred()) && m_fileInfo.lastModified().isValid() )
181           Bee::setLastModifiedToFile( m_fileInfo.path(), m_fileInfo.lastModified() );
182       }
183       else
184         qWarning() << qPrintable( name() ) << "cannot rename downloaded file" << qPrintable( m_file.fileName() ) << "to" << qPrintable( m_fileInfo.path() );
185     }
186     else
187     {
188       if( (m_fileInfo.isInShareBox() || Settings::instance().keepModificationDateOnFileTransferred()) && m_fileInfo.lastModified().isValid() && QFile::exists( m_fileInfo.path() ) )
189         Bee::setLastModifiedToFile( m_fileInfo.path(), m_fileInfo.lastModified() );
190     }
191   }
192 
193   if( isSkipped() )
194     emit message( id(), remoteUserId(), m_fileInfo, QString( "%1 (%2)" ).arg( tr( "Transfer skipped" ), tr( "the file already exists" ) ), m_state );
195   else
196     emit message( id(), remoteUserId(), m_fileInfo, tr( "Transfer completed in %1" ).arg( Bee::timeToString( m_elapsedTime ) ), m_state );
197   emit operationCompleted();
198 }
199 
cancelTransfer()200 void FileTransferPeer::cancelTransfer()
201 {
202   if( m_state == FileTransferPeer::Canceled )
203     return;
204   if( m_state != FileTransferPeer::Completed && m_state != FileTransferPeer::Error )
205     m_state = FileTransferPeer::Canceled;
206   qDebug() << qPrintable( name() ) << "cancels the file transfer";
207   if( mp_socket->isOpen() )
208     mp_socket->abortConnection();
209   closeAll();
210   if( m_fileInfo.isValid() && remoteUserId() != ID_INVALID )
211     emit message( id(), remoteUserId(), m_fileInfo, tr( "Transfer canceled" ), m_state );
212   emit operationCompleted();
213 }
214 
canPauseTransfer() const215 bool FileTransferPeer::canPauseTransfer() const
216 {
217   if( m_state == FileTransferPeer::Transferring )
218   {
219     if( Settings::instance().resumeFileTransfer() && mp_socket->protocolVersion() >= FILE_TRANSFER_RESUME_PROTO_VERSION )
220       return true;
221   }
222   return false;
223 }
224 
pauseTransfer(bool close_connection)225 void FileTransferPeer::pauseTransfer( bool close_connection )
226 {
227   if( m_state == FileTransferPeer::Paused )
228     return;
229   if( m_state == FileTransferPeer::Pausing && !close_connection )
230     return;
231   if( m_state != FileTransferPeer::Completed && m_state != FileTransferPeer::Error && m_state != FileTransferPeer::Canceled )
232   {
233     if( !close_connection )
234     {
235       qDebug() << qPrintable( name() ) << "is pausing the file transfer";
236       m_state = FileTransferPeer::Pausing;
237       if( m_fileInfo.isValid() && remoteUserId() != ID_INVALID )
238       {
239         QString pause_msg = tr( "Transfer is about to pause" );
240         if( !m_fileInfo.isDownload() )
241           pause_msg += QString( " (%1)" ).arg( tr( "Please wait" ) );
242         emit message( id(), remoteUserId(), m_fileInfo, pause_msg, m_state );
243       }
244     }
245     else
246       setTransferPaused();
247   }
248 }
249 
setTransferPaused()250 void FileTransferPeer::setTransferPaused()
251 {
252   if( m_state == FileTransferPeer::Paused )
253     return;
254   qDebug() << qPrintable( name() ) << "has paused the transfer of file" << qPrintable( m_fileInfo.name() ) << "with user id" << remoteUserId();
255   m_state = FileTransferPeer::Paused;
256   closeAll();
257   if( m_fileInfo.isValid() && remoteUserId() != ID_INVALID )
258     emit message( id(), remoteUserId(), m_fileInfo, tr( "Transfer paused after %1" ).arg( Bee::timeToString( m_elapsedTime ) ), m_state );
259   emit operationCompleted();
260 }
261 
setTransferringState()262 void FileTransferPeer::setTransferringState()
263 {
264   if( m_state == FileTransferPeer::Transferring )
265     return;
266   m_startTimestamp = QDateTime::currentDateTime();
267   m_state = FileTransferPeer::Transferring;
268   if( m_fileInfo.isValid() && remoteUserId() != ID_INVALID )
269     emit message( id(), remoteUserId(), m_fileInfo, tr( "Starting transfer" ), m_state );
270 }
271 
socketError(QAbstractSocket::SocketError)272 void FileTransferPeer::socketError( QAbstractSocket::SocketError )
273 {
274   // Make a check to remove the error after a transfer completed
275   if( m_state <= FileTransferPeer::Transferring )
276     setError( mp_socket->errorString() );
277 }
278 
setError(const QString & str_err)279 void FileTransferPeer::setError( const QString& str_err )
280 {
281   m_state = FileTransferPeer::Error;
282   qWarning() << qPrintable( name() ) << "found an error when transfer file" << qPrintable( Bee::convertToNativeFolderSeparator( m_fileInfo.name() ) ) << ":" << str_err;
283   closeAll();
284   if( remoteUserId() != ID_INVALID && m_fileInfo.isValid() )
285     emit message( id(), remoteUserId(), m_fileInfo, str_err, m_state );
286   operationCompleted();
287 }
288 
showProgress()289 void FileTransferPeer::showProgress()
290 {
291   if( m_totalBytesTransferred > 0 )
292   {
293     computeElapsedTime();
294     emit progress( id(), remoteUserId(), m_fileInfo, m_totalBytesTransferred, m_elapsedTime );
295   }
296 }
297 
checkTransferData(const QByteArray & byte_array)298 void FileTransferPeer::checkTransferData( const QByteArray& byte_array )
299 {
300   if( isDownload() )
301     checkDownloadData( byte_array );
302   else
303     checkUploadData( byte_array );
304 }
305 
sendTransferData()306 void FileTransferPeer::sendTransferData()
307 {
308   if( isDownload() )
309     sendDownloadData();
310   else
311     sendUploadData();
312 }
313 
checkUserAuthentication(const QByteArray & auth_byte_array)314 void FileTransferPeer::checkUserAuthentication( const QByteArray& auth_byte_array )
315 {
316 #ifdef BEEBEEP_DEBUG
317   qDebug() << qPrintable( name() ) << "checks authentication message";
318 #endif
319   Message m = Protocol::instance().toMessage( auth_byte_array, mp_socket->protocolVersion() );
320   if( !m.isValid() )
321   {
322     qWarning() << qPrintable( name() ) << "has found an invalid auth message";
323     cancelTransfer();
324     return;
325   }
326 
327   User user_to_check = Protocol::instance().createUser( m, mp_socket->peerAddress() );
328   User user_connected;
329   if( user_to_check.isValid() )
330     user_connected = Protocol::instance().recognizeUser( user_to_check, Settings::instance().userRecognitionMethod() );
331 
332   if( !user_connected.isValid() )
333   {
334     qWarning() << qPrintable( user_to_check.path() ) << "is not authorized for file transfer" << id();
335     cancelTransfer();
336     return;
337   }
338 
339   setUserAuthorized( user_connected.id() );
340 }
341 
setUserAuthorized(VNumber user_id)342 void FileTransferPeer::setUserAuthorized( VNumber user_id )
343 {
344   mp_socket->setUserId( user_id );
345   if( m_remoteUserId == ID_INVALID )
346     m_remoteUserId = user_id;
347   if( user_id != m_remoteUserId )
348     qWarning() << qPrintable( name() ) << "was for user id" << m_remoteUserId << "but it is authorized also user id" << user_id;
349   if( isDownload() )
350     sendDownloadRequest();
351 }
352 
connectionTimeout()353 void FileTransferPeer::connectionTimeout()
354 {
355   if( m_state <= FileTransferPeer::Request )
356     setError( tr( "Connection timeout" ) );
357 }
358 
onTickEvent(int)359 void FileTransferPeer::onTickEvent( int )
360 {
361   if( m_state == FileTransferPeer::Transferring )
362   {
363     if( mp_socket->activityIdle() > Settings::instance().pongTimeout() )
364     {
365       if( mp_socket->protocolVersion() >= FILE_TRANSFER_RESUME_PROTO_VERSION && Settings::instance().resumeFileTransfer() )
366       {
367         pauseTransfer( false );
368         if( isDownload() )
369         {
370           m_bytesTransferred = 0;
371           sendDownloadDataConfirmation();
372         }
373       }
374       else
375         setError( tr( "Transfer timeout" ) );
376     }
377   }
378 }
379 
computeElapsedTime()380 void FileTransferPeer::computeElapsedTime()
381 {
382   if( m_startTimestamp.isValid() )
383   {
384     qint64 elapsed_time_ms = qAbs( QDateTime::currentDateTime().msecsTo( m_startTimestamp ) );
385     if( elapsed_time_ms >= 86399999 ) // no more than 1 day - 1 ms
386       m_elapsedTime = 86399999;
387     else
388       m_elapsedTime = static_cast<int>( elapsed_time_ms );
389   }
390   else
391     m_elapsedTime = 0;
392 }
393 
stateIsStopped(FileTransferPeer::TransferState ft_state)394 bool FileTransferPeer::stateIsStopped( FileTransferPeer::TransferState ft_state )
395 {
396   return ft_state == Completed || ft_state == Error || ft_state == Canceled || ft_state == Paused;
397 }
398