2013-05-03 21:11:00 +04:00
/*
* Copyright ( C ) by Olivier Goffart < ogoffart @ owncloud . com >
* Copyright ( C ) by Klaas Freitag < freitag @ owncloud . com >
*
* This program is free 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 2 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 GNU General Public License
* for more details .
*/
2014-01-16 16:49:55 +04:00
# include "owncloudpropagator_p.h"
2013-10-16 13:59:54 +04:00
# include "syncjournaldb.h"
2013-10-28 13:47:10 +04:00
# include "syncjournalfilerecord.h"
2013-12-10 16:47:44 +04:00
# include "utility.h"
2013-05-03 21:11:00 +04:00
# include <httpbf.h>
# include <qfile.h>
# include <qdir.h>
# include <qdiriterator.h>
# include <qtemporaryfile.h>
2013-09-05 19:08:05 +04:00
# if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
2013-05-03 21:11:00 +04:00
# include <qabstractfileengine.h>
2013-09-05 19:08:05 +04:00
# else
# include <qsavefile.h>
# endif
2013-09-05 22:15:55 +04:00
# include <QDebug>
2013-05-05 13:14:40 +04:00
# include <QDateTime>
2013-10-28 13:47:10 +04:00
# include <qstack.h>
2014-01-31 20:29:50 +04:00
# include <QCoreApplication>
2013-05-03 21:11:00 +04:00
# include <neon/ne_basic.h>
# include <neon/ne_socket.h>
# include <neon/ne_session.h>
# include <neon/ne_props.h>
# include <neon/ne_auth.h>
# include <neon/ne_dates.h>
# include <neon/ne_compress.h>
# include <neon/ne_redirect.h>
2013-12-03 20:07:21 +04:00
# ifdef Q_OS_WIN
# include <windef.h>
# include <winbase.h>
# endif
2013-12-10 20:19:25 +04:00
# include <time.h>
2013-05-08 16:31:52 +04:00
// We use some internals of csync:
extern " C " int c_utimes ( const char * , const struct timeval * ) ;
extern " C " void csync_win32_set_file_hidden ( const char * file , bool h ) ;
2013-05-06 14:09:08 +04:00
2013-05-03 21:11:00 +04:00
namespace Mirall {
2013-11-20 16:44:01 +04:00
void PropagateItemJob : : done ( SyncFileItem : : Status status , const QString & errorString )
{
_item . _errorString = errorString ;
_item . _status = status ;
// Blacklisting
int retries = 0 ;
2014-02-04 18:01:10 +04:00
if ( _item . _httpErrorCode = = 403 | | _item . _httpErrorCode = = 413 | | _item . _httpErrorCode = = 415 ) {
2013-12-12 14:38:41 +04:00
qDebug ( ) < < " Fatal Error condition " < < _item . _httpErrorCode < < " , forbid retry! " ;
2013-11-20 16:44:01 +04:00
retries = - 1 ;
} else {
retries = 3 ; // FIXME: good number of allowed retries?
}
SyncJournalBlacklistRecord record ( _item , retries ) ; ;
switch ( status ) {
2013-11-26 14:31:05 +04:00
case SyncFileItem : : SoftError :
// do not blacklist in case of soft error.
2013-11-26 15:22:28 +04:00
emit progress ( Progress : : SoftError , _item , 0 , 0 ) ;
2013-11-26 14:31:05 +04:00
break ;
2013-11-20 16:44:01 +04:00
case SyncFileItem : : FatalError :
case SyncFileItem : : NormalError :
_propagator - > _journal - > updateBlacklistEntry ( record ) ;
2013-11-26 15:22:28 +04:00
if ( status = = SyncFileItem : : NormalError ) {
emit progress ( Progress : : NormalError , _item , 0 , 0 ) ;
}
2013-11-20 16:44:01 +04:00
break ;
case SyncFileItem : : Success :
if ( _item . _blacklistedInDb ) {
// wipe blacklist entry.
_propagator - > _journal - > wipeBlacklistEntry ( _item . _file ) ;
}
break ;
case SyncFileItem : : Conflict :
case SyncFileItem : : FileIgnored :
case SyncFileItem : : NoStatus :
// nothing
break ;
}
emit completed ( _item ) ;
emit finished ( status ) ;
}
2014-02-04 18:01:10 +04:00
2014-02-12 16:44:55 +04:00
/**
* For delete or remove , check that we are not removing from a shared directory .
* If we are , try to restore the file
*
* Return true if the problem is handled .
*/
2014-02-19 20:21:01 +04:00
bool PropagateItemJob : : checkForProblemsWithShared ( const QString & msg )
2014-02-04 18:01:10 +04:00
{
QString errorString = QString : : fromUtf8 ( ne_get_error ( _propagator - > _session ) ) ;
int httpStatusCode = errorString . mid ( 0 , errorString . indexOf ( QChar ( ' ' ) ) ) . toInt ( ) ;
2014-02-19 20:21:01 +04:00
PropagateItemJob * newJob = NULL ;
2014-02-04 18:01:10 +04:00
if ( httpStatusCode = = 403 & & _propagator - > isInSharedDirectory ( _item . _file ) ) {
2014-02-12 16:44:55 +04:00
if ( _item . _type ! = SyncFileItem : : Directory ) {
// the file was removed locally from a read only Shared sync
// the file is gone locally and it should be recovered.
SyncFileItem downloadItem ( _item ) ;
downloadItem . _instruction = CSYNC_INSTRUCTION_SYNC ;
downloadItem . _dir = SyncFileItem : : Down ;
2014-02-19 20:21:01 +04:00
newJob = new PropagateDownloadFile ( _propagator , downloadItem ) ;
2014-02-12 16:44:55 +04:00
} else {
// Directories are harder to recover.
// But just re-create the directory, next sync will be able to recover the files
SyncFileItem mkdirItem ( _item ) ;
mkdirItem . _instruction = CSYNC_INSTRUCTION_SYNC ;
mkdirItem . _dir = SyncFileItem : : Down ;
2014-02-19 20:21:01 +04:00
newJob = new PropagateLocalMkdir ( _propagator , mkdirItem ) ;
2014-02-12 16:44:55 +04:00
// Also remove the inodes and fileid from the db so no further renames are tried for
// this item.
_propagator - > _journal - > avoidRenamesOnNextSync ( _item . _file ) ;
}
2014-02-19 20:21:01 +04:00
if ( newJob ) {
newJob - > setRestoreJobMsg ( msg ) ;
_restoreJob . reset ( newJob ) ;
connect ( _restoreJob . data ( ) , SIGNAL ( completed ( SyncFileItem ) ) ,
this , SLOT ( slotRestoreJobCompleted ( SyncFileItem ) ) ) ;
_restoreJob - > start ( ) ;
}
2014-02-04 18:01:10 +04:00
return true ;
}
return false ;
}
void PropagateItemJob : : slotRestoreJobCompleted ( const SyncFileItem & item )
{
2014-02-19 20:21:01 +04:00
QString msg ;
if ( _restoreJob ) {
msg = _restoreJob - > restoreJobMsg ( ) ;
_restoreJob - > setRestoreJobMsg ( ) ;
}
2014-02-04 18:01:10 +04:00
if ( item . _status = = SyncFileItem : : Success ) {
2014-02-19 20:21:01 +04:00
done ( SyncFileItem : : SoftError , msg ) ;
2014-02-04 18:01:10 +04:00
} else {
2014-02-19 20:21:01 +04:00
done ( item . _status , tr ( " A file or directory was removed from a read only share, but restoring failed: %1 " ) . arg ( item . _errorString ) ) ;
2014-02-04 18:01:10 +04:00
}
}
2014-02-19 20:21:01 +04:00
QString PropagateItemJob : : restoreJobMsg ( )
{
return _restoreJobMsg ;
}
void PropagateItemJob : : setRestoreJobMsg ( const QString & msg )
{
_restoreJobMsg = msg ;
}
2013-05-05 13:14:40 +04:00
// compare two files with given filename and return true if they have the same content
static bool fileEquals ( const QString & fn1 , const QString & fn2 ) {
QFile f1 ( fn1 ) ;
QFile f2 ( fn2 ) ;
if ( ! f1 . open ( QIODevice : : ReadOnly ) | | ! f2 . open ( QIODevice : : ReadOnly ) ) {
qDebug ( ) < < " fileEquals: Failed to open " < < fn1 < < " or " < < fn2 ;
return false ;
}
if ( f1 . size ( ) ! = f2 . size ( ) ) {
return false ;
}
const int BufferSize = 16 * 1024 ;
char buffer1 [ BufferSize ] ;
char buffer2 [ BufferSize ] ;
do {
int r = f1 . read ( buffer1 , BufferSize ) ;
if ( f2 . read ( buffer2 , BufferSize ) ! = r ) {
// this should normaly not happen: the file are supposed to have the same size.
return false ;
}
if ( r < = 0 ) {
return true ;
}
if ( memcmp ( buffer1 , buffer2 , r ) ! = 0 ) {
return false ;
}
} while ( true ) ;
return false ;
}
2013-05-03 21:11:00 +04:00
// Code copied from Qt5's QDir::removeRecursively
static bool removeRecursively ( const QString & path )
{
bool success = true ;
QDirIterator di ( path , QDir : : AllEntries | QDir : : Hidden | QDir : : System | QDir : : NoDotAndDotDot ) ;
while ( di . hasNext ( ) ) {
di . next ( ) ;
const QFileInfo & fi = di . fileInfo ( ) ;
bool ok ;
if ( fi . isDir ( ) & & ! fi . isSymLink ( ) )
ok = removeRecursively ( di . filePath ( ) ) ; // recursive
else
ok = QFile : : remove ( di . filePath ( ) ) ;
if ( ! ok )
success = false ;
}
if ( success )
success = QDir ( ) . rmdir ( path ) ;
return success ;
}
2013-10-28 13:47:10 +04:00
void PropagateLocalRemove : : start ( )
2013-05-03 21:11:00 +04:00
{
2013-10-28 13:47:10 +04:00
QString filename = _propagator - > _localDir + _item . _file ;
if ( _item . _isDirectory ) {
if ( QDir ( filename ) . exists ( ) & & ! removeRecursively ( filename ) ) {
done ( SyncFileItem : : NormalError , tr ( " Could not remove directory %1 " ) . arg ( filename ) ) ;
return ;
2013-10-04 17:13:36 +04:00
}
2013-05-03 21:11:00 +04:00
} else {
QFile file ( filename ) ;
2013-10-28 13:47:10 +04:00
if ( file . exists ( ) & & ! file . remove ( ) ) {
done ( SyncFileItem : : NormalError , file . errorString ( ) ) ;
2013-11-08 15:56:54 +04:00
return ;
2013-10-04 17:13:36 +04:00
}
2013-05-03 21:11:00 +04:00
}
2013-11-26 14:31:40 +04:00
emit progress ( Progress : : StartDelete , _item , 0 , _item . _size ) ;
2013-12-06 19:38:03 +04:00
_propagator - > _journal - > deleteFileRecord ( _item . _originalFile , _item . _isDirectory ) ;
2013-11-21 14:13:58 +04:00
_propagator - > _journal - > commit ( " Local remove " ) ;
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : Success ) ;
2013-11-27 19:48:10 +04:00
emit progress ( Progress : : EndDelete , _item , _item . _size , _item . _size ) ;
2013-05-03 21:11:00 +04:00
}
2013-10-28 13:47:10 +04:00
void PropagateLocalMkdir : : start ( )
2013-05-03 21:11:00 +04:00
{
QDir d ;
2013-10-28 13:47:10 +04:00
if ( ! d . mkpath ( _propagator - > _localDir + _item . _file ) ) {
done ( SyncFileItem : : NormalError , tr ( " could not create directory %1 " ) . arg ( _propagator - > _localDir + _item . _file ) ) ;
return ;
2013-05-03 21:11:00 +04:00
}
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : Success ) ;
2013-05-03 21:11:00 +04:00
}
2013-10-28 13:47:10 +04:00
void PropagateRemoteRemove : : start ( )
2013-05-03 21:11:00 +04:00
{
2013-10-28 13:47:10 +04:00
QScopedPointer < char , QScopedPointerPodDeleter > uri (
ne_path_escape ( ( _propagator - > _remoteDir + _item . _file ) . toUtf8 ( ) ) ) ;
2013-11-26 14:31:40 +04:00
emit progress ( Progress : : StartDelete , _item , 0 , _item . _size ) ;
2013-10-30 20:33:06 +04:00
qDebug ( ) < < " ** DELETE " < < uri . data ( ) ;
2013-10-28 13:47:10 +04:00
int rc = ne_delete ( _propagator - > _session , uri . data ( ) ) ;
2014-02-04 18:01:10 +04:00
2014-02-19 20:21:01 +04:00
if ( checkForProblemsWithShared ( tr ( " The file has been removed from a read only share. It was restored. " ) ) ) {
2014-02-04 18:01:10 +04:00
return ;
}
2013-10-30 20:33:06 +04:00
/* Ignore the error 404, it means it is already deleted */
if ( updateErrorFromSession ( rc , 0 , 404 ) ) {
2013-10-28 13:47:10 +04:00
return ;
2013-05-03 21:11:00 +04:00
}
2014-02-04 18:01:10 +04:00
2013-10-28 13:47:10 +04:00
_propagator - > _journal - > deleteFileRecord ( _item . _originalFile , _item . _isDirectory ) ;
2013-11-21 14:13:58 +04:00
_propagator - > _journal - > commit ( " Remote Remove " ) ;
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : Success ) ;
2013-11-27 19:48:10 +04:00
emit progress ( Progress : : EndDelete , _item , _item . _size , _item . _size ) ;
2013-05-03 21:11:00 +04:00
}
2013-10-28 13:47:10 +04:00
void PropagateRemoteMkdir : : start ( )
2013-05-03 21:11:00 +04:00
{
2013-10-28 13:47:10 +04:00
QScopedPointer < char , QScopedPointerPodDeleter > uri (
ne_path_escape ( ( _propagator - > _remoteDir + _item . _file ) . toUtf8 ( ) ) ) ;
2013-05-04 19:14:25 +04:00
2013-10-28 13:47:10 +04:00
int rc = ne_mkcol ( _propagator - > _session , uri . data ( ) ) ;
2013-05-04 19:14:25 +04:00
2013-10-28 13:47:10 +04:00
/* Special for mkcol: it returns 405 if the directory already exists.
* Ignore that error */
if ( updateErrorFromSession ( rc , 0 , 405 ) ) {
return ;
2013-05-03 21:11:00 +04:00
}
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : Success ) ;
2013-05-03 21:11:00 +04:00
}
2013-11-26 15:10:52 +04:00
static QByteArray parseEtag ( const char * header ) {
if ( ! header )
return QByteArray ( ) ;
QByteArray arr = header ;
arr . replace ( " -gzip " , " " ) ; // https://github.com/owncloud/mirall/issues/1195
if ( arr . length ( ) > = 2 & & arr . startsWith ( ' " ' ) & & arr . endsWith ( ' " ' ) ) {
arr = arr . mid ( 1 , arr . length ( ) - 2 ) ;
}
return arr ;
}
2013-10-28 13:47:10 +04:00
void PropagateUploadFile : : start ( )
2013-05-03 21:11:00 +04:00
{
2013-10-28 13:47:10 +04:00
QFile file ( _propagator - > _localDir + _item . _file ) ;
2013-05-03 21:11:00 +04:00
if ( ! file . open ( QIODevice : : ReadOnly ) ) {
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : NormalError , file . errorString ( ) ) ;
return ;
2013-05-03 21:11:00 +04:00
}
2013-10-28 13:47:10 +04:00
QScopedPointer < char , QScopedPointerPodDeleter > uri (
ne_path_escape ( ( _propagator - > _remoteDir + _item . _file ) . toUtf8 ( ) ) ) ;
2013-05-03 21:11:00 +04:00
2013-10-28 13:47:10 +04:00
int attempts = 0 ;
2013-09-25 16:24:31 +04:00
2013-05-03 21:11:00 +04:00
/*
* do ten tries to upload the file chunked . Check the file size and mtime
* before submitting a chunk and after having submitted the last one .
* If the file has changed , retry .
2013-10-28 13:47:10 +04:00
*/
2013-10-28 18:28:34 +04:00
qDebug ( ) < < " ** PUT request to " < < uri . data ( ) ;
2013-11-22 22:45:26 +04:00
const SyncJournalDb : : UploadInfo progressInfo = _propagator - > _journal - > getUploadInfo ( _item . _file ) ;
2013-05-03 21:11:00 +04:00
do {
Hbf_State state = HBF_SUCCESS ;
QScopedPointer < hbf_transfer_t , ScopedPointerHelpers > trans ( hbf_init_transfer ( uri . data ( ) ) ) ;
2013-10-16 18:47:24 +04:00
trans - > user_data = this ;
2013-10-02 21:41:17 +04:00
hbf_set_log_callback ( trans . data ( ) , _log_callback ) ;
hbf_set_abort_callback ( trans . data ( ) , _user_want_abort ) ;
2013-10-16 18:47:24 +04:00
trans . data ( ) - > chunk_finished_cb = chunk_finished_cb ;
2013-05-03 21:11:00 +04:00
Q_ASSERT ( trans ) ;
2014-01-07 18:42:21 +04:00
2013-05-03 21:11:00 +04:00
state = hbf_splitlist ( trans . data ( ) , file . handle ( ) ) ;
2014-01-07 18:42:21 +04:00
// If the source file has changed during upload, it is detected and the
// variable _previousFileSize is set accordingly. The propagator waits a
// couple of seconds and retries.
if ( _previousFileSize > 0 ) {
qDebug ( ) < < " File size changed underway: " < < trans - > stat_size - _previousFileSize ;
// Report the change of the overall transmission size to the propagator
_propagator - > overallTransmissionSizeChanged ( qint64 ( trans - > stat_size - _previousFileSize ) ) ;
// update the item's values to the current from trans. hbf_splitlist does a stat
_item . _size = trans - > stat_size ;
_item . _modtime = trans - > modtime ;
}
emit progress ( Progress : : StartUpload , _item , 0 , trans - > stat_size ) ;
2013-10-16 13:59:54 +04:00
if ( progressInfo . _valid ) {
2014-01-29 14:39:14 +04:00
if ( Utility : : qDateTimeToTime_t ( progressInfo . _modtime ) = = _item . _modtime ) {
2013-10-16 13:59:54 +04:00
trans - > start_id = progressInfo . _chunk ;
trans - > transfer_id = progressInfo . _transferid ;
2013-05-06 20:41:56 +04:00
}
2013-05-03 21:11:00 +04:00
}
2013-10-28 13:47:10 +04:00
ne_set_notifier ( _propagator - > _session , notify_status_cb , this ) ;
2013-08-14 21:59:16 +04:00
_lastTime . restart ( ) ;
_lastProgress = 0 ;
_chunked_done = 0 ;
2013-10-28 13:47:10 +04:00
_chunked_total_size = _item . _size ;
2013-10-02 17:37:30 +04:00
2013-05-03 21:11:00 +04:00
if ( state = = HBF_SUCCESS ) {
2013-10-02 17:37:30 +04:00
QByteArray previousEtag ;
2013-10-28 13:47:10 +04:00
if ( ! _item . _etag . isEmpty ( ) & & _item . _etag ! = " empty_etag " ) {
2013-10-02 17:37:30 +04:00
// We add quotes because the owncloud server always add quotes around the etag, and
// csync_owncloud.c's owncloud_file_id always strip the quotes.
2013-10-28 13:47:10 +04:00
previousEtag = ' " ' + _item . _etag + ' " ' ;
2013-10-02 17:37:30 +04:00
trans - > previous_etag = previousEtag . data ( ) ;
}
2013-08-14 21:59:16 +04:00
_chunked_total_size = trans - > stat_size ;
2013-10-28 19:26:25 +04:00
qDebug ( ) < < " About to upload " < < _item . _file < < " ( " < < previousEtag < < _item . _size < < " bytes ) " ;
2013-05-03 21:11:00 +04:00
/* Transfer all the chunks through the HTTP session using PUT. */
2013-10-28 13:47:10 +04:00
state = hbf_transfer ( _propagator - > _session , trans . data ( ) , " PUT " ) ;
2013-09-25 16:24:31 +04:00
}
2013-10-28 18:28:34 +04:00
// the file id should only be empty for new files up- or downloaded
QString fid = QString : : fromUtf8 ( hbf_transfer_file_id ( trans . data ( ) ) ) ;
2013-11-15 13:14:06 +04:00
if ( ! fid . isEmpty ( ) ) {
if ( ! _item . _fileId . isEmpty ( ) & & _item . _fileId ! = fid ) {
2013-11-14 16:47:43 +04:00
qDebug ( ) < < " WARN: File ID changed! " < < _item . _fileId < < fid ;
}
2013-11-15 13:14:06 +04:00
_item . _fileId = fid ;
2013-10-28 18:28:34 +04:00
}
2013-10-25 15:28:48 +04:00
2013-05-03 21:11:00 +04:00
/* Handle errors. */
if ( state ! = HBF_SUCCESS ) {
/* If the source file changed during submission, lets try again */
if ( state = = HBF_SOURCE_FILE_CHANGE ) {
2013-11-25 22:12:13 +04:00
if ( attempts + + < 5 ) { /* FIXME: How often do we want to try? */
qDebug ( " SOURCE file has changed during upload, retry #%d in %d seconds! " , attempts , 2 * attempts ) ;
sleep ( 2 * attempts ) ;
2014-01-07 18:42:21 +04:00
if ( _previousFileSize = = 0 ) {
_previousFileSize = _item . _size ;
} else {
_previousFileSize = trans - > stat_size ;
}
2013-11-25 19:16:33 +04:00
continue ;
}
const QString errMsg = tr ( " Local file changed during sync, syncing once it arrived completely " ) ;
done ( SyncFileItem : : SoftError , errMsg ) ;
2013-11-25 01:26:50 +04:00
} else if ( state = = HBF_USER_ABORTED ) {
const QString errMsg = tr ( " Sync was aborted by user. " ) ;
2013-11-26 15:22:28 +04:00
done ( SyncFileItem : : SoftError , errMsg ) ;
2013-11-25 01:26:50 +04:00
} else {
2013-11-25 19:16:33 +04:00
// Other HBF error conditions.
2013-11-25 01:26:50 +04:00
_item . _httpErrorCode = hbf_fail_http_code ( trans . data ( ) ) ;
2014-02-19 20:21:01 +04:00
if ( _item . _httpErrorCode = = 403 & & _propagator - > isInSharedDirectory ( _item . _file ) ) {
// a read only share file has been modified. Conflict it and
// restore the original file.
QString fn = _propagator - > _localDir + _item . _file ;
QFile f ( fn ) ;
QString conflictFileName ( fn ) ;
int dotLocation = conflictFileName . lastIndexOf ( ' . ' ) ;
QString timeString = Utility : : qDateTimeFromTime_t ( _item . _modtime ) . toString ( " yyyyMMdd-hhmmss " ) ;
conflictFileName . insert ( dotLocation , " _conflict- " + timeString ) ;
if ( ! f . rename ( conflictFileName ) ) {
//If the rename fails, don't replace it.
done ( SyncFileItem : : NormalError , f . errorString ( ) ) ;
return ;
}
if ( checkForProblemsWithShared ( tr ( " The file was edited locally but is part of a read only share. It is restored and your edit is in the conflict file. " ) ) ) {
return ;
}
}
2013-11-25 01:26:50 +04:00
done ( SyncFileItem : : NormalError , hbf_error_string ( trans . data ( ) , state ) ) ;
2013-05-03 21:11:00 +04:00
}
2013-10-28 13:47:10 +04:00
return ;
2013-05-03 21:11:00 +04:00
}
2013-10-28 13:47:10 +04:00
ne_set_notifier ( _propagator - > _session , 0 , 0 ) ;
2013-10-28 18:28:34 +04:00
2013-10-28 13:47:10 +04:00
if ( trans - > modtime_accepted ) {
2013-11-26 15:10:52 +04:00
_item . _etag = parseEtag ( hbf_transfer_etag ( trans . data ( ) ) ) ;
2013-10-28 13:47:10 +04:00
} else {
2014-02-06 20:02:10 +04:00
if ( ! updateMTimeAndETag ( uri . data ( ) , _item . _modtime ) )
return ;
2013-10-28 13:47:10 +04:00
}
2013-10-16 13:59:54 +04:00
2013-10-28 13:47:10 +04:00
_propagator - > _journal - > setFileRecord ( SyncJournalFileRecord ( _item , _propagator - > _localDir + _item . _file ) ) ;
// Remove from the progress database:
_propagator - > _journal - > setUploadInfo ( _item . _file , SyncJournalDb : : UploadInfo ( ) ) ;
2013-11-21 14:13:58 +04:00
_propagator - > _journal - > commit ( " upload file start " ) ;
2013-11-25 22:12:13 +04:00
if ( hbf_validate_source_file ( trans . data ( ) ) = = HBF_SOURCE_FILE_CHANGE ) {
/* Did the source file changed since the upload ?
This is different from the previous check because the previous check happens between
chunks while this one happens when the whole file has been uploaded .
The new etag is already stored in the database in the previous lines so in case of
crash , we won ' t have a conflict but we will properly do a new upload
*/
if ( attempts + + < 5 ) { /* FIXME: How often do we want to try? */
qDebug ( " SOURCE file has changed after upload, retry #%d in %d seconds! " , attempts , 2 * attempts ) ;
sleep ( 2 * attempts ) ;
continue ;
}
// Still the file change error, but we tried a couple of times.
// Ignore this file for now.
// Lets remove the file from the server (at least if it is new) as it is different
// from our file here.
if ( _item . _instruction = = CSYNC_INSTRUCTION_NEW ) {
QScopedPointer < char , QScopedPointerPodDeleter > uri (
ne_path_escape ( ( _propagator - > _remoteDir + _item . _file ) . toUtf8 ( ) ) ) ;
int rc = ne_delete ( _propagator - > _session , uri . data ( ) ) ;
qDebug ( ) < < " Remove the invalid file from server: " < < rc ;
}
const QString errMsg = tr ( " Local file changed during sync, syncing once it arrived completely " ) ;
done ( SyncFileItem : : SoftError , errMsg ) ;
return ;
}
2013-11-27 19:48:10 +04:00
emit progress ( Progress : : EndUpload , _item , _item . _size , _item . _size ) ;
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : Success ) ;
return ;
2013-10-16 13:59:54 +04:00
2013-10-28 13:47:10 +04:00
} while ( true ) ;
2013-05-04 17:32:11 +04:00
}
2013-05-03 21:11:00 +04:00
2014-01-16 16:49:55 +04:00
void PropagateUploadFile : : chunk_finished_cb ( hbf_transfer_s * trans , int chunk , void * userdata )
{
PropagateUploadFile * that = static_cast < PropagateUploadFile * > ( userdata ) ;
Q_ASSERT ( that ) ;
that - > _chunked_done + = trans - > block_arr [ chunk ] - > size ;
if ( trans - > block_cnt > 1 ) {
SyncJournalDb : : UploadInfo pi ;
pi . _valid = true ;
pi . _chunk = chunk + 1 ; // next chunk to start with
pi . _transferid = trans - > transfer_id ;
2014-01-29 14:39:14 +04:00
pi . _modtime = Utility : : qDateTimeFromTime_t ( trans - > modtime ) ;
2014-01-16 16:49:55 +04:00
that - > _propagator - > _journal - > setUploadInfo ( that - > _item . _file , pi ) ;
that - > _propagator - > _journal - > commit ( " Upload info " ) ;
}
}
void PropagateUploadFile : : notify_status_cb ( void * userdata , ne_session_status status ,
const ne_session_status_info * info )
{
PropagateUploadFile * that = reinterpret_cast < PropagateUploadFile * > ( userdata ) ;
if ( status = = ne_status_sending & & info - > sr . total > 0 ) {
emit that - > progress ( Progress : : Context , that - > _item ,
that - > _chunked_done + info - > sr . progress ,
that - > _chunked_total_size ? that - > _chunked_total_size : info - > sr . total ) ;
2014-01-31 20:29:50 +04:00
QCoreApplication : : processEvents ( ) ;
2014-01-16 16:49:55 +04:00
that - > limitBandwidth ( that - > _chunked_done + info - > sr . progress , that - > _propagator - > _uploadLimit ) ;
}
}
2013-05-05 13:29:06 +04:00
2013-10-28 18:28:34 +04:00
static QString parseFileId ( ne_request * req ) {
QString fileId ;
2013-10-30 20:36:11 +04:00
const char * header = ne_get_response_header ( req , " OC-FileId " ) ;
2013-10-28 18:28:34 +04:00
if ( header ) {
fileId = QString : : fromUtf8 ( header ) ;
}
return fileId ;
}
2014-02-06 20:02:10 +04:00
bool PropagateItemJob : : updateMTimeAndETag ( const char * uri , time_t mtime )
2013-05-04 17:32:11 +04:00
{
QByteArray modtime = QByteArray : : number ( qlonglong ( mtime ) ) ;
2013-05-03 21:11:00 +04:00
ne_propname pname ;
pname . nspace = " DAV: " ;
pname . name = " lastmodified " ;
ne_proppatch_operation ops [ 2 ] ;
ops [ 0 ] . name = & pname ;
ops [ 0 ] . type = ne_propset ;
ops [ 0 ] . value = modtime . constData ( ) ;
ops [ 1 ] . name = NULL ;
2013-10-28 13:47:10 +04:00
int rc = ne_proppatch ( _propagator - > _session , uri , ops ) ;
2013-11-13 17:30:08 +04:00
Q_UNUSED ( rc ) ;
2013-10-28 13:47:10 +04:00
/* FIXME: error handling
2013-05-04 19:14:25 +04:00
bool error = updateErrorFromSession ( rc ) ;
if ( error ) {
// FIXME: We could not set the mtime. Error or not?
2013-05-05 14:34:38 +04:00
qDebug ( ) < < " PROP-Patching of modified date failed. " ;
2013-10-28 13:47:10 +04:00
} */
2013-05-03 21:11:00 +04:00
// get the etag
2013-10-28 13:47:10 +04:00
QScopedPointer < ne_request , ScopedPointerHelpers > req ( ne_request_create ( _propagator - > _session , " HEAD " , uri ) ) ;
2013-05-03 21:11:00 +04:00
int neon_stat = ne_request_dispatch ( req . data ( ) ) ;
2014-02-06 20:02:10 +04:00
if ( updateErrorFromSession ( neon_stat , req . data ( ) ) ) {
return false ;
2013-05-03 21:11:00 +04:00
} else {
2013-11-26 15:10:52 +04:00
_item . _etag = parseEtag ( ne_get_response_header ( req . data ( ) , " etag " ) ) ;
2013-10-28 18:28:34 +04:00
QString fid = parseFileId ( req . data ( ) ) ;
2013-10-28 19:26:25 +04:00
if ( _item . _fileId . isEmpty ( ) ) {
_item . _fileId = fid ;
qDebug ( ) < < " FileID was empty, set it to " < < _item . _fileId ;
2013-10-28 18:28:34 +04:00
} else {
2013-10-28 19:26:25 +04:00
if ( ! fid . isEmpty ( ) & & fid ! = _item . _fileId ) {
qDebug ( ) < < " WARN: FileID seems to have changed: " < < fid < < _item . _fileId ;
2013-10-30 20:36:58 +04:00
} else {
qDebug ( ) < < " FileID is " < < _item . _fileId ;
2013-10-28 18:28:34 +04:00
}
}
2014-02-06 20:02:10 +04:00
return true ;
2013-05-03 21:11:00 +04:00
}
2013-10-25 15:29:20 +04:00
}
2013-10-28 13:47:10 +04:00
void PropagateItemJob : : limitBandwidth ( qint64 progress , qint64 bandwidth_limit )
{
if ( bandwidth_limit > 0 ) {
int64_t diff = _lastTime . nsecsElapsed ( ) / 1000 ;
int64_t len = progress - _lastProgress ;
if ( len > 0 & & diff > 0 & & ( 1000000 * len / diff ) > bandwidth_limit ) {
int64_t wait_time = ( 1000000 * len / bandwidth_limit ) - diff ;
if ( wait_time > 0 ) {
2013-12-10 16:47:44 +04:00
//qDebug() << "Limiting bandwidth to " << bandwidth_limit << "KB/s by waiting " << wait_time << " µs; ";
Mirall : : Utility : : usleep ( wait_time ) ;
2013-10-28 13:47:10 +04:00
}
}
_lastProgress = progress ;
_lastTime . start ( ) ;
} else if ( bandwidth_limit < 0 & & bandwidth_limit > - 100 ) {
int64_t diff = _lastTime . nsecsElapsed ( ) / 1000 ;
if ( diff > 0 ) {
// -bandwidth_limit is the % of bandwidth
int64_t wait_time = - diff * ( 1 + 100.0 / bandwidth_limit ) ;
if ( wait_time > 0 ) {
2013-12-10 16:47:44 +04:00
Mirall : : Utility : : usleep ( wait_time ) ;
2013-10-28 13:47:10 +04:00
}
}
_lastTime . start ( ) ;
}
}
2013-05-04 17:32:11 +04:00
2014-01-16 16:49:55 +04:00
int PropagateDownloadFile : : content_reader ( void * userdata , const char * buf , size_t len )
{
PropagateDownloadFile * that = static_cast < PropagateDownloadFile * > ( userdata ) ;
size_t written = 0 ;
2013-05-03 21:11:00 +04:00
2014-01-16 16:49:55 +04:00
if ( that - > _propagator - > _abortRequested - > fetchAndAddRelaxed ( 0 ) ) {
2014-01-20 20:22:29 +04:00
ne_set_error ( that - > _propagator - > _session , " %s " , tr ( " Sync was aborted by user. " ) . toUtf8 ( ) . data ( ) ) ;
2014-01-16 16:49:55 +04:00
return NE_ERROR ;
}
2013-05-03 21:11:00 +04:00
2014-01-16 16:49:55 +04:00
if ( buf ) {
written = that - > _file - > write ( buf , len ) ;
if ( len ! = written | | that - > _file - > error ( ) ! = QFile : : NoError ) {
qDebug ( ) < < " WRN: content_reader wrote wrong num of bytes: " < < len < < " , " < < written ;
2013-10-02 21:41:17 +04:00
return NE_ERROR ;
}
2014-01-16 16:49:55 +04:00
return NE_OK ;
2013-05-03 21:11:00 +04:00
}
2014-01-31 20:29:50 +04:00
2014-01-16 16:49:55 +04:00
return NE_ERROR ;
}
2013-05-03 21:11:00 +04:00
2014-01-16 16:49:55 +04:00
/*
* This hook is called after the response is here from the server , but before
* the response body is parsed . It decides if the response is compressed and
* if it is it installs the compression reader accordingly .
* If the response is not compressed , the normal response body reader is installed .
*/
void PropagateDownloadFile : : install_content_reader ( ne_request * req , void * userdata , const ne_status * status )
{
PropagateDownloadFile * that = static_cast < PropagateDownloadFile * > ( userdata ) ;
2013-11-20 17:59:58 +04:00
2014-01-16 16:49:55 +04:00
Q_UNUSED ( status ) ;
2013-11-19 15:44:25 +04:00
2014-01-16 16:49:55 +04:00
if ( ! that ) {
qDebug ( " Error: install_content_reader called without valid write context! " ) ;
return ;
2013-11-19 15:44:25 +04:00
}
2014-01-16 16:49:55 +04:00
if ( ne_get_status ( req ) - > klass ! = 2 ) {
qDebug ( ) < < " Request class != 2, aborting. " ;
ne_add_response_body_reader ( req , do_not_accept ,
do_not_download_content_reader ,
( void * ) that ) ;
return ;
}
2013-05-03 21:11:00 +04:00
2014-01-16 16:49:55 +04:00
QByteArray reason_phrase = ne_get_status ( req ) - > reason_phrase ;
if ( reason_phrase = = QByteArray ( " Connection established " ) ) {
ne_add_response_body_reader ( req , ne_accept_2xx ,
content_reader ,
( void * ) that ) ;
return ;
}
2013-05-03 21:11:00 +04:00
2014-01-16 16:49:55 +04:00
QByteArray etag = parseEtag ( ne_get_response_header ( req , " etag " ) ) ;
if ( etag . isEmpty ( ) )
etag = parseEtag ( ne_get_response_header ( req , " ETag " ) ) ;
2013-12-11 15:59:01 +04:00
2014-01-16 16:49:55 +04:00
if ( etag . isEmpty ( ) ) {
qDebug ( ) < < Q_FUNC_INFO < < " No E-Tag reply by server, considering it invalid " < < ne_get_response_header ( req , " etag " ) ;
that - > errorString = tr ( " No E-Tag received from server, check Proxy/Gateway " ) ;
2014-01-20 20:22:29 +04:00
ne_set_error ( that - > _propagator - > _session , " %s " , that - > errorString . toUtf8 ( ) . data ( ) ) ;
2014-01-16 16:49:55 +04:00
ne_add_response_body_reader ( req , do_not_accept ,
do_not_download_content_reader ,
2013-12-11 16:50:22 +04:00
( void * ) that ) ;
2014-01-16 16:49:55 +04:00
return ;
} else if ( ! that - > _expectedEtagForResume . isEmpty ( ) & & that - > _expectedEtagForResume ! = etag ) {
qDebug ( ) < < Q_FUNC_INFO < < " We received a different E-Tag for resuming! "
< < QString : : fromLatin1 ( that - > _expectedEtagForResume . data ( ) ) < < " vs "
< < QString : : fromLatin1 ( etag . data ( ) ) ;
that - > errorString = tr ( " We received a different E-Tag for resuming. Retrying next time. " ) ;
2014-01-20 20:22:29 +04:00
ne_set_error ( that - > _propagator - > _session , " %s " , that - > errorString . toUtf8 ( ) . data ( ) ) ;
2014-01-16 16:49:55 +04:00
ne_add_response_body_reader ( req , do_not_accept ,
do_not_download_content_reader ,
( void * ) that ) ;
return ;
}
2013-11-19 15:44:25 +04:00
2013-11-25 22:00:34 +04:00
2014-01-16 16:49:55 +04:00
const char * enc = ne_get_response_header ( req , " Content-Encoding " ) ;
qDebug ( " Content encoding ist <%s> with status %d " , enc ? enc : " empty " ,
status ? status - > code : - 1 ) ;
2013-05-03 21:11:00 +04:00
2014-01-16 16:49:55 +04:00
if ( enc = = QLatin1String ( " gzip " ) ) {
that - > _decompress . reset ( ne_decompress_reader ( req , ne_accept_2xx ,
content_reader , /* reader callback */
that ) ) ; /* userdata */
} else {
ne_add_response_body_reader ( req , ne_accept_2xx ,
content_reader ,
( void * ) that ) ;
2013-10-28 13:47:10 +04:00
}
2014-01-16 16:49:55 +04:00
}
2013-10-28 13:47:10 +04:00
2014-01-16 16:49:55 +04:00
void PropagateDownloadFile : : notify_status_cb ( void * userdata , ne_session_status status ,
const ne_session_status_info * info )
{
PropagateDownloadFile * that = reinterpret_cast < PropagateDownloadFile * > ( userdata ) ;
if ( status = = ne_status_recving & & info - > sr . total > 0 ) {
emit that - > progress ( Progress : : Context , that - > _item , info - > sr . progress , info - > sr . total ) ;
2014-01-31 20:29:50 +04:00
QCoreApplication : : processEvents ( ) ;
2014-01-16 16:49:55 +04:00
that - > limitBandwidth ( info - > sr . progress , that - > _propagator - > _downloadLimit ) ;
2013-05-03 21:11:00 +04:00
}
2014-01-16 16:49:55 +04:00
}
2013-05-03 21:11:00 +04:00
2013-10-28 13:47:10 +04:00
void PropagateDownloadFile : : start ( )
2013-05-03 21:11:00 +04:00
{
2013-11-25 19:16:33 +04:00
emit progress ( Progress : : StartDownload , _item , 0 , _item . _size ) ;
2013-10-28 13:47:10 +04:00
2013-05-06 18:59:11 +04:00
QString tmpFileName ;
2013-10-28 13:47:10 +04:00
const SyncJournalDb : : DownloadInfo progressInfo = _propagator - > _journal - > getDownloadInfo ( _item . _file ) ;
2013-10-16 13:59:54 +04:00
if ( progressInfo . _valid ) {
2013-10-03 14:40:48 +04:00
// if the etag has changed meanwhile, remove the already downloaded part.
2013-10-28 13:47:10 +04:00
if ( progressInfo . _etag ! = _item . _etag ) {
QFile : : remove ( _propagator - > _localDir + progressInfo . _tmpfile ) ;
_propagator - > _journal - > setDownloadInfo ( _item . _file , SyncJournalDb : : DownloadInfo ( ) ) ;
2013-05-06 18:59:11 +04:00
} else {
2013-10-16 13:59:54 +04:00
tmpFileName = progressInfo . _tmpfile ;
2013-11-25 22:00:34 +04:00
_expectedEtagForResume = progressInfo . _etag ;
2013-05-06 18:59:11 +04:00
}
2013-10-16 13:59:54 +04:00
2013-05-06 18:59:11 +04:00
}
2013-10-03 14:40:48 +04:00
2013-05-06 18:59:11 +04:00
if ( tmpFileName . isEmpty ( ) ) {
2013-10-28 13:47:10 +04:00
tmpFileName = _item . _file ;
2013-05-08 16:31:52 +04:00
//add a dot at the begining of the filename to hide the file.
int slashPos = tmpFileName . lastIndexOf ( ' / ' ) ;
tmpFileName . insert ( slashPos + 1 , ' . ' ) ;
//add the suffix
tmpFileName + = " .~ " + QString : : number ( uint ( qrand ( ) ) , 16 ) ;
2013-05-06 18:59:11 +04:00
}
2013-10-28 13:47:10 +04:00
QFile tmpFile ( _propagator - > _localDir + tmpFileName ) ;
_file = & tmpFile ;
2013-11-25 22:22:43 +04:00
if ( ! tmpFile . open ( QIODevice : : Append | QIODevice : : Unbuffered ) ) {
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : NormalError , tmpFile . errorString ( ) ) ;
return ;
2013-05-03 21:11:00 +04:00
}
2014-01-08 16:32:48 +04:00
csync_win32_set_file_hidden ( tmpFile . fileName ( ) . toUtf8 ( ) . constData ( ) , true ) ;
2013-05-06 18:59:11 +04:00
{
2013-10-16 13:59:54 +04:00
SyncJournalDb : : DownloadInfo pi ;
2013-10-28 13:47:10 +04:00
pi . _etag = _item . _etag ;
2013-10-16 13:59:54 +04:00
pi . _tmpfile = tmpFileName ;
pi . _valid = true ;
2013-10-28 13:47:10 +04:00
_propagator - > _journal - > setDownloadInfo ( _item . _file , pi ) ;
2013-11-21 14:13:58 +04:00
_propagator - > _journal - > commit ( " download file start " ) ;
2013-05-06 18:59:11 +04:00
}
2013-05-03 21:11:00 +04:00
/* actually do the request */
int retry = 0 ;
2013-05-06 18:59:11 +04:00
2013-10-28 13:47:10 +04:00
QScopedPointer < char , QScopedPointerPodDeleter > uri (
ne_path_escape ( ( _propagator - > _remoteDir + _item . _file ) . toUtf8 ( ) ) ) ;
2013-05-03 21:11:00 +04:00
do {
2013-10-28 13:47:10 +04:00
QScopedPointer < ne_request , ScopedPointerHelpers > req ( ne_request_create ( _propagator - > _session , " GET " , uri . data ( ) ) ) ;
2013-05-03 21:11:00 +04:00
/* Allow compressed content by setting the header */
ne_add_request_header ( req . data ( ) , " Accept-Encoding " , " gzip " ) ;
if ( tmpFile . size ( ) > 0 ) {
2013-12-03 17:43:01 +04:00
quint64 done = tmpFile . size ( ) ;
if ( done = = _item . _size ) {
qDebug ( ) < < " File is already complete, no need to download " ;
break ;
}
QByteArray rangeRequest = " bytes= " + QByteArray : : number ( done ) + ' - ' ;
ne_add_request_header ( req . data ( ) , " Range " , rangeRequest . constData ( ) ) ;
2013-05-03 21:11:00 +04:00
ne_add_request_header ( req . data ( ) , " Accept-Ranges " , " bytes " ) ;
2013-12-03 17:43:01 +04:00
qDebug ( ) < < " Retry with range " < < rangeRequest ;
2013-05-03 21:11:00 +04:00
}
/* hook called before the content is parsed to set the correct reader,
* either the compressed - or uncompressed reader .
*/
2013-10-28 13:47:10 +04:00
ne_hook_post_headers ( _propagator - > _session , install_content_reader , this ) ;
ne_set_notifier ( _propagator - > _session , notify_status_cb , this ) ;
2013-08-14 21:59:16 +04:00
_lastProgress = 0 ;
_lastTime . start ( ) ;
2013-05-03 21:11:00 +04:00
int neon_stat = ne_request_dispatch ( req . data ( ) ) ;
2013-12-05 19:49:24 +04:00
_decompress . reset ( ) ; // Destroy the decompress after the request has been dispatched.
2013-10-28 13:47:10 +04:00
/* delete the hook again, otherwise they get chained as they are with the session */
ne_unhook_post_headers ( _propagator - > _session , install_content_reader , this ) ;
ne_set_notifier ( _propagator - > _session , 0 , 0 ) ;
2013-05-04 19:14:25 +04:00
if ( neon_stat = = NE_TIMEOUT & & ( + + retry ) < 3 ) {
2013-05-03 21:11:00 +04:00
continue ;
2013-05-04 19:14:25 +04:00
}
2013-05-03 21:11:00 +04:00
2013-11-19 15:44:25 +04:00
// This one is set by install_content_reader if e.g. there is no E-Tag
if ( ! errorString . isEmpty ( ) ) {
2013-12-11 15:48:11 +04:00
// don't keep the temporary file as the file downloaded so far is invalid
tmpFile . close ( ) ;
tmpFile . remove ( ) ;
_propagator - > _journal - > setDownloadInfo ( _item . _file , SyncJournalDb : : DownloadInfo ( ) ) ;
2013-11-19 15:44:25 +04:00
done ( SyncFileItem : : SoftError , errorString ) ;
return ;
}
// This one is set by neon
2013-05-05 13:41:31 +04:00
if ( updateErrorFromSession ( neon_stat , req . data ( ) ) ) {
2013-05-03 21:11:00 +04:00
qDebug ( " Error GET: Neon: %d " , neon_stat ) ;
2013-05-06 18:59:11 +04:00
if ( tmpFile . size ( ) = = 0 ) {
// don't keep the temporary file if it is empty.
tmpFile . close ( ) ;
tmpFile . remove ( ) ;
2013-10-28 13:47:10 +04:00
_propagator - > _journal - > setDownloadInfo ( _item . _file , SyncJournalDb : : DownloadInfo ( ) ) ;
2013-05-06 18:59:11 +04:00
}
2013-10-28 13:47:10 +04:00
return ;
2013-05-03 21:11:00 +04:00
}
2013-11-26 15:10:52 +04:00
_item . _etag = parseEtag ( ne_get_response_header ( req . data ( ) , " etag " ) ) ;
2013-05-03 21:11:00 +04:00
break ;
} while ( 1 ) ;
tmpFile . close ( ) ;
tmpFile . flush ( ) ;
2013-10-28 13:47:10 +04:00
QString fn = _propagator - > _localDir + _item . _file ;
2013-05-03 21:11:00 +04:00
2013-10-30 13:46:05 +04:00
2013-10-29 15:23:51 +04:00
bool isConflict = _item . _instruction = = CSYNC_INSTRUCTION_CONFLICT
& & ! fileEquals ( fn , tmpFile . fileName ( ) ) ; // compare the files to see if there was an actual conflict.
2013-05-05 13:14:40 +04:00
//In case of conflict, make a backup of the old file
if ( isConflict ) {
QFile f ( fn ) ;
2013-11-07 19:53:22 +04:00
QString conflictFileName ( fn ) ;
2013-05-05 13:14:40 +04:00
// Add _conflict-XXXX before the extention.
2013-11-07 19:53:22 +04:00
int dotLocation = conflictFileName . lastIndexOf ( ' . ' ) ;
2013-05-05 13:14:40 +04:00
// If no extention, add it at the end (take care of cases like foo/.hidden or foo.bar/file)
2013-11-07 19:53:22 +04:00
if ( dotLocation < = conflictFileName . lastIndexOf ( ' / ' ) + 1 ) {
dotLocation = conflictFileName . size ( ) ;
2013-05-05 13:14:40 +04:00
}
2014-01-29 14:39:14 +04:00
QString timeString = Utility : : qDateTimeFromTime_t ( _item . _modtime ) . toString ( " yyyyMMdd-hhmmss " ) ;
conflictFileName . insert ( dotLocation , " _conflict- " + timeString ) ;
2013-11-07 19:53:22 +04:00
if ( ! f . rename ( conflictFileName ) ) {
2013-05-05 13:14:40 +04:00
//If the rename fails, don't replace it.
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : NormalError , f . errorString ( ) ) ;
return ;
2013-05-05 13:14:40 +04:00
}
}
2014-01-08 16:17:35 +04:00
QFileInfo existingFile ( fn ) ;
if ( existingFile . exists ( ) & & existingFile . permissions ( ) ! = tmpFile . permissions ( ) ) {
tmpFile . setPermissions ( existingFile . permissions ( ) ) ;
}
2014-01-08 16:32:48 +04:00
csync_win32_set_file_hidden ( tmpFile . fileName ( ) . toUtf8 ( ) . constData ( ) , false ) ;
2013-05-08 16:31:52 +04:00
2013-12-03 19:10:44 +04:00
# ifndef Q_OS_WIN
2013-09-05 19:08:05 +04:00
bool success ;
# if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
2013-10-28 13:47:10 +04:00
success = tmpFile . fileEngine ( ) - > rename ( fn ) ;
2013-11-07 19:53:22 +04:00
// qDebug() << "Renaming " << tmpFile.fileName() << " to " << fn;
2013-09-05 19:08:05 +04:00
# else
// We want a rename that also overwite. QFile::rename does not overwite.
// Qt 5.1 has QSaveFile::renameOverwrite we cold use.
// ### FIXME
2013-10-28 13:47:10 +04:00
QFile : : remove ( fn ) ;
success = tmpFile . rename ( fn ) ;
2013-09-05 19:08:05 +04:00
# endif
2013-10-04 18:41:15 +04:00
// unixoids
2013-09-05 19:08:05 +04:00
if ( ! success ) {
2013-11-07 19:53:22 +04:00
qDebug ( ) < < " FAIL: renaming temp file to final failed: " < < tmpFile . errorString ( ) ;
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : NormalError , tmpFile . errorString ( ) ) ;
return ;
2013-05-03 21:11:00 +04:00
}
2013-12-03 19:10:44 +04:00
# else //Q_OS_WIN
2013-12-03 20:07:21 +04:00
BOOL ok ;
ok = MoveFileEx ( ( wchar_t * ) tmpFile . fileName ( ) . utf16 ( ) ,
( wchar_t * ) QString ( _propagator - > _localDir + _item . _file ) . utf16 ( ) ,
MOVEFILE_REPLACE_EXISTING + MOVEFILE_COPY_ALLOWED + MOVEFILE_WRITE_THROUGH ) ;
if ( ! ok ) {
2013-05-04 18:18:13 +04:00
wchar_t * string = 0 ;
FormatMessage ( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM ,
NULL , : : GetLastError ( ) , MAKELANGID ( LANG_NEUTRAL , SUBLANG_DEFAULT ) ,
( LPWSTR ) & string , 0 , NULL ) ;
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : NormalError , QString : : fromWCharArray ( string ) ) ;
2013-05-04 18:18:13 +04:00
LocalFree ( ( HLOCAL ) string ) ;
2013-10-28 13:47:10 +04:00
return ;
2013-05-04 18:18:13 +04:00
}
# endif
2013-05-06 14:09:08 +04:00
struct timeval times [ 2 ] ;
2013-10-28 13:47:10 +04:00
times [ 0 ] . tv_sec = times [ 1 ] . tv_sec = _item . _modtime ;
2013-05-06 14:09:08 +04:00
times [ 0 ] . tv_usec = times [ 1 ] . tv_usec = 0 ;
2013-10-28 13:47:10 +04:00
c_utimes ( fn . toUtf8 ( ) . data ( ) , times ) ;
2013-05-06 14:09:08 +04:00
2013-10-28 13:47:10 +04:00
_propagator - > _journal - > setFileRecord ( SyncJournalFileRecord ( _item , fn ) ) ;
_propagator - > _journal - > setDownloadInfo ( _item . _file , SyncJournalDb : : DownloadInfo ( ) ) ;
2013-11-21 14:13:58 +04:00
_propagator - > _journal - > commit ( " download file start2 " ) ;
2013-11-27 19:48:10 +04:00
emit progress ( Progress : : EndDownload , _item , _item . _size , _item . _size ) ;
2013-10-28 13:47:10 +04:00
done ( isConflict ? SyncFileItem : : Conflict : SyncFileItem : : Success ) ;
2013-05-03 21:11:00 +04:00
}
2013-10-30 20:36:58 +04:00
void PropagateLocalRename : : start ( )
{
2013-12-06 15:32:26 +04:00
// if the file is a file underneath a moved dir, the _item.file is equal
// to _item.renameTarget and the file is not moved as a result.
2013-10-30 21:14:33 +04:00
if ( _item . _file ! = _item . _renameTarget ) {
2013-12-06 15:32:26 +04:00
emit progress ( Progress : : StartRename , _item , 0 , _item . _size ) ;
2013-10-30 20:36:58 +04:00
qDebug ( ) < < " MOVE " < < _propagator - > _localDir + _item . _file < < " => " < < _propagator - > _localDir + _item . _renameTarget ;
QFile : : rename ( _propagator - > _localDir + _item . _file , _propagator - > _localDir + _item . _renameTarget ) ;
2013-12-06 15:32:26 +04:00
emit progress ( Progress : : EndRename , _item , _item . _size , _item . _size ) ;
2013-10-30 20:36:58 +04:00
}
_item . _instruction = CSYNC_INSTRUCTION_DELETED ;
_propagator - > _journal - > deleteFileRecord ( _item . _originalFile ) ;
2013-11-05 20:50:09 +04:00
// store the rename file name in the item.
_item . _file = _item . _renameTarget ;
SyncJournalFileRecord record ( _item , _propagator - > _localDir + _item . _renameTarget ) ;
2013-10-30 20:36:58 +04:00
record . _path = _item . _renameTarget ;
2013-11-05 20:50:09 +04:00
2014-02-06 20:16:22 +04:00
if ( ! _item . _isDirectory ) { // Directory are saved at the end
_propagator - > _journal - > setFileRecord ( record ) ;
}
2013-11-21 14:13:58 +04:00
_propagator - > _journal - > commit ( " localRename " ) ;
2013-11-18 12:59:59 +04:00
2013-11-05 20:50:09 +04:00
2013-10-30 20:36:58 +04:00
done ( SyncFileItem : : Success ) ;
}
2013-10-28 13:47:10 +04:00
void PropagateRemoteRename : : start ( )
2013-05-04 17:32:11 +04:00
{
2014-02-19 16:06:55 +04:00
qDebug ( ) < < Q_FUNC_INFO < < _item . _file < < " --> " < < _item . _renameTarget ;
2013-10-28 13:47:10 +04:00
if ( _item . _file = = _item . _renameTarget ) {
if ( ! _item . _isDirectory ) {
2013-10-17 14:57:05 +04:00
// The parents has been renamed already so there is nothing more to do.
// But we still need to fetch the new ETAG
2013-10-30 20:36:58 +04:00
// FIXME maybe do a recusrsive propfind after having moved the parent.
2013-10-17 14:57:05 +04:00
// Note: we also update the mtime because the server do not keep the mtime when moving files
2013-10-28 13:47:10 +04:00
QScopedPointer < char , QScopedPointerPodDeleter > uri2 (
ne_path_escape ( ( _propagator - > _remoteDir + _item . _renameTarget ) . toUtf8 ( ) ) ) ;
2014-02-06 20:02:10 +04:00
if ( ! updateMTimeAndETag ( uri2 . data ( ) , _item . _modtime ) )
return ;
2013-10-17 14:57:05 +04:00
}
2013-10-28 13:47:10 +04:00
} else if ( _item . _file = = QLatin1String ( " Shared " ) ) {
// Check if it is the toplevel Shared folder and do not propagate it.
if ( QFile : : rename ( _propagator - > _localDir + _item . _renameTarget , _propagator - > _localDir + QLatin1String ( " Shared " ) ) ) {
done ( SyncFileItem : : NormalError , tr ( " This folder must not be renamed. It is renamed back to its original name. " ) ) ;
2013-10-17 12:48:24 +04:00
} else {
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : NormalError , tr ( " This folder must not be renamed. Please name it back to Shared. " ) ) ;
2013-10-17 12:48:24 +04:00
}
2013-10-28 13:47:10 +04:00
return ;
} else {
2013-11-25 19:16:33 +04:00
emit progress ( Progress : : StartRename , _item , 0 , _item . _size ) ;
2013-10-17 12:48:24 +04:00
2013-10-28 13:47:10 +04:00
QScopedPointer < char , QScopedPointerPodDeleter > uri1 ( ne_path_escape ( ( _propagator - > _remoteDir + _item . _file ) . toUtf8 ( ) ) ) ;
QScopedPointer < char , QScopedPointerPodDeleter > uri2 ( ne_path_escape ( ( _propagator - > _remoteDir + _item . _renameTarget ) . toUtf8 ( ) ) ) ;
2013-11-15 15:27:06 +04:00
qDebug ( ) < < " MOVE on Server: " < < uri1 . data ( ) < < " -> " < < uri2 . data ( ) ;
2013-05-03 21:11:00 +04:00
2013-10-28 13:47:10 +04:00
int rc = ne_move ( _propagator - > _session , 1 , uri1 . data ( ) , uri2 . data ( ) ) ;
2014-02-04 18:01:10 +04:00
2014-02-19 20:21:01 +04:00
if ( checkForProblemsWithShared ( tr ( " The file was renamed but is part of a read only share. The original file was restored. " ) ) ) {
2014-02-04 18:01:10 +04:00
return ;
}
2013-10-28 13:47:10 +04:00
if ( updateErrorFromSession ( rc ) ) {
return ;
}
2013-05-03 21:11:00 +04:00
2014-02-06 20:02:10 +04:00
if ( ! updateMTimeAndETag ( uri2 . data ( ) , _item . _modtime ) )
return ;
2013-11-27 19:48:10 +04:00
emit progress ( Progress : : EndRename , _item , _item . _size , _item . _size ) ;
2013-10-28 13:47:10 +04:00
}
2013-05-04 17:32:11 +04:00
2013-10-28 13:47:10 +04:00
_propagator - > _journal - > deleteFileRecord ( _item . _originalFile ) ;
SyncJournalFileRecord record ( _item , _propagator - > _localDir + _item . _renameTarget ) ;
record . _path = _item . _renameTarget ;
2013-11-05 20:50:09 +04:00
2013-10-28 13:47:10 +04:00
_propagator - > _journal - > setFileRecord ( record ) ;
2013-11-21 14:13:58 +04:00
_propagator - > _journal - > commit ( " Remote Rename " ) ;
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : Success ) ;
2013-05-03 21:11:00 +04:00
}
2013-10-28 13:47:10 +04:00
bool PropagateItemJob : : updateErrorFromSession ( int neon_code , ne_request * req , int ignoreHttpCode )
2013-05-04 18:12:51 +04:00
{
if ( neon_code ! = NE_OK ) {
2013-05-05 13:41:31 +04:00
qDebug ( " Neon error code was %d " , neon_code ) ;
}
2013-05-04 18:12:51 +04:00
2013-10-28 13:47:10 +04:00
QString errorString ;
int httpStatusCode = 0 ;
2013-05-04 19:14:25 +04:00
switch ( neon_code ) {
case NE_OK : /* Success, but still the possiblity of problems */
2013-10-04 17:13:36 +04:00
if ( req ) {
2013-05-05 13:41:31 +04:00
const ne_status * status = ne_get_status ( req ) ;
2013-11-20 17:02:02 +04:00
2013-10-04 17:13:36 +04:00
if ( status ) {
2013-10-28 13:47:10 +04:00
if ( status - > klass = = 2 | | status - > code = = ignoreHttpCode ) {
2013-10-04 17:13:36 +04:00
// Everything is ok, no error.
return false ;
2013-05-05 13:41:31 +04:00
}
2013-10-28 13:47:10 +04:00
errorString = QString : : fromUtf8 ( status - > reason_phrase ) ;
httpStatusCode = status - > code ;
2013-11-20 17:02:02 +04:00
_item . _httpErrorCode = httpStatusCode ;
2013-05-05 13:41:31 +04:00
}
2013-10-04 17:55:10 +04:00
} else {
2013-10-28 13:47:10 +04:00
errorString = QString : : fromUtf8 ( ne_get_error ( _propagator - > _session ) ) ;
2013-10-30 20:36:58 +04:00
httpStatusCode = errorString . mid ( 0 , errorString . indexOf ( QChar ( ' ' ) ) ) . toInt ( ) ;
2013-11-20 17:02:02 +04:00
_item . _httpErrorCode = httpStatusCode ;
2013-10-28 13:47:10 +04:00
if ( ( httpStatusCode > = 200 & & httpStatusCode < 300 )
| | ( httpStatusCode ! = 0 & & httpStatusCode = = ignoreHttpCode ) ) {
2013-10-04 17:55:10 +04:00
// No error
return false ;
}
2013-05-04 19:14:25 +04:00
}
2013-10-04 17:13:36 +04:00
// FIXME: classify the error
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : NormalError , errorString ) ;
2013-10-04 17:13:36 +04:00
return true ;
2013-05-04 19:14:25 +04:00
case NE_ERROR : /* Generic error; use ne_get_error(session) for message */
2013-10-30 20:33:06 +04:00
errorString = QString : : fromUtf8 ( ne_get_error ( _propagator - > _session ) ) ;
2013-11-20 17:02:02 +04:00
// Check if we don't need to ignore that error.
httpStatusCode = errorString . mid ( 0 , errorString . indexOf ( QChar ( ' ' ) ) ) . toInt ( ) ;
_item . _httpErrorCode = httpStatusCode ;
2013-11-25 19:37:06 +04:00
qDebug ( ) < < Q_FUNC_INFO < < " NE_ERROR " < < errorString < < httpStatusCode < < ignoreHttpCode ;
if ( ignoreHttpCode & & httpStatusCode = = ignoreHttpCode )
2013-11-20 17:02:02 +04:00
return false ;
2013-10-30 20:33:06 +04:00
done ( SyncFileItem : : NormalError , errorString ) ;
2013-10-04 17:13:36 +04:00
return true ;
2013-05-04 19:14:25 +04:00
case NE_LOOKUP : /* Server or proxy hostname lookup failed */
case NE_AUTH : /* User authentication failed on server */
case NE_PROXYAUTH : /* User authentication failed on proxy */
case NE_CONNECT : /* Could not connect to server */
case NE_TIMEOUT : /* Connection timed out */
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : FatalError , QString : : fromUtf8 ( ne_get_error ( _propagator - > _session ) ) ) ;
return true ;
2013-05-04 19:14:25 +04:00
case NE_FAILED : /* The precondition failed */
case NE_RETRY : /* Retry request (ne_end_request ONLY) */
case NE_REDIRECT : /* See ne_redirect.h */
default :
2013-10-28 13:47:10 +04:00
done ( SyncFileItem : : SoftError , QString : : fromUtf8 ( ne_get_error ( _propagator - > _session ) ) ) ;
2013-10-04 17:13:36 +04:00
return true ;
2013-05-04 19:14:25 +04:00
}
2013-10-28 13:47:10 +04:00
return false ;
2013-05-03 21:11:00 +04:00
}
2013-10-28 13:47:10 +04:00
PropagateItemJob * OwncloudPropagator : : createJob ( const SyncFileItem & item ) {
switch ( item . _instruction ) {
case CSYNC_INSTRUCTION_REMOVE :
if ( item . _dir = = SyncFileItem : : Down ) return new PropagateLocalRemove ( this , item ) ;
else return new PropagateRemoteRemove ( this , item ) ;
case CSYNC_INSTRUCTION_NEW :
if ( item . _isDirectory ) {
if ( item . _dir = = SyncFileItem : : Down ) return new PropagateLocalMkdir ( this , item ) ;
else return new PropagateRemoteMkdir ( this , item ) ;
} //fall trough
case CSYNC_INSTRUCTION_SYNC :
case CSYNC_INSTRUCTION_CONFLICT :
if ( item . _isDirectory ) {
// Should we set the mtime?
return 0 ;
}
2013-10-29 15:07:57 +04:00
if ( item . _dir ! = SyncFileItem : : Up ) return new PropagateDownloadFile ( this , item ) ;
2013-10-28 13:47:10 +04:00
else return new PropagateUploadFile ( this , item ) ;
case CSYNC_INSTRUCTION_RENAME :
2013-10-30 20:36:58 +04:00
if ( item . _dir = = SyncFileItem : : Up ) {
return new PropagateRemoteRename ( this , item ) ;
} else {
return new PropagateLocalRename ( this , item ) ;
}
2013-10-28 13:47:10 +04:00
case CSYNC_INSTRUCTION_IGNORE :
return new PropagateIgnoreJob ( this , item ) ;
default :
return 0 ;
}
return 0 ;
}
2013-08-14 21:59:16 +04:00
2013-10-28 13:47:10 +04:00
void OwncloudPropagator : : start ( const SyncFileItemVector & _syncedItems )
{
2013-10-28 20:00:27 +04:00
/* This builds all the job needed for the propagation.
* Each directories is a PropagateDirectory job , which contains the files in it .
* In order to do that we sort the items by destination . and loop over it . When we enter a
* directory , we can create the directory job and push it on the stack . */
2013-10-28 13:47:10 +04:00
SyncFileItemVector items = _syncedItems ;
std : : sort ( items . begin ( ) , items . end ( ) ) ;
_rootJob . reset ( new PropagateDirectory ( this ) ) ;
2013-10-28 20:00:27 +04:00
QStack < QPair < QString /* directory name */ , PropagateDirectory * /* job */ > > directories ;
2013-10-28 13:47:10 +04:00
directories . push ( qMakePair ( QString ( ) , _rootJob . data ( ) ) ) ;
2013-10-28 20:00:27 +04:00
QVector < PropagatorJob * > directoriesToRemove ;
2013-10-28 13:47:10 +04:00
QString removedDirectory ;
foreach ( const SyncFileItem & item , items ) {
if ( item . _instruction = = CSYNC_INSTRUCTION_REMOVE
& & ! removedDirectory . isEmpty ( ) & & item . _file . startsWith ( removedDirectory ) ) {
//already taken care of. (by the removal of the parent directory)
continue ;
2013-08-14 21:59:16 +04:00
}
2014-02-19 16:06:55 +04:00
while ( ! item . destination ( ) . startsWith ( directories . top ( ) . first ) ) {
2013-10-28 13:47:10 +04:00
directories . pop ( ) ;
2013-08-14 21:59:16 +04:00
}
2013-10-28 13:47:10 +04:00
if ( item . _isDirectory ) {
PropagateDirectory * dir = new PropagateDirectory ( this , item ) ;
dir - > _firstJob . reset ( createJob ( item ) ) ;
if ( item . _instruction = = CSYNC_INSTRUCTION_REMOVE ) {
//We do the removal of directories at the end
directoriesToRemove . append ( dir ) ;
removedDirectory = item . _file + " / " ;
} else {
directories . top ( ) . second - > append ( dir ) ;
2013-08-14 21:59:16 +04:00
}
2014-02-19 16:06:55 +04:00
directories . push ( qMakePair ( item . destination ( ) + " / " , dir ) ) ;
2013-10-28 13:47:10 +04:00
} else if ( PropagateItemJob * current = createJob ( item ) ) {
directories . top ( ) . second - > append ( current ) ;
2013-08-14 21:59:16 +04:00
}
}
2013-10-28 13:47:10 +04:00
foreach ( PropagatorJob * it , directoriesToRemove ) {
_rootJob - > append ( it ) ;
}
connect ( _rootJob . data ( ) , SIGNAL ( completed ( SyncFileItem ) ) , this , SIGNAL ( completed ( SyncFileItem ) ) ) ;
2013-11-25 19:16:33 +04:00
connect ( _rootJob . data ( ) , SIGNAL ( progress ( Progress : : Kind , SyncFileItem , quint64 , quint64 ) ) , this ,
SIGNAL ( progress ( Progress : : Kind , SyncFileItem , quint64 , quint64 ) ) ) ;
2013-10-28 13:47:10 +04:00
connect ( _rootJob . data ( ) , SIGNAL ( finished ( SyncFileItem : : Status ) ) , this , SIGNAL ( finished ( ) ) ) ;
2013-11-21 14:13:58 +04:00
2013-10-31 15:11:56 +04:00
_rootJob - > start ( ) ;
2013-08-14 21:59:16 +04:00
}
2013-05-03 21:11:00 +04:00
2014-01-07 18:42:21 +04:00
void OwncloudPropagator : : overallTransmissionSizeChanged ( qint64 change )
{
emit progressChanged ( change ) ;
}
2014-02-04 18:01:10 +04:00
bool OwncloudPropagator : : isInSharedDirectory ( const QString & file )
{
bool re = false ;
if ( _remoteDir . contains ( " remote.php/webdav/Shared " ) ) {
// The Shared directory is synced as its own sync connection
re = true ;
} else {
2014-02-19 18:23:36 +04:00
if ( file . startsWith ( " Shared/ " ) | | file = = " Shared " ) {
2014-02-04 18:01:10 +04:00
// The whole ownCloud is synced and Shared is always a top dir
re = true ;
}
}
return re ;
}
2013-11-15 20:42:27 +04:00
void PropagateDirectory : : start ( )
{
_current = - 1 ;
2013-11-28 13:00:12 +04:00
_hasError = SyncFileItem : : NoStatus ;
2013-11-15 20:42:27 +04:00
if ( ! _firstJob ) {
proceedNext ( SyncFileItem : : Success ) ;
} else {
startJob ( _firstJob . data ( ) ) ;
}
}
2013-10-28 13:47:10 +04:00
void PropagateDirectory : : proceedNext ( SyncFileItem : : Status status )
{
2014-02-19 16:06:55 +04:00
if ( status = = SyncFileItem : : FatalError | | ( _current = = - 1 & & status ! = SyncFileItem : : Success ) ) {
2013-10-28 13:47:10 +04:00
emit finished ( status ) ;
return ;
2013-11-28 13:00:12 +04:00
} else if ( status = = SyncFileItem : : NormalError | | status = = SyncFileItem : : SoftError ) {
_hasError = status ;
2013-10-28 13:47:10 +04:00
}
_current + + ;
2013-11-28 12:43:39 +04:00
if ( _current < _subJobs . size ( ) & & ! _propagator - > _abortRequested - > fetchAndAddRelaxed ( 0 ) ) {
2013-10-28 13:47:10 +04:00
PropagatorJob * next = _subJobs . at ( _current ) ;
startJob ( next ) ;
} else {
2013-11-28 13:00:12 +04:00
if ( ! _item . isEmpty ( ) & & _hasError = = SyncFileItem : : NoStatus ) {
2013-11-05 20:50:09 +04:00
if ( ! _item . _renameTarget . isEmpty ( ) ) {
_item . _file = _item . _renameTarget ;
}
2014-01-08 15:51:42 +04:00
if ( _item . _should_update_etag & & _item . _instruction ! = CSYNC_INSTRUCTION_REMOVE ) {
2013-11-15 16:53:18 +04:00
SyncJournalFileRecord record ( _item , _propagator - > _localDir + _item . _file ) ;
_propagator - > _journal - > setFileRecord ( record ) ;
}
2013-10-28 13:47:10 +04:00
}
2013-11-28 13:00:12 +04:00
emit finished ( _hasError = = SyncFileItem : : NoStatus ? SyncFileItem : : Success : _hasError ) ;
2013-10-28 13:47:10 +04:00
}
}
2013-05-03 21:11:00 +04:00
2014-02-04 18:01:10 +04:00
2013-05-04 18:12:51 +04:00
}