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