2012-02-15 12:30:37 +04:00
/*
* Copyright ( C ) by Duncan Mac - Vicar P . < duncan @ kde . org >
* 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-07-11 02:31:24 +04:00
# include "syncengine.h"
# include "account.h"
2013-05-03 21:11:00 +04:00
# include "owncloudpropagator.h"
2017-09-01 19:11:43 +03:00
# include "common/syncjournaldb.h"
# include "common/syncjournalfilerecord.h"
2014-08-11 19:47:16 +04:00
# include "discoveryphase.h"
2013-07-30 13:19:22 +04:00
# include "creds/abstractcredentials.h"
2019-01-21 13:13:17 +03:00
# include "common/syncfilestatus.h"
2018-07-26 10:53:40 +03:00
# include "csync_exclude.h"
2015-10-29 16:10:11 +03:00
# include "filesystem.h"
2021-01-05 13:48:30 +03:00
# include "deletejob.h"
2017-06-28 13:45:54 +03:00
# include "propagatedownload.h"
2017-09-01 19:11:43 +03:00
# include "common/asserts.h"
2019-04-12 13:11:43 +03:00
# include "configfile.h"
2018-07-10 11:21:45 +03:00
# include "discovery.h"
2018-08-15 11:46:16 +03:00
# include "common/vfs.h"
2012-05-21 18:48:49 +04:00
2012-12-06 19:24:53 +04:00
# ifdef Q_OS_WIN
# include <windows.h>
# else
# include <unistd.h>
# endif
2014-10-09 13:03:07 +04:00
# include <climits>
2020-08-13 14:00:56 +03:00
# include <cassert>
2019-02-08 11:57:11 +03:00
# include <chrono>
2013-04-04 19:14:38 +04:00
2015-06-15 18:38:12 +03:00
# include <QCoreApplication>
2013-01-14 15:13:29 +04:00
# include <QSslSocket>
2012-02-15 12:30:37 +04:00
# include <QDir>
2017-05-09 15:24:11 +03:00
# include <QLoggingCategory>
2012-02-15 12:30:37 +04:00
# include <QMutexLocker>
# include <QThread>
# include <QStringList>
# include <QTextStream>
2012-02-28 19:49:13 +04:00
# include <QTime>
2012-12-11 15:49:48 +04:00
# include <QUrl>
2012-12-13 22:52:07 +04:00
# include <QSslCertificate>
2014-07-22 20:07:02 +04:00
# include <QProcess>
2014-08-15 17:00:10 +04:00
# include <QElapsedTimer>
2015-01-05 15:54:31 +03:00
# include <qtextcodec.h>
2012-08-26 13:47:45 +04:00
2014-11-10 00:34:07 +03:00
namespace OCC {
2012-02-15 12:30:37 +04:00
2017-12-28 22:33:10 +03:00
Q_LOGGING_CATEGORY ( lcEngine , " nextcloud.sync.engine " , QtInfoMsg )
2017-05-09 15:24:11 +03:00
2016-03-09 19:53:18 +03:00
bool SyncEngine : : s_anySyncRunning = false ;
2012-03-21 21:03:49 +04:00
2019-02-08 11:57:11 +03:00
/** When the client touches a file, block change notifications for this duration (ms)
*
* On Linux and Windows the file watcher can ' t distinguish a change that originates
* from the client ( like a download during a sync operation ) and an external change .
* To work around that , all files the client touches are recorded and file change
* notifications for these are blocked for some time . This value controls for how
* long .
*
* Reasons this delay can ' t be very small :
* - it takes time for the change notification to arrive and to be processed by the client
* - some time could pass between the client recording that a file will be touched
* and its filesystem operation finishing , triggering the notification
*/
static const std : : chrono : : milliseconds s_touchedFilesMaxAgeMs ( 3 * 1000 ) ;
// doc in header
std : : chrono : : milliseconds SyncEngine : : minimumFileAgeForUpload ( 2000 ) ;
2015-11-20 17:14:22 +03:00
2016-02-10 23:55:16 +03:00
SyncEngine : : SyncEngine ( AccountPtr account , const QString & localPath ,
2016-11-15 20:47:04 +03:00
const QString & remotePath , OCC : : SyncJournalDb * journal )
2014-12-18 14:09:48 +03:00
: _account ( account )
2014-06-20 15:25:39 +04:00
, _needsUpdate ( false )
2016-03-09 19:53:18 +03:00
, _syncRunning ( false )
2014-06-20 15:25:39 +04:00
, _localPath ( localPath )
, _remotePath ( remotePath )
, _journal ( journal )
2015-01-30 15:36:20 +03:00
, _progressInfo ( new ProgressInfo )
2014-07-15 13:22:16 +04:00
, _hasNoneFiles ( false )
, _hasRemoveFile ( false )
2014-06-20 15:25:39 +04:00
, _uploadLimit ( 0 )
2014-06-23 14:48:34 +04:00
, _downloadLimit ( 0 )
2016-12-06 15:18:59 +03:00
, _anotherSyncNeeded ( NoFollowUpSync )
2012-02-15 12:30:37 +04:00
{
2013-05-16 15:54:22 +04:00
qRegisterMetaType < SyncFileItem > ( " SyncFileItem " ) ;
2017-01-25 13:12:38 +03:00
qRegisterMetaType < SyncFileItemPtr > ( " SyncFileItemPtr " ) ;
2013-10-28 13:47:10 +04:00
qRegisterMetaType < SyncFileItem : : Status > ( " SyncFileItem::Status " ) ;
2016-08-04 15:59:46 +03:00
qRegisterMetaType < SyncFileStatus > ( " SyncFileStatus " ) ;
2016-12-20 13:43:39 +03:00
qRegisterMetaType < SyncFileItemVector > ( " SyncFileItemVector " ) ;
qRegisterMetaType < SyncFileItem : : Direction > ( " SyncFileItem::Direction " ) ;
2014-02-05 23:18:03 +04:00
2016-08-04 15:59:46 +03:00
// Everything in the SyncEngine expects a trailing slash for the localPath.
2017-02-07 15:52:15 +03:00
ASSERT ( localPath . endsWith ( QLatin1Char ( ' / ' ) ) ) ;
2016-02-10 23:55:16 +03:00
2019-08-09 01:30:49 +03:00
_excludedFiles . reset ( new ExcludedFiles ( localPath ) ) ;
2017-11-29 14:04:01 +03:00
2016-03-17 14:26:44 +03:00
_syncFileStatusTracker . reset ( new SyncFileStatusTracker ( this ) ) ;
2016-02-10 23:55:16 +03:00
2016-06-09 13:07:18 +03:00
_clearTouchedFilesTimer . setSingleShot ( true ) ;
_clearTouchedFilesTimer . setInterval ( 30 * 1000 ) ;
2017-09-20 11:14:48 +03:00
connect ( & _clearTouchedFilesTimer , & QTimer : : timeout , this , & SyncEngine : : slotClearTouchedFiles ) ;
2012-02-15 12:30:37 +04:00
}
2014-03-17 14:34:51 +04:00
SyncEngine : : ~ SyncEngine ( )
2012-02-15 12:30:37 +04:00
{
2016-03-09 19:53:18 +03:00
abort ( ) ;
2016-06-15 18:01:00 +03:00
_excludedFiles . reset ( ) ;
2012-02-15 12:30:37 +04:00
}
2016-05-25 16:01:26 +03:00
/**
* Check if the item is in the blacklist .
* If it should not be sync ' ed because of the blacklist , update the item with the error instruction
* and proper error message , and return true .
* If the item is not in the blacklist , or the blacklist is stale , return false .
*/
2015-04-15 16:19:11 +03:00
bool SyncEngine : : checkErrorBlacklisting ( SyncFileItem & item )
2013-11-20 16:44:01 +04:00
{
if ( ! _journal ) {
2017-03-30 14:46:20 +03:00
qCCritical ( lcEngine ) < < " Journal is undefined! " ;
2013-11-20 16:44:01 +04:00
return false ;
}
2015-04-15 16:19:11 +03:00
SyncJournalErrorBlacklistRecord entry = _journal - > errorBlacklistEntry ( item . _file ) ;
item . _hasBlacklistEntry = false ;
2013-11-20 16:44:01 +04:00
2014-10-09 16:49:51 +04:00
if ( ! entry . isValid ( ) ) {
return false ;
}
2013-11-29 19:15:26 +04:00
2015-04-15 16:19:11 +03:00
item . _hasBlacklistEntry = true ;
2013-11-20 16:44:01 +04:00
2014-10-09 16:49:51 +04:00
// If duration has expired, it's not blacklisted anymore
2017-09-25 12:49:11 +03:00
time_t now = Utility : : qDateTimeToTime_t ( QDateTime : : currentDateTimeUtc ( ) ) ;
2017-03-16 11:27:17 +03:00
if ( now > = entry . _lastTryTime + entry . _ignoreDuration ) {
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < " blacklist entry for " < < item . _file < < " has expired! " ;
2014-10-09 16:49:51 +04:00
return false ;
}
2013-11-29 19:15:26 +04:00
2014-10-09 16:49:51 +04:00
// If the file has changed locally or on the server, the blacklist
// entry no longer applies
2015-04-15 16:19:11 +03:00
if ( item . _direction = = SyncFileItem : : Up ) { // check the modtime
if ( item . _modtime = = 0 | | entry . _lastTryModtime = = 0 ) {
2014-10-09 16:49:51 +04:00
return false ;
2015-04-15 16:19:11 +03:00
} else if ( item . _modtime ! = entry . _lastTryModtime ) {
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < item . _file < < " is blacklisted, but has changed mtime! " ;
2014-10-09 16:49:51 +04:00
return false ;
2016-05-25 16:01:26 +03:00
} else if ( item . _renameTarget ! = entry . _renameTarget ) {
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < item . _file < < " is blacklisted, but rename target changed from " < < entry . _renameTarget ;
2016-05-25 16:01:26 +03:00
return false ;
2014-10-09 16:49:51 +04:00
}
2015-04-15 16:19:11 +03:00
} else if ( item . _direction = = SyncFileItem : : Down ) {
2014-10-09 16:49:51 +04:00
// download, check the etag.
2015-04-15 16:19:11 +03:00
if ( item . _etag . isEmpty ( ) | | entry . _lastTryEtag . isEmpty ( ) ) {
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < item . _file < < " one ETag is empty, no blacklisting " ;
2014-10-09 16:49:51 +04:00
return false ;
2015-04-15 16:19:11 +03:00
} else if ( item . _etag ! = entry . _lastTryEtag ) {
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < item . _file < < " is blacklisted, but has changed etag! " ;
2014-10-09 16:49:51 +04:00
return false ;
2013-11-20 16:44:01 +04:00
}
}
2020-08-18 22:46:30 +03:00
qint64 waitSeconds = entry . _lastTryTime + entry . _ignoreDuration - now ;
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < " Item is on blacklist: " < < entry . _file
2014-10-09 16:49:51 +04:00
< < " retries: " < < entry . _retryCount
2017-06-27 15:17:26 +03:00
< < " for another " < < waitSeconds < < " s " ;
// We need to indicate that we skip this file due to blacklisting
// for reporting and for making sure we don't update the blacklist
// entry yet.
// Classification is this _instruction and _status
item . _instruction = CSYNC_INSTRUCTION_IGNORE ;
item . _status = SyncFileItem : : BlacklistedError ;
auto waitSecondsStr = Utility : : durationToDescriptiveString1 ( 1000 * waitSeconds ) ;
item . _errorString = tr ( " %1 (skipped due to earlier error, trying again in %2) " ) . arg ( entry . _errorString , waitSecondsStr ) ;
2014-10-09 16:49:51 +04:00
2017-07-07 16:11:00 +03:00
if ( entry . _errorCategory = = SyncJournalErrorBlacklistRecord : : InsufficientRemoteStorage ) {
slotInsufficientRemoteStorage ( ) ;
}
2014-10-09 16:49:51 +04:00
return true ;
2013-11-20 16:44:01 +04:00
}
2020-04-22 16:23:18 +03:00
static bool isFileTransferInstruction ( SyncInstructions instruction )
2018-02-23 14:13:42 +03:00
{
return instruction = = CSYNC_INSTRUCTION_CONFLICT
| | instruction = = CSYNC_INSTRUCTION_NEW
| | instruction = = CSYNC_INSTRUCTION_SYNC
| | instruction = = CSYNC_INSTRUCTION_TYPE_CHANGE ;
}
2017-01-25 13:28:18 +03:00
void SyncEngine : : deleteStaleDownloadInfos ( const SyncFileItemVector & syncItems )
2014-09-03 14:11:03 +04:00
{
// Find all downloadinfo paths that we want to preserve.
QSet < QString > download_file_paths ;
2017-01-25 13:28:18 +03:00
foreach ( const SyncFileItemPtr & it , syncItems ) {
2015-04-15 16:19:11 +03:00
if ( it - > _direction = = SyncFileItem : : Down
2018-02-23 14:13:42 +03:00
& & it - > _type = = ItemTypeFile
& & isFileTransferInstruction ( it - > _instruction ) ) {
2015-04-15 16:19:11 +03:00
download_file_paths . insert ( it - > _file ) ;
2014-09-03 14:11:03 +04:00
}
}
// Delete from journal and from filesystem.
const QVector < SyncJournalDb : : DownloadInfo > deleted_infos =
_journal - > getAndDeleteStaleDownloadInfos ( download_file_paths ) ;
foreach ( const SyncJournalDb : : DownloadInfo & deleted_info , deleted_infos ) {
2020-09-22 12:47:40 +03:00
const QString tmppath = _propagator - > fullLocalPath ( deleted_info . _tmpfile ) ;
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < " Deleting stale temporary file: " < < tmppath ;
2016-01-05 13:58:18 +03:00
FileSystem : : remove ( tmppath ) ;
2014-09-03 14:11:03 +04:00
}
}
2017-01-25 13:28:18 +03:00
void SyncEngine : : deleteStaleUploadInfos ( const SyncFileItemVector & syncItems )
2014-09-03 14:11:03 +04:00
{
// Find all blacklisted paths that we want to preserve.
QSet < QString > upload_file_paths ;
2017-01-25 13:28:18 +03:00
foreach ( const SyncFileItemPtr & it , syncItems ) {
2015-04-15 16:19:11 +03:00
if ( it - > _direction = = SyncFileItem : : Up
2018-02-23 14:13:42 +03:00
& & it - > _type = = ItemTypeFile
& & isFileTransferInstruction ( it - > _instruction ) ) {
2015-04-15 16:19:11 +03:00
upload_file_paths . insert ( it - > _file ) ;
2014-09-03 14:11:03 +04:00
}
}
// Delete from journal.
2017-01-20 15:59:13 +03:00
auto ids = _journal - > deleteStaleUploadInfos ( upload_file_paths ) ;
// Delete the stales chunk on the server.
if ( account ( ) - > capabilities ( ) . chunkingNg ( ) ) {
foreach ( uint transferId , ids ) {
2018-04-04 18:33:14 +03:00
if ( ! transferId )
continue ; // Was not a chunked upload
2017-01-20 15:59:13 +03:00
QUrl url = Utility : : concatUrlPath ( account ( ) - > url ( ) , QLatin1String ( " remote.php/dav/uploads/ " ) + account ( ) - > davUser ( ) + QLatin1Char ( ' / ' ) + QString : : number ( transferId ) ) ;
( new DeleteJob ( account ( ) , url , this ) ) - > start ( ) ;
}
}
2014-09-03 14:11:03 +04:00
}
2017-01-25 13:28:18 +03:00
void SyncEngine : : deleteStaleErrorBlacklistEntries ( const SyncFileItemVector & syncItems )
2014-09-03 14:11:03 +04:00
{
// Find all blacklisted paths that we want to preserve.
QSet < QString > blacklist_file_paths ;
2017-01-25 13:28:18 +03:00
foreach ( const SyncFileItemPtr & it , syncItems ) {
2015-04-15 16:19:11 +03:00
if ( it - > _hasBlacklistEntry )
blacklist_file_paths . insert ( it - > _file ) ;
2014-09-03 14:11:03 +04:00
}
// Delete from journal.
2015-01-16 12:17:19 +03:00
_journal - > deleteStaleErrorBlacklistEntries ( blacklist_file_paths ) ;
2014-09-03 14:11:03 +04:00
}
2020-04-19 18:11:38 +03:00
# if (QT_VERSION < 0x050600)
template < typename T >
constexpr typename std : : add_const < T > : : type & qAsConst ( T & t ) noexcept { return t ; }
# endif
2017-12-02 13:40:43 +03:00
void SyncEngine : : conflictRecordMaintenance ( )
{
// Remove stale conflict entries from the database
// by checking which files still exist and removing the
// missing ones.
2020-03-21 03:24:06 +03:00
const auto conflictRecordPaths = _journal - > conflictRecordPaths ( ) ;
2017-12-02 13:40:43 +03:00
for ( const auto & path : conflictRecordPaths ) {
2020-09-22 12:47:40 +03:00
auto fsPath = _propagator - > fullLocalPath ( QString : : fromUtf8 ( path ) ) ;
2017-12-02 13:40:43 +03:00
if ( ! QFileInfo ( fsPath ) . exists ( ) ) {
_journal - > deleteConflictRecord ( path ) ;
}
}
// Did the sync see any conflict files that don't yet have records?
// If so, add them now.
//
// This happens when the conflicts table is new or when conflict files
// are downlaoded but the server doesn't send conflict headers.
2019-10-14 14:19:41 +03:00
for ( const auto & path : qAsConst ( _seenConflictFiles ) ) {
ASSERT ( Utility : : isConflictFile ( path ) ) ;
2017-12-02 13:40:43 +03:00
auto bapath = path . toUtf8 ( ) ;
if ( ! conflictRecordPaths . contains ( bapath ) ) {
ConflictRecord record ;
record . path = bapath ;
2018-08-14 14:35:04 +03:00
auto basePath = Utility : : conflictFileBaseNameFromPattern ( bapath ) ;
2018-08-21 12:24:45 +03:00
record . initialBasePath = basePath ;
2017-12-02 13:40:43 +03:00
// Determine fileid of target file
SyncJournalFileRecord baseRecord ;
if ( _journal - > getFileRecord ( basePath , & baseRecord ) & & baseRecord . isValid ( ) ) {
record . baseFileId = baseRecord . _fileId ;
}
_journal - > setConflictRecord ( record ) ;
}
}
}
2018-07-16 17:31:10 +03:00
void OCC : : SyncEngine : : slotItemDiscovered ( const OCC : : SyncFileItemPtr & item )
{
2019-10-14 14:19:41 +03:00
if ( Utility : : isConflictFile ( item - > _file ) )
_seenConflictFiles . insert ( item - > _file ) ;
2018-07-16 17:31:10 +03:00
if ( item - > _instruction = = CSYNC_INSTRUCTION_UPDATE_METADATA & & ! item - > isDirectory ( ) ) {
// For directories, metadata-only updates will be done after all their files are propagated.
// Update the database now already: New remote fileid or Etag or RemotePerm
// Or for files that were detected as "resolved conflict".
// Or a local inode/mtime change
// In case of "resolved conflict": there should have been a conflict because they
// both were new, or both had their local mtime or remote etag modified, but the
// size and mtime is the same on the server. This typically happens when the
// database is removed. Nothing will be done for those files, but we still need
// to update the database.
// This metadata update *could* be a propagation job of its own, but since it's
// quick to do and we don't want to create a potentially large number of
// mini-jobs later on, we just update metadata right now.
if ( item - > _direction = = SyncFileItem : : Down ) {
QString filePath = _localPath + item - > _file ;
// If the 'W' remote permission changed, update the local filesystem
SyncJournalFileRecord prev ;
if ( _journal - > getFileRecord ( item - > _file , & prev )
& & prev . isValid ( )
& & prev . _remotePerm . hasPermission ( RemotePermissions : : CanWrite ) ! = item - > _remotePerm . hasPermission ( RemotePermissions : : CanWrite ) ) {
const bool isReadOnly = ! item - > _remotePerm . isNull ( ) & & ! item - > _remotePerm . hasPermission ( RemotePermissions : : CanWrite ) ;
FileSystem : : setFileReadOnlyWeak ( filePath , isReadOnly ) ;
}
2018-11-06 12:30:33 +03:00
auto rec = item - > toSyncJournalFileRecordWithInode ( filePath ) ;
if ( rec . _checksumHeader . isEmpty ( ) )
rec . _checksumHeader = prev . _checksumHeader ;
rec . _serverHasIgnoredFiles | = prev . _serverHasIgnoredFiles ;
2018-07-16 17:31:10 +03:00
2019-07-23 15:26:46 +03:00
// Ensure it's a placeholder file on disk
if ( item - > _type = = ItemTypeFile ) {
2020-12-30 14:53:03 +03:00
const auto result = _syncOptions . _vfs - > convertToPlaceholder ( filePath , * item ) ;
if ( ! result ) {
item - > _instruction = CSYNC_INSTRUCTION_ERROR ;
2021-01-22 23:26:56 +03:00
item - > _errorString = tr ( " Could not update file: %1 " ) . arg ( result . error ( ) ) ;
2020-12-30 14:53:03 +03:00
return ;
}
2019-07-23 15:26:46 +03:00
}
2018-11-12 14:24:08 +03:00
// Update on-disk virtual file metadata
2019-07-23 15:26:46 +03:00
if ( item - > _type = = ItemTypeVirtualFile ) {
2019-10-30 15:16:32 +03:00
auto r = _syncOptions . _vfs - > updateMetadata ( filePath , item - > _modtime , item - > _size , item - > _fileId ) ;
if ( ! r ) {
2018-11-12 14:24:08 +03:00
item - > _instruction = CSYNC_INSTRUCTION_ERROR ;
2019-10-30 15:16:32 +03:00
item - > _errorString = tr ( " Could not update virtual file metadata: %1 " ) . arg ( r . error ( ) ) ;
2018-11-12 14:24:08 +03:00
return ;
}
}
// Updating the db happens on success
_journal - > setFileRecord ( rec ) ;
2018-08-15 11:46:16 +03:00
2018-07-16 17:31:10 +03:00
// This might have changed the shared flag, so we must notify SyncFileStatusTracker for example
emit itemCompleted ( item ) ;
} else {
// Update only outdated data from the disk.
_journal - > updateLocalMetadata ( item - > _file , item - > _modtime , item - > _size , item - > _inode ) ;
}
_hasNoneFiles = true ;
return ;
} else if ( item - > _instruction = = CSYNC_INSTRUCTION_NONE ) {
_hasNoneFiles = true ;
2018-08-06 12:27:11 +03:00
if ( _account - > capabilities ( ) . uploadConflictFiles ( ) & & Utility : : isConflictFile ( item - > _file ) ) {
// For uploaded conflict files, files with no action performed on them should
// be displayed: but we mustn't overwrite the instruction if something happens
// to the file!
item - > _errorString = tr ( " Unresolved conflict. " ) ;
item - > _instruction = CSYNC_INSTRUCTION_IGNORE ;
item - > _status = SyncFileItem : : Conflict ;
}
2018-07-16 17:31:10 +03:00
return ;
2019-10-16 13:12:02 +03:00
} else if ( item - > _instruction = = CSYNC_INSTRUCTION_REMOVE & & ! item - > _isSelectiveSync ) {
2018-07-16 17:31:10 +03:00
_hasRemoveFile = true ;
2019-06-04 13:08:15 +03:00
} else if ( item - > _instruction = = CSYNC_INSTRUCTION_RENAME ) {
_hasNoneFiles = true ; // If a file (or every file) has been renamed, it means not al files where deleted
2018-07-16 17:31:10 +03:00
} else if ( item - > _instruction = = CSYNC_INSTRUCTION_TYPE_CHANGE
| | item - > _instruction = = CSYNC_INSTRUCTION_SYNC ) {
if ( item - > _direction = = SyncFileItem : : Up ) {
// An upload of an existing file means that the file was left unchanged on the server
// This counts as a NONE for detecting if all the files on the server were changed
_hasNoneFiles = true ;
}
}
2018-07-18 13:15:13 +03:00
// check for blacklisting of this item.
// if the item is on blacklist, the instruction was set to ERROR
checkErrorBlacklisting ( * item ) ;
_needsUpdate = true ;
2019-09-06 13:58:56 +03:00
// Insert sorted
auto it = std : : lower_bound ( _syncItems . begin ( ) , _syncItems . end ( ) , item ) ; // the _syncItems is sorted
_syncItems . insert ( it , item ) ;
2018-07-16 17:31:10 +03:00
slotNewItem ( item ) ;
2018-08-06 13:53:51 +03:00
if ( item - > isDirectory ( ) ) {
slotFolderDiscovered ( item - > _etag . isEmpty ( ) , item - > _file ) ;
}
2018-07-16 17:31:10 +03:00
}
2014-03-17 14:34:51 +04:00
void SyncEngine : : startSync ( )
2012-02-15 12:30:37 +04:00
{
2014-07-28 14:12:52 +04:00
if ( _journal - > exists ( ) ) {
QVector < SyncJournalDb : : PollInfo > pollInfos = _journal - > getPollInfos ( ) ;
if ( ! pollInfos . isEmpty ( ) ) {
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < " Finish Poll jobs before starting a sync " ;
2020-05-18 21:54:23 +03:00
auto * job = new CleanupPollsJob ( pollInfos , _account ,
2019-01-21 13:22:40 +03:00
_journal , _localPath , _syncOptions . _vfs , this ) ;
2017-09-20 11:14:48 +03:00
connect ( job , & CleanupPollsJob : : finished , this , & SyncEngine : : startSync ) ;
connect ( job , & CleanupPollsJob : : aborted , this , & SyncEngine : : slotCleanPollsJobAborted ) ;
2014-07-28 14:12:52 +04:00
job - > start ( ) ;
return ;
}
}
2017-02-07 15:52:15 +03:00
if ( s_anySyncRunning | | _syncRunning ) {
ASSERT ( false ) ;
return ;
}
2016-03-09 19:53:18 +03:00
s_anySyncRunning = true ;
2014-05-29 13:34:25 +04:00
_syncRunning = true ;
2016-12-06 15:18:59 +03:00
_anotherSyncNeeded = NoFollowUpSync ;
2016-06-09 13:07:18 +03:00
_clearTouchedFilesTimer . stop ( ) ;
2013-04-04 19:14:38 +04:00
2018-07-16 17:31:10 +03:00
_hasNoneFiles = false ;
_hasRemoveFile = false ;
2019-10-14 14:19:41 +03:00
_seenConflictFiles . clear ( ) ;
2018-07-16 17:31:10 +03:00
2016-05-20 16:07:54 +03:00
_progressInfo - > reset ( ) ;
2014-08-28 13:47:40 +04:00
if ( ! QDir ( _localPath ) . exists ( ) ) {
2016-12-06 15:18:59 +03:00
_anotherSyncNeeded = DelayedFollowUp ;
2014-08-28 13:47:40 +04:00
// No _tr, it should only occur in non-mirall
2018-07-26 10:53:40 +03:00
syncError ( " Unable to find local sync folder. " ) ;
2015-10-29 18:43:30 +03:00
finalize ( false ) ;
2014-08-28 13:47:40 +04:00
return ;
}
2015-09-30 16:07:45 +03:00
// Check free size on disk first.
2015-10-01 12:39:09 +03:00
const qint64 minFree = criticalFreeSpaceLimit ( ) ;
2015-09-30 16:07:45 +03:00
const qint64 freeBytes = Utility : : freeDiskSpace ( _localPath ) ;
if ( freeBytes > = 0 ) {
if ( freeBytes < minFree ) {
2017-09-22 12:06:29 +03:00
qCWarning ( lcEngine ( ) ) < < " Too little space available at " < < _localPath < < " . Have "
< < freeBytes < < " bytes and require at least " < < minFree < < " bytes " ;
2016-12-06 15:18:59 +03:00
_anotherSyncNeeded = DelayedFollowUp ;
2018-07-26 10:53:40 +03:00
syncError ( tr ( " Only %1 are available, need at least %2 to start " ,
2016-01-06 18:50:59 +03:00
" Placeholders are postfixed with file sizes using Utility::octetsToString() " )
2018-07-26 10:53:40 +03:00
. arg (
Utility : : octetsToString ( freeBytes ) ,
Utility : : octetsToString ( minFree ) ) ) ;
2015-10-29 18:43:30 +03:00
finalize ( false ) ;
2015-09-30 16:07:45 +03:00
return ;
2017-09-22 12:06:29 +03:00
} else {
qCInfo ( lcEngine ) < < " There are " < < freeBytes < < " bytes available at " < < _localPath ;
2015-09-30 16:07:45 +03:00
}
} else {
2017-03-30 14:46:20 +03:00
qCWarning ( lcEngine ) < < " Could not determine free space available at " < < _localPath ;
2015-09-30 16:07:45 +03:00
}
2018-07-10 11:21:45 +03:00
_syncItems . clear ( ) ;
2013-02-14 19:25:00 +04:00
_needsUpdate = false ;
2014-01-20 19:29:26 +04:00
2013-10-04 16:44:57 +04:00
if ( ! _journal - > exists ( ) ) {
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < " New sync (no sync journal exists) " ;
2014-10-20 17:51:50 +04:00
} else {
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < " Sync with existing sync journal " ;
2014-10-20 17:51:50 +04:00
}
2017-05-09 15:24:11 +03:00
QString verStr ( " Using Qt " ) ;
2016-07-28 17:30:40 +03:00
verStr . append ( qVersion ( ) ) ;
verStr . append ( " SSL library " ) . append ( QSslSocket : : sslLibraryVersionString ( ) . toUtf8 ( ) . data ( ) ) ;
verStr . append ( " on " ) . append ( Utility : : platformName ( ) ) ;
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < verStr ;
2014-11-12 11:21:05 +03:00
2017-09-14 17:34:14 +03:00
// This creates the DB if it does not exist yet.
2019-04-23 14:38:58 +03:00
if ( ! _journal - > open ( ) ) {
2017-03-30 14:46:20 +03:00
qCWarning ( lcEngine ) < < " No way to create a sync journal! " ;
2018-07-26 10:53:40 +03:00
syncError ( tr ( " Unable to open or create the local sync database. Make sure you have write access in the sync folder. " ) ) ;
2015-10-29 18:43:30 +03:00
finalize ( false ) ;
2014-10-20 17:51:50 +04:00
return ;
// database creation error!
}
2014-10-20 16:25:22 +04:00
2018-02-16 12:33:35 +03:00
// Functionality like selective sync might have set up etag storage
2019-02-13 16:18:54 +03:00
// filtering via schedulePathForRemoteDiscovery(). This *is* the next sync, so
2018-02-16 12:33:35 +03:00
// undo the filter to allow this sync to retrieve and store the correct etags.
_journal - > clearEtagStorageFilter ( ) ;
2017-12-02 13:40:43 +03:00
_excludedFiles - > setExcludeConflictFiles ( ! _account - > capabilities ( ) . uploadConflictFiles ( ) ) ;
2018-02-21 15:22:54 +03:00
_lastLocalDiscoveryStyle = _localDiscoveryStyle ;
2014-10-20 17:51:50 +04:00
2018-11-21 14:23:08 +03:00
if ( _syncOptions . _vfs - > mode ( ) = = Vfs : : WithSuffix & & _syncOptions . _vfs - > fileSuffix ( ) . isEmpty ( ) ) {
syncError ( tr ( " Using virtual files with suffix, but suffix is not set " ) ) ;
finalize ( false ) ;
return ;
2018-01-29 13:16:50 +03:00
}
2020-05-29 16:07:05 +03:00
bool ok = false ;
2016-04-06 16:01:28 +03:00
auto selectiveSyncBlackList = _journal - > getSelectiveSyncList ( SyncJournalDb : : SelectiveSyncBlackList , & ok ) ;
if ( ok ) {
bool usingSelectiveSync = ( ! selectiveSyncBlackList . isEmpty ( ) ) ;
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < ( usingSelectiveSync ? " Using Selective Sync " : " NOT Using Selective Sync " ) ;
2016-04-06 16:01:28 +03:00
} else {
2017-03-30 14:46:20 +03:00
qCWarning ( lcEngine ) < < " Could not retrieve selective sync list from DB " ;
2018-07-26 10:53:40 +03:00
syncError ( tr ( " Unable to read the blacklist from the local database " ) ) ;
2016-04-06 18:20:48 +03:00
finalize ( false ) ;
return ;
2016-04-06 16:01:28 +03:00
}
2015-11-23 13:53:06 +03:00
2014-03-26 21:06:25 +04:00
_stopWatch . start ( ) ;
2017-07-11 13:52:40 +03:00
_progressInfo - > _status = ProgressInfo : : Starting ;
emit transmissionProgress ( * _progressInfo ) ;
2014-02-19 00:41:20 +04:00
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < " #### Discovery start #################################################### " ;
2018-07-10 15:50:32 +03:00
qCInfo ( lcEngine ) < < " Server " < < account ( ) - > serverVersion ( )
< < ( account ( ) - > isHttp2Supported ( ) ? " Using HTTP/2 " : " " ) ;
2017-07-11 13:52:40 +03:00
_progressInfo - > _status = ProgressInfo : : Discovery ;
emit transmissionProgress ( * _progressInfo ) ;
2013-09-24 17:56:03 +04:00
2018-07-11 17:55:00 +03:00
_discoveryPhase . reset ( new DiscoveryPhase ) ;
_discoveryPhase - > _account = _account ;
_discoveryPhase - > _excludes = _excludedFiles . data ( ) ;
_discoveryPhase - > _statedb = _journal ;
_discoveryPhase - > _localDir = _localPath ;
2018-12-18 12:38:39 +03:00
if ( ! _discoveryPhase - > _localDir . endsWith ( ' / ' ) )
_discoveryPhase - > _localDir + = ' / ' ;
2018-07-11 17:55:00 +03:00
_discoveryPhase - > _remoteFolder = _remotePath ;
2018-12-18 12:38:39 +03:00
if ( ! _discoveryPhase - > _remoteFolder . endsWith ( ' / ' ) )
_discoveryPhase - > _remoteFolder + = ' / ' ;
2018-07-11 17:55:00 +03:00
_discoveryPhase - > _syncOptions = _syncOptions ;
2018-10-15 18:02:04 +03:00
_discoveryPhase - > _shouldDiscoverLocaly = [ this ] ( const QString & s ) { return shouldDiscoverLocally ( s ) ; } ;
2019-01-10 12:19:27 +03:00
_discoveryPhase - > setSelectiveSyncBlackList ( selectiveSyncBlackList ) ;
_discoveryPhase - > setSelectiveSyncWhiteList ( _journal - > getSelectiveSyncList ( SyncJournalDb : : SelectiveSyncWhiteList , & ok ) ) ;
2018-07-10 15:50:32 +03:00
if ( ! ok ) {
qCWarning ( lcEngine ) < < " Unable to read selective sync list, aborting. " ;
2018-07-26 10:53:40 +03:00
syncError ( tr ( " Unable to read from the sync journal. " ) ) ;
2018-07-10 15:50:32 +03:00
finalize ( false ) ;
return ;
}
2018-07-10 17:21:45 +03:00
// Check for invalid character in old server version
QString invalidFilenamePattern = _account - > capabilities ( ) . invalidFilenameRegex ( ) ;
if ( invalidFilenamePattern . isNull ( )
& & _account - > serverVersionInt ( ) < Account : : makeServerVersion ( 8 , 1 , 0 ) ) {
// Server versions older than 8.1 don't support some characters in filenames.
// If the capability is not set, default to a pattern that avoids uploading
// files with names that contain these.
// It's important to respect the capability also for older servers -- the
// version check doesn't make sense for custom servers.
2020-12-10 21:52:58 +03:00
invalidFilenamePattern = R " ([ \\ :?* " < > | ] ) " ;
2018-07-10 17:21:45 +03:00
}
2018-10-19 11:51:25 +03:00
if ( ! invalidFilenamePattern . isEmpty ( ) )
_discoveryPhase - > _invalidFilenameRx = QRegExp ( invalidFilenamePattern ) ;
2018-11-29 19:00:16 +03:00
_discoveryPhase - > _serverBlacklistedFiles = _account - > capabilities ( ) . blacklistedFiles ( ) ;
2018-07-11 17:55:00 +03:00
_discoveryPhase - > _ignoreHiddenFiles = ignoreHiddenFiles ( ) ;
2018-07-10 17:21:45 +03:00
2018-08-06 13:41:59 +03:00
connect ( _discoveryPhase . data ( ) , & DiscoveryPhase : : itemDiscovered , this , & SyncEngine : : slotItemDiscovered ) ;
2018-07-11 17:55:00 +03:00
connect ( _discoveryPhase . data ( ) , & DiscoveryPhase : : newBigFolder , this , & SyncEngine : : newBigFolder ) ;
2018-07-16 19:35:35 +03:00
connect ( _discoveryPhase . data ( ) , & DiscoveryPhase : : fatalError , this , [ this ] ( const QString & errorString ) {
2018-07-26 10:53:40 +03:00
syncError ( errorString ) ;
2018-07-16 19:35:35 +03:00
finalize ( false ) ;
} ) ;
2019-01-14 17:44:50 +03:00
connect ( _discoveryPhase . data ( ) , & DiscoveryPhase : : finished , this , & SyncEngine : : slotDiscoveryFinished ) ;
2019-01-22 13:29:03 +03:00
connect ( _discoveryPhase . data ( ) , & DiscoveryPhase : : silentlyExcluded ,
_syncFileStatusTracker . data ( ) , & SyncFileStatusTracker : : slotAddSilentlyExcluded ) ;
2018-07-10 15:50:32 +03:00
2019-01-23 17:12:02 +03:00
auto discoveryJob = new ProcessDirectoryJob (
_discoveryPhase . data ( ) , PinState : : AlwaysLocal , _discoveryPhase . data ( ) ) ;
2018-08-06 13:41:59 +03:00
_discoveryPhase - > startJob ( discoveryJob ) ;
2018-10-09 16:12:02 +03:00
connect ( discoveryJob , & ProcessDirectoryJob : : etag , this , & SyncEngine : : slotRootEtagReceived ) ;
2014-02-05 23:18:03 +04:00
}
2018-02-21 11:39:55 +03:00
void SyncEngine : : slotFolderDiscovered ( bool local , const QString & folder )
2017-07-11 13:52:40 +03:00
{
2018-08-06 13:53:51 +03:00
// Don't wanna overload the UI
2020-12-10 21:52:58 +03:00
if ( ! _lastUpdateProgressCallbackCall . isValid ( ) | | _lastUpdateProgressCallbackCall . elapsed ( ) > = 200 ) {
_lastUpdateProgressCallbackCall . start ( ) ; // first call or enough elapsed time
2018-08-06 13:53:51 +03:00
} else {
2020-12-10 21:52:58 +03:00
return ;
2018-08-06 13:53:51 +03:00
}
2018-02-21 11:39:55 +03:00
if ( local ) {
_progressInfo - > _currentDiscoveredLocalFolder = folder ;
_progressInfo - > _currentDiscoveredRemoteFolder . clear ( ) ;
} else {
_progressInfo - > _currentDiscoveredRemoteFolder = folder ;
_progressInfo - > _currentDiscoveredLocalFolder . clear ( ) ;
}
2017-07-11 13:52:40 +03:00
emit transmissionProgress ( * _progressInfo ) ;
}
2020-12-03 18:27:43 +03:00
void SyncEngine : : slotRootEtagReceived ( const QString & e , const QDateTime & time )
2015-10-29 17:01:29 +03:00
{
2015-01-23 17:30:00 +03:00
if ( _remoteRootEtag . isEmpty ( ) ) {
2017-03-30 14:46:20 +03:00
qCDebug ( lcEngine ) < < " Root etag: " < < e ;
2015-01-23 17:30:00 +03:00
_remoteRootEtag = e ;
2020-12-03 18:27:43 +03:00
emit rootEtag ( _remoteRootEtag , time ) ;
2015-01-23 17:30:00 +03:00
}
}
2017-12-02 13:40:43 +03:00
void SyncEngine : : slotNewItem ( const SyncFileItemPtr & item )
{
_progressInfo - > adjustTotalsForFile ( * item ) ;
}
2019-01-14 17:44:50 +03:00
void SyncEngine : : slotDiscoveryFinished ( )
2018-07-16 17:31:10 +03:00
{
2018-11-05 15:25:31 +03:00
if ( ! _discoveryPhase ) {
// There was an error that was already taken care of
return ;
}
2017-03-30 14:46:20 +03:00
qCInfo ( lcEngine ) < < " #### Discovery end #################################################### " < < _stopWatch . addLapTime ( QLatin1String ( " Discovery Finished " ) ) < < " ms " ;
2012-02-15 12:30:37 +04:00
2014-10-20 18:50:55 +04:00
// Sanity check
2019-04-23 14:38:58 +03:00
if ( ! _journal - > open ( ) ) {
2017-03-30 14:46:20 +03:00
qCWarning ( lcEngine ) < < " Bailing out, DB failure " ;
2018-07-26 10:53:40 +03:00
syncError ( tr ( " Cannot open the sync journal " ) ) ;
2015-10-29 18:43:30 +03:00
finalize ( false ) ;
2014-10-20 18:50:55 +04:00
return ;
} else {
// Commits a possibly existing (should not though) transaction and starts a new one for the propagate phase
2014-10-20 19:20:58 +04:00
_journal - > commitIfNeededAndStartNewTransaction ( " Post discovery " ) ;
2014-10-20 18:50:55 +04:00
}
2018-02-21 11:39:55 +03:00
_progressInfo - > _currentDiscoveredRemoteFolder . clear ( ) ;
_progressInfo - > _currentDiscoveredLocalFolder . clear ( ) ;
2017-07-11 13:52:40 +03:00
_progressInfo - > _status = ProgressInfo : : Reconcile ;
emit transmissionProgress ( * _progressInfo ) ;
2018-07-16 17:31:10 +03:00
// qCInfo(lcEngine) << "Permissions of the root folder: " << _csync_ctx->remote.root_perms.toString();
2020-10-15 17:05:51 +03:00
auto finish = [ this ] {
auto databaseFingerprint = _journal - > dataFingerprint ( ) ;
// If databaseFingerprint is empty, this means that there was no information in the database
// (for example, upgrading from a previous version, or first sync, or server not supporting fingerprint)
if ( ! databaseFingerprint . isEmpty ( ) & & _discoveryPhase
& & _discoveryPhase - > _dataFingerprint ! = databaseFingerprint ) {
qCInfo ( lcEngine ) < < " data fingerprint changed, assume restore from backup " < < databaseFingerprint < < _discoveryPhase - > _dataFingerprint ;
restoreOldFiles ( _syncItems ) ;
2018-08-14 10:19:31 +03:00
}
2016-08-02 11:30:49 +03:00
2020-10-15 17:05:51 +03:00
if ( _discoveryPhase - > _anotherSyncNeeded & & _anotherSyncNeeded = = NoFollowUpSync ) {
_anotherSyncNeeded = ImmediateFollowUp ;
}
2018-08-21 20:54:48 +03:00
2020-10-15 17:05:51 +03:00
Q_ASSERT ( std : : is_sorted ( _syncItems . begin ( ) , _syncItems . end ( ) ) ) ;
2019-08-28 15:20:40 +03:00
2020-10-15 17:05:51 +03:00
qCInfo ( lcEngine ) < < " #### Reconcile (aboutToPropagate) #################################################### " < < _stopWatch . addLapTime ( QStringLiteral ( " Reconcile (aboutToPropagate) " ) ) < < " ms " ;
2019-09-06 13:58:56 +03:00
2020-10-15 17:05:51 +03:00
_localDiscoveryPaths . clear ( ) ;
2019-09-28 10:17:12 +03:00
2020-10-15 17:05:51 +03:00
// To announce the beginning of the sync
emit aboutToPropagate ( _syncItems ) ;
2017-08-24 17:48:40 +03:00
2020-10-15 17:05:51 +03:00
qCInfo ( lcEngine ) < < " #### Reconcile (aboutToPropagate OK) #################################################### " < < _stopWatch . addLapTime ( QStringLiteral ( " Reconcile (aboutToPropagate OK) " ) ) < < " ms " ;
2017-07-11 13:52:40 +03:00
2020-10-15 17:05:51 +03:00
// it's important to do this before ProgressInfo::start(), to announce start of new sync
_progressInfo - > _status = ProgressInfo : : Propagation ;
emit transmissionProgress ( * _progressInfo ) ;
_progressInfo - > startEstimateUpdates ( ) ;
2019-09-06 13:58:56 +03:00
2020-10-15 17:05:51 +03:00
// post update phase script: allow to tweak stuff by a custom script in debug mode.
if ( ! qEnvironmentVariableIsEmpty ( " OWNCLOUD_POST_UPDATE_SCRIPT " ) ) {
# ifndef NDEBUG
const QString script = qEnvironmentVariable ( " OWNCLOUD_POST_UPDATE_SCRIPT " ) ;
2014-03-14 21:29:23 +04:00
2020-10-15 17:05:51 +03:00
qCDebug ( lcEngine ) < < " Post Update Script: " < < script ;
QProcess : : execute ( script ) ;
# else
qCWarning ( lcEngine ) < < " **** Attention: POST_UPDATE_SCRIPT installed, but not executed because compiled with NDEBUG " ;
# endif
}
2014-07-22 20:07:02 +04:00
2020-10-15 17:05:51 +03:00
// do a database commit
_journal - > commit ( QStringLiteral ( " post treewalk " ) ) ;
_propagator = QSharedPointer < OwncloudPropagator > (
new OwncloudPropagator ( _account , _localPath , _remotePath , _journal ) ) ;
_propagator - > setSyncOptions ( _syncOptions ) ;
connect ( _propagator . data ( ) , & OwncloudPropagator : : itemCompleted ,
this , & SyncEngine : : slotItemCompleted ) ;
connect ( _propagator . data ( ) , & OwncloudPropagator : : progress ,
this , & SyncEngine : : slotProgress ) ;
connect ( _propagator . data ( ) , & OwncloudPropagator : : finished , this , & SyncEngine : : slotPropagationFinished , Qt : : QueuedConnection ) ;
connect ( _propagator . data ( ) , & OwncloudPropagator : : seenLockedFile , this , & SyncEngine : : seenLockedFile ) ;
connect ( _propagator . data ( ) , & OwncloudPropagator : : touchedFile , this , & SyncEngine : : slotAddTouchedFile ) ;
connect ( _propagator . data ( ) , & OwncloudPropagator : : insufficientLocalStorage , this , & SyncEngine : : slotInsufficientLocalStorage ) ;
connect ( _propagator . data ( ) , & OwncloudPropagator : : insufficientRemoteStorage , this , & SyncEngine : : slotInsufficientRemoteStorage ) ;
connect ( _propagator . data ( ) , & OwncloudPropagator : : newItem , this , & SyncEngine : : slotNewItem ) ;
// apply the network limits to the propagator
setNetworkLimits ( _uploadLimit , _downloadLimit ) ;
deleteStaleDownloadInfos ( _syncItems ) ;
deleteStaleUploadInfos ( _syncItems ) ;
deleteStaleErrorBlacklistEntries ( _syncItems ) ;
_journal - > commit ( QStringLiteral ( " post stale entry removal " ) ) ;
// Emit the started signal only after the propagator has been set up.
if ( _needsUpdate )
emit ( started ( ) ) ;
_propagator - > start ( _syncItems ) ;
_syncItems . clear ( ) ;
qCInfo ( lcEngine ) < < " #### Post-Reconcile end #################################################### " < < _stopWatch . addLapTime ( QStringLiteral ( " Post-Reconcile Finished " ) ) < < " ms " ;
} ;
if ( ! _hasNoneFiles & & _hasRemoveFile ) {
qCInfo ( lcEngine ) < < " All the files are going to be changed, asking the user " ;
int side = 0 ; // > 0 means more deleted on the server. < 0 means more deleted on the client
foreach ( const auto & it , _syncItems ) {
if ( it - > _instruction = = CSYNC_INSTRUCTION_REMOVE ) {
side + = it - > _direction = = SyncFileItem : : Down ? 1 : - 1 ;
}
}
2020-10-19 17:27:45 +03:00
QPointer < QObject > guard = new QObject ( ) ;
2020-12-18 14:48:08 +03:00
QPointer < QObject > self = this ;
auto callback = [ this , self , finish , guard ] ( bool cancel ) - > void {
2020-10-19 17:27:45 +03:00
// use a guard to ensure its only called once...
2020-12-18 14:48:08 +03:00
// qpointer to self to ensure we still exist
if ( ! guard | | ! self ) {
2020-10-19 17:27:45 +03:00
return ;
}
guard - > deleteLater ( ) ;
2020-10-15 17:05:51 +03:00
if ( cancel ) {
qCInfo ( lcEngine ) < < " User aborted sync " ;
finalize ( false ) ;
return ;
} else {
finish ( ) ;
}
2020-10-19 17:27:45 +03:00
} ;
emit aboutToRemoveAllFiles ( side > = 0 ? SyncFileItem : : Down : SyncFileItem : : Up , callback ) ;
2020-10-15 17:05:51 +03:00
return ;
2014-10-15 18:43:58 +04:00
}
2020-10-15 17:05:51 +03:00
finish ( ) ;
2014-01-31 20:29:50 +04:00
}
2014-07-29 17:51:22 +04:00
void SyncEngine : : slotCleanPollsJobAborted ( const QString & error )
{
2018-07-26 10:53:40 +03:00
syncError ( error ) ;
2015-10-29 18:43:30 +03:00
finalize ( false ) ;
2014-07-29 17:51:22 +04:00
}
2014-06-07 13:49:41 +04:00
void SyncEngine : : setNetworkLimits ( int upload , int download )
2014-01-31 20:29:50 +04:00
{
2014-06-07 13:49:41 +04:00
_uploadLimit = upload ;
_downloadLimit = download ;
2014-01-31 20:29:50 +04:00
if ( ! _propagator )
return ;
2014-06-07 13:49:41 +04:00
_propagator - > _uploadLimit = upload ;
_propagator - > _downloadLimit = download ;
2013-08-14 21:59:16 +04:00
2020-02-05 14:57:09 +03:00
if ( upload ! = 0 | | download ! = 0 ) {
qCInfo ( lcEngine ) < < " Network Limits (down/up) " < < upload < < download ;
2014-04-07 17:10:44 +04:00
}
2013-05-16 15:54:22 +04:00
}
2013-05-07 19:47:29 +04:00
2017-01-25 13:12:38 +03:00
void SyncEngine : : slotItemCompleted ( const SyncFileItemPtr & item )
2013-05-16 15:54:22 +04:00
{
2017-01-25 13:12:38 +03:00
_progressInfo - > setProgressComplete ( * item ) ;
2014-03-14 16:03:16 +04:00
2015-01-30 15:36:20 +03:00
emit transmissionProgress ( * _progressInfo ) ;
2017-01-25 16:09:44 +03:00
emit itemCompleted ( item ) ;
2013-05-16 15:54:22 +04:00
}
2013-05-06 18:59:11 +04:00
2019-01-14 17:44:50 +03:00
void SyncEngine : : slotPropagationFinished ( bool success )
2013-05-16 15:54:22 +04:00
{
2016-12-06 15:18:59 +03:00
if ( _propagator - > _anotherSyncNeeded & & _anotherSyncNeeded = = NoFollowUpSync ) {
_anotherSyncNeeded = ImmediateFollowUp ;
}
2014-09-10 19:25:13 +04:00
2021-04-05 16:04:00 +03:00
// TODO: Remove this when the file restoration problem is fixed for a user
bool shouldStartSyncAgain = false ;
const auto checkAndOverrideSetDataFingerprint = [ this , & shouldStartSyncAgain ] {
const int dataFingerprintOverrideThreshold = 9 ;
const QString dataFingerprintOverrideHostHash = QStringLiteral ( " 63debc9ef6d217649ea70632ca573a1db7a237ba61c48cdd2bf797f7060233db " ) ;
const auto accountHost = account ( ) - > url ( ) . host ( ) ;
const auto accountDisplayName = account ( ) - > displayName ( ) ;
if ( _dataFingerprintSetFailCount > = 0 ) {
qCWarning ( lcEngine ) < < " setDataFingerprint has failed for account " < < accountDisplayName < < " on host " < < accountHost < < " due to sync errors. Checking the possibility for override... " ;
if ( _dataFingerprintSetFailCount > 0 ) {
if ( _dataFingerprintSetFailCount > = dataFingerprintOverrideThreshold ) {
qCWarning ( lcEngine ) < < " All sync attempts failed for account " < < accountDisplayName < < " on host " < < accountHost < < " setting the dataFingerprint anyway. " ;
_journal - > setDataFingerprint ( _discoveryPhase - > _dataFingerprint ) ;
// this mechanism should only run once per app launch
_dataFingerprintSetFailCount = - 1 ;
} else {
+ + _dataFingerprintSetFailCount ;
// request to start sync again as it won't happen by itself unless the file has changed on the server or in the local folder
shouldStartSyncAgain = true ;
}
} else {
// only compare hash once
// if it matches - we don't need to calculate it again while _dataFingerprintSetFailCount is greater than 0
const auto accountHostHash = QString : : fromUtf8 ( QCryptographicHash : : hash ( accountHost . toUtf8 ( ) , QCryptographicHash : : Sha256 ) . toHex ( ) ) ;
if ( accountHostHash = = dataFingerprintOverrideHostHash ) {
qCInfo ( lcEngine ) < < " accountHostHash " < < accountHostHash < < " equals to dataFingerprintOverrideHostHash " < < dataFingerprintOverrideHostHash < < " _dataFingerprintSetFailCount " < < _dataFingerprintSetFailCount ;
+ + _dataFingerprintSetFailCount ;
// request to start sync again as it won't happen by itself unless the file has changed on the server or in the local folder
shouldStartSyncAgain = true ;
} else {
qCInfo ( lcEngine ) < < " accountHostHash " < < accountHostHash < < " differs from dataFingerprintOverrideHostHash " < < dataFingerprintOverrideHostHash ;
// give up on calculating the has next time, as it's not the host we are looking for
_dataFingerprintSetFailCount = - 1 ;
}
}
} else {
qCWarning ( lcEngine ) < < " setDataFingerprint was overridden already for account " < < accountDisplayName < < " on host " < < accountHost < < " but is failing again! Or, it's not the host that we are looking for. " ;
}
} ;
//
2018-08-21 20:54:48 +03:00
if ( success & & _discoveryPhase ) {
_journal - > setDataFingerprint ( _discoveryPhase - > _dataFingerprint ) ;
2021-04-05 16:04:00 +03:00
} else if ( _discoveryPhase ) {
// TODO: Remove this when the file restoration problem is fixed for a user
checkAndOverrideSetDataFingerprint ( ) ;
2016-08-02 11:30:49 +03:00
}
2017-12-02 13:40:43 +03:00
conflictRecordMaintenance ( ) ;
2019-06-27 16:47:04 +03:00
_journal - > deleteStaleFlagsEntries ( ) ;
2013-11-21 14:13:58 +04:00
_journal - > commit ( " All Finished. " , false ) ;
2016-11-18 14:30:41 +03:00
// Send final progress information even if no
2017-02-16 17:09:06 +03:00
// files needed propagation, but clear the lastCompletedItem
// so we don't count this twice (like Recent Files)
_progressInfo - > _lastCompletedItem = SyncFileItem ( ) ;
2017-07-11 13:52:40 +03:00
_progressInfo - > _status = ProgressInfo : : Done ;
2016-11-18 14:30:41 +03:00
emit transmissionProgress ( * _progressInfo ) ;
2016-08-02 11:30:49 +03:00
finalize ( success ) ;
2021-04-05 16:04:00 +03:00
if ( shouldStartSyncAgain ) {
// TODO: Remove this when the file restoration problem is fixed for a user
qCWarning ( lcEngine ) < < " Starting sync again for account " < < account ( ) - > displayName ( ) < < " on host " < < account ( ) - > url ( ) . host ( ) < < " due to setDataFingerprint override is running. " ;
startSync ( ) ;
}
2014-05-20 14:28:55 +04:00
}
2013-05-16 15:54:22 +04:00
2015-10-29 18:43:30 +03:00
void SyncEngine : : finalize ( bool success )
2014-05-20 14:28:55 +04:00
{
2018-07-26 10:53:40 +03:00
qCInfo ( lcEngine ) < < " Sync run took " < < _stopWatch . addLapTime ( QLatin1String ( " Sync Finished " ) ) < < " ms " ;
2014-03-26 21:06:25 +04:00
_stopWatch . stop ( ) ;
2018-11-05 15:25:31 +03:00
if ( _discoveryPhase ) {
_discoveryPhase . take ( ) - > deleteLater ( ) ;
}
2016-03-09 19:53:18 +03:00
s_anySyncRunning = false ;
2014-05-29 13:34:25 +04:00
_syncRunning = false ;
2015-10-29 18:43:30 +03:00
emit finished ( success ) ;
2014-11-07 13:41:21 +03:00
// Delete the propagator only after emitting the signal.
2014-11-25 18:24:47 +03:00
_propagator . clear ( ) ;
2019-10-14 14:19:41 +03:00
_seenConflictFiles . clear ( ) ;
2017-06-28 13:45:54 +03:00
_uniqueErrors . clear ( ) ;
2018-02-21 15:22:54 +03:00
_localDiscoveryPaths . clear ( ) ;
_localDiscoveryStyle = LocalDiscoveryStyle : : FilesystemOnly ;
2016-06-09 13:07:18 +03:00
_clearTouchedFilesTimer . start ( ) ;
2012-02-28 19:49:13 +04:00
}
2019-02-13 12:15:33 +03:00
void SyncEngine : : slotProgress ( const SyncFileItem & item , qint64 current )
2014-01-07 18:42:21 +04:00
{
2015-01-30 15:36:20 +03:00
_progressInfo - > setProgressItem ( item , current ) ;
emit transmissionProgress ( * _progressInfo ) ;
2014-01-07 18:42:21 +04:00
}
2013-11-26 15:22:28 +04:00
2017-01-25 13:28:18 +03:00
void SyncEngine : : restoreOldFiles ( SyncFileItemVector & syncItems )
2016-01-05 13:47:17 +03:00
{
/* When the server is trying to send us lots of file in the past, this means that a backup
was restored in the server . In that case , we should not simply overwrite the newer file
on the file system with the older file from the backup on the server . Instead , we will
upload the client file . But we still downloaded the old file in a conflict file just in case
*/
2020-08-13 13:23:02 +03:00
for ( const auto & syncItem : qAsConst ( syncItems ) ) {
if ( syncItem - > _direction ! = SyncFileItem : : Down )
2016-01-05 13:47:17 +03:00
continue ;
2020-08-13 13:23:02 +03:00
switch ( syncItem - > _instruction ) {
2016-01-05 13:47:17 +03:00
case CSYNC_INSTRUCTION_SYNC :
2020-08-13 13:23:02 +03:00
qCWarning ( lcEngine ) < < " restoreOldFiles: RESTORING " < < syncItem - > _file ;
syncItem - > _instruction = CSYNC_INSTRUCTION_CONFLICT ;
2016-01-05 13:47:17 +03:00
break ;
case CSYNC_INSTRUCTION_REMOVE :
2020-08-13 13:23:02 +03:00
qCWarning ( lcEngine ) < < " restoreOldFiles: RESTORING " < < syncItem - > _file ;
syncItem - > _instruction = CSYNC_INSTRUCTION_NEW ;
syncItem - > _direction = SyncFileItem : : Up ;
2016-01-05 13:47:17 +03:00
break ;
case CSYNC_INSTRUCTION_RENAME :
case CSYNC_INSTRUCTION_NEW :
// Ideally we should try to revert the rename or remove, but this would be dangerous
// without re-doing the reconcile phase. So just let it happen.
default :
break ;
}
}
}
2016-06-09 13:07:18 +03:00
void SyncEngine : : slotAddTouchedFile ( const QString & fn )
{
2017-07-24 09:03:05 +03:00
QElapsedTimer now ;
now . start ( ) ;
2016-06-09 13:07:18 +03:00
QString file = QDir : : cleanPath ( fn ) ;
2017-07-24 09:03:05 +03:00
// Iterate from the oldest and remove anything older than 15 seconds.
while ( true ) {
auto first = _touchedFiles . begin ( ) ;
if ( first = = _touchedFiles . end ( ) )
break ;
// Compare to our new QElapsedTimer instead of using elapsed().
// This avoids querying the current time from the OS for every loop.
2019-02-08 11:57:11 +03:00
auto elapsed = std : : chrono : : milliseconds ( now . msecsSinceReference ( ) - first . key ( ) . msecsSinceReference ( ) ) ;
if ( elapsed < = s_touchedFilesMaxAgeMs ) {
// We found the first path younger than the maximum age, keep the rest.
2017-07-24 09:03:05 +03:00
break ;
}
2016-06-09 13:07:18 +03:00
2017-07-24 09:03:05 +03:00
_touchedFiles . erase ( first ) ;
}
// This should be the largest QElapsedTimer yet, use constEnd() as hint.
_touchedFiles . insert ( _touchedFiles . constEnd ( ) , now , file ) ;
2016-06-09 13:07:18 +03:00
}
void SyncEngine : : slotClearTouchedFiles ( )
{
_touchedFiles . clear ( ) ;
}
2017-07-24 09:03:05 +03:00
bool SyncEngine : : wasFileTouched ( const QString & fn ) const
2014-11-07 13:41:21 +03:00
{
2017-07-24 09:03:05 +03:00
// Start from the end (most recent) and look for our path. Check the time just in case.
auto begin = _touchedFiles . constBegin ( ) ;
for ( auto it = _touchedFiles . constEnd ( ) ; it ! = begin ; - - it ) {
2020-11-24 20:50:26 +03:00
if ( ( it - 1 ) . value ( ) = = fn )
2019-02-08 11:57:11 +03:00
return std : : chrono : : milliseconds ( ( it - 1 ) . key ( ) . elapsed ( ) ) < = s_touchedFilesMaxAgeMs ;
2014-11-07 13:41:21 +03:00
}
2017-07-24 09:03:05 +03:00
return false ;
2014-11-07 13:41:21 +03:00
}
2014-12-18 17:39:20 +03:00
AccountPtr SyncEngine : : account ( ) const
{
return _account ;
}
2018-07-18 14:44:41 +03:00
void SyncEngine : : setLocalDiscoveryOptions ( LocalDiscoveryStyle style , std : : set < QString > paths )
2017-09-14 17:43:23 +03:00
{
2018-02-21 15:22:54 +03:00
_localDiscoveryStyle = style ;
2018-03-28 12:04:20 +03:00
_localDiscoveryPaths = std : : move ( paths ) ;
2018-10-11 13:15:44 +03:00
// Normalize to make sure that no path is a contained in another.
// Note: for simplicity, this code consider anything less than '/' as a path separator, so for
// example, this will remove "foo.bar" if "foo" is in the list. This will mean we might have
// some false positive, but that's Ok.
// This invariant is used in SyncEngine::shouldDiscoverLocally
QString prev ;
auto it = _localDiscoveryPaths . begin ( ) ;
while ( it ! = _localDiscoveryPaths . end ( ) ) {
if ( ! prev . isNull ( ) & & it - > startsWith ( prev ) & & ( prev . endsWith ( ' / ' ) | | * it = = prev | | it - > at ( prev . size ( ) ) < = ' / ' ) ) {
it = _localDiscoveryPaths . erase ( it ) ;
} else {
prev = * it ;
+ + it ;
}
}
2017-09-14 17:43:23 +03:00
}
2018-07-18 14:44:41 +03:00
bool SyncEngine : : shouldDiscoverLocally ( const QString & path ) const
2018-02-21 14:18:52 +03:00
{
2018-02-21 15:22:54 +03:00
if ( _localDiscoveryStyle = = LocalDiscoveryStyle : : FilesystemOnly )
return true ;
2018-10-11 13:15:44 +03:00
// The intention is that if "A/X" is in _localDiscoveryPaths:
// - parent folders like "/", "A" will be discovered (to make sure the discovery reaches the
// point where something new happened)
// - the folder itself "A/X" will be discovered
// - subfolders like "A/X/Y" will be discovered (so data inside a new or renamed folder will be
// discovered in full)
// Check out TestLocalDiscovery::testLocalDiscoveryDecision()
2018-02-21 15:22:54 +03:00
auto it = _localDiscoveryPaths . lower_bound ( path ) ;
2018-10-11 13:15:44 +03:00
if ( it = = _localDiscoveryPaths . end ( ) | | ! it - > startsWith ( path ) ) {
// Maybe a subfolder of something in the list?
if ( it ! = _localDiscoveryPaths . begin ( ) & & path . startsWith ( * ( - - it ) ) ) {
return it - > endsWith ( ' / ' ) | | ( path . size ( ) > it - > size ( ) & & path . at ( it - > size ( ) ) < = ' / ' ) ;
}
2018-02-21 15:22:54 +03:00
return false ;
2018-10-11 13:15:44 +03:00
}
2018-02-21 15:22:54 +03:00
// maybe an exact match or an empty path?
if ( it - > size ( ) = = path . size ( ) | | path . isEmpty ( ) )
return true ;
2018-10-11 13:15:44 +03:00
// Maybe a parent folder of something in the list?
2018-02-21 15:22:54 +03:00
// check for a prefix + / match
forever {
if ( it - > size ( ) > path . size ( ) & & it - > at ( path . size ( ) ) = = ' / ' )
return true ;
+ + it ;
if ( it = = _localDiscoveryPaths . end ( ) | | ! it - > startsWith ( path ) )
return false ;
}
return false ;
2017-09-14 17:43:23 +03:00
}
2018-11-21 14:23:08 +03:00
void SyncEngine : : wipeVirtualFiles ( const QString & localPath , SyncJournalDb & journal , Vfs & vfs )
2018-08-15 11:46:16 +03:00
{
qCInfo ( lcEngine ) < < " Wiping virtual files inside " < < localPath ;
journal . getFilesBelowPath ( QByteArray ( ) , [ & ] ( const SyncJournalFileRecord & rec ) {
if ( rec . _type ! = ItemTypeVirtualFile & & rec . _type ! = ItemTypeVirtualFileDownload )
return ;
2021-02-12 00:21:12 +03:00
qCDebug ( lcEngine ) < < " Removing db record for " < < rec . path ( ) ;
2018-08-15 11:46:16 +03:00
journal . deleteFileRecord ( rec . _path ) ;
// If the local file is a dehydrated placeholder, wipe it too.
// Otherwise leave it to allow the next sync to have a new-new conflict.
QString localFile = localPath + rec . _path ;
2018-11-21 14:23:08 +03:00
if ( QFile : : exists ( localFile ) & & vfs . isDehydratedPlaceholder ( localFile ) ) {
2021-02-12 00:21:12 +03:00
qCDebug ( lcEngine ) < < " Removing local dehydrated placeholder " < < rec . path ( ) ;
2018-08-15 11:46:16 +03:00
QFile : : remove ( localFile ) ;
}
} ) ;
journal . forceRemoteDiscoveryNextSync ( ) ;
2019-01-23 14:58:14 +03:00
// Postcondition: No ItemTypeVirtualFile / ItemTypeVirtualFileDownload left in the db.
// But hydrated placeholders may still be around.
2018-08-15 11:46:16 +03:00
}
2014-03-17 14:34:51 +04:00
void SyncEngine : : abort ( )
2013-10-02 21:41:17 +04:00
{
2017-05-11 16:36:47 +03:00
if ( _propagator )
qCInfo ( lcEngine ) < < " Aborting sync " ;
2014-12-02 14:25:51 +03:00
if ( _propagator ) {
2019-01-14 17:41:34 +03:00
// If we're already in the propagation phase, aborting that is sufficient
2014-02-06 17:52:56 +04:00
_propagator - > abort ( ) ;
2019-01-14 17:41:34 +03:00
} else if ( _discoveryPhase ) {
// Delete the discovery and all child jobs after ensuring
// it can't finish and start the propagator
2020-12-10 21:52:58 +03:00
disconnect ( _discoveryPhase . data ( ) , nullptr , this , nullptr ) ;
2019-01-14 17:41:34 +03:00
_discoveryPhase . take ( ) - > deleteLater ( ) ;
syncError ( tr ( " Aborted " ) ) ;
finalize ( false ) ;
2014-12-02 14:25:51 +03:00
}
2013-10-02 21:41:17 +04:00
}
2017-06-28 13:45:54 +03:00
void SyncEngine : : slotSummaryError ( const QString & message )
{
if ( _uniqueErrors . contains ( message ) )
return ;
_uniqueErrors . insert ( message ) ;
2017-07-11 16:54:01 +03:00
emit syncError ( message , ErrorCategory : : Normal ) ;
2017-06-28 13:45:54 +03:00
}
void SyncEngine : : slotInsufficientLocalStorage ( )
{
slotSummaryError (
tr ( " Disk space is low: Downloads that would reduce free space "
" below %1 were skipped. " )
. arg ( Utility : : octetsToString ( freeSpaceLimit ( ) ) ) ) ;
}
2017-07-07 16:11:00 +03:00
void SyncEngine : : slotInsufficientRemoteStorage ( )
{
2017-07-11 16:54:01 +03:00
auto msg = tr ( " There is insufficient space available on the server for some uploads. " ) ;
if ( _uniqueErrors . contains ( msg ) )
return ;
_uniqueErrors . insert ( msg ) ;
emit syncError ( msg , ErrorCategory : : InsufficientRemoteStorage ) ;
2017-07-07 16:11:00 +03:00
}
2014-11-10 00:34:07 +03:00
} // namespace OCC