/* * Copyright (C) by Duncan Mac-Vicar P. * Copyright (C) by Klaas Freitag * * 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. */ #include "mirall/csyncthread.h" #include "mirall/mirallconfigfile.h" #include "mirall/theme.h" #include "mirall/logger.h" #include "mirall/owncloudinfo.h" #include "creds/abstractcredentials.h" #ifdef Q_OS_WIN #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include namespace Mirall { /* static variables to hold the credentials */ QMutex CSyncThread::_mutex; QMutex CSyncThread::_syncMutex; CSyncThread::CSyncThread(CSYNC *csync) { _mutex.lock(); _csync_ctx = csync; _mutex.unlock(); } CSyncThread::~CSyncThread() { } //Convert an error code from csync to a user readable string. // Keep that function thread safe as it can be called from the sync thread or the main thread QString CSyncThread::csyncErrorToString( CSYNC_ERROR_CODE err, const char *errString ) { QString errStr; switch( err ) { case CSYNC_ERR_NONE: errStr = tr("Success."); break; case CSYNC_ERR_LOG: errStr = tr("CSync Logging setup failed."); break; case CSYNC_ERR_LOCK: errStr = tr("CSync failed to create a lock file."); break; case CSYNC_ERR_STATEDB_LOAD: errStr = tr("CSync failed to load the state db."); break; case CSYNC_ERR_MODULE: errStr = tr("

The %1 plugin for csync could not be loaded.
Please verify the installation!

").arg(Theme::instance()->appNameGUI()); break; case CSYNC_ERR_TIMESKEW: errStr = tr("The system time on this client is different than the system time on the server. " "Please use a time synchronization service (NTP) on the server and client machines " "so that the times remain the same."); break; case CSYNC_ERR_FILESYSTEM: errStr = tr("CSync could not detect the filesystem type."); break; case CSYNC_ERR_TREE: errStr = tr("CSync got an error while processing internal trees."); break; case CSYNC_ERR_MEM: errStr = tr("CSync failed to reserve memory."); break; case CSYNC_ERR_PARAM: errStr = tr("CSync fatal parameter error."); break; case CSYNC_ERR_UPDATE: errStr = tr("CSync processing step update failed."); break; case CSYNC_ERR_RECONCILE: errStr = tr("CSync processing step reconcile failed."); break; case CSYNC_ERR_PROPAGATE: errStr = tr("CSync processing step propagate failed."); break; case CSYNC_ERR_ACCESS_FAILED: errStr = tr("

The target directory does not exist.

Please check the sync setup.

"); break; case CSYNC_ERR_REMOTE_CREATE: case CSYNC_ERR_REMOTE_STAT: errStr = tr("A remote file can not be written. Please check the remote access."); break; case CSYNC_ERR_LOCAL_CREATE: case CSYNC_ERR_LOCAL_STAT: errStr = tr("The local filesystem can not be written. Please check permissions."); break; case CSYNC_ERR_PROXY: errStr = tr("CSync failed to connect through a proxy."); break; case CSYNC_ERR_LOOKUP: errStr = tr("CSync failed to lookup proxy or server."); break; case CSYNC_ERR_AUTH_SERVER: errStr = tr("CSync failed to authenticate at the %1 server.").arg(Theme::instance()->appNameGUI()); break; case CSYNC_ERR_AUTH_PROXY: errStr = tr("CSync failed to authenticate at the proxy."); break; case CSYNC_ERR_CONNECT: errStr = tr("CSync failed to connect to the network."); break; case CSYNC_ERR_TIMEOUT: errStr = tr("A network connection timeout happend."); break; case CSYNC_ERR_HTTP: errStr = tr("A HTTP transmission error happened."); break; case CSYNC_ERR_PERM: errStr = tr("CSync failed due to not handled permission deniend."); break; case CSYNC_ERR_NOT_FOUND: errStr = tr("CSync failed to find a specific file."); break; case CSYNC_ERR_EXISTS: errStr = tr("CSync tried to create a directory that already exists."); break; case CSYNC_ERR_NOSPC: errStr = tr("CSync: No space on %1 server available.").arg(Theme::instance()->appNameGUI()); break; case CSYNC_ERR_UNSPEC: errStr = tr("CSync unspecified error."); default: errStr = tr("An internal error number %1 happend.").arg( (int) err ); } if( errString ) { errStr += tr("
Backend Message: ")+QString::fromUtf8(errString); } return errStr; } int CSyncThread::treewalkLocal( TREE_WALK_FILE* file, void *data ) { return static_cast(data)->treewalkFile( file, false ); } int CSyncThread::treewalkRemote( TREE_WALK_FILE* file, void *data ) { return static_cast(data)->treewalkFile( file, true ); } int CSyncThread::walkFinalize(TREE_WALK_FILE* file, void *data ) { return static_cast(data)->treewalkError( file); } int CSyncThread::treewalkFile( TREE_WALK_FILE *file, bool remote ) { if( ! file ) return -1; SyncFileItem item; item._file = QString::fromUtf8( file->path ); item._instruction = file->instruction; item._dir = SyncFileItem::None; SyncFileItem::Direction dir; int re = 0; if (file->instruction != CSYNC_INSTRUCTION_IGNORE && file->instruction != CSYNC_INSTRUCTION_REMOVE) { _hasFiles = true; } switch(file->instruction) { case CSYNC_INSTRUCTION_NONE: case CSYNC_INSTRUCTION_IGNORE: break; default: if (!_needsUpdate) _needsUpdate = true; } switch(file->instruction) { case CSYNC_INSTRUCTION_NONE: // No need to do anything. return re; break; case CSYNC_INSTRUCTION_RENAME: dir = !remote ? SyncFileItem::Down : SyncFileItem::Up; item._renameTarget = QString::fromUtf8( file->rename_path ); break; case CSYNC_INSTRUCTION_REMOVE: dir = !remote ? SyncFileItem::Down : SyncFileItem::Up; break; case CSYNC_INSTRUCTION_CONFLICT: case CSYNC_INSTRUCTION_IGNORE: case CSYNC_INSTRUCTION_ERROR: dir = SyncFileItem::None; break; case CSYNC_INSTRUCTION_EVAL: case CSYNC_INSTRUCTION_NEW: case CSYNC_INSTRUCTION_SYNC: case CSYNC_INSTRUCTION_STAT_ERROR: case CSYNC_INSTRUCTION_DELETED: case CSYNC_INSTRUCTION_UPDATED: default: dir = remote ? SyncFileItem::Down : SyncFileItem::Up; break; } switch( file->type ) { case CSYNC_FTW_TYPE_DIR: item._type = SyncFileItem::Directory; break; case CSYNC_FTW_TYPE_FILE: item._type = SyncFileItem::File; break; case CSYNC_FTW_TYPE_SLINK: item._type = SyncFileItem::SoftLink; break; default: item._type = SyncFileItem::UnknownType; } item._dir = dir; _mutex.lock(); _syncedItems.append(item); _mutex.unlock(); return re; } int CSyncThread::treewalkError(TREE_WALK_FILE* file) { SyncFileItem item; item._file= QString::fromUtf8(file->path); int indx = _syncedItems.indexOf(item); if ( indx == -1 ) return 0; if( file && (file->instruction == CSYNC_INSTRUCTION_STAT_ERROR || file->instruction == CSYNC_INSTRUCTION_ERROR) ) { _mutex.lock(); _syncedItems[indx]._instruction = file->instruction; _mutex.unlock(); } return 0; } struct CSyncRunScopeHelper { CSyncRunScopeHelper(CSYNC *ctx, CSyncThread *parent) : _ctx(ctx), _parent(parent) { _t.start(); } ~CSyncRunScopeHelper() { csync_commit(_ctx); qDebug() << "CSync run took " << _t.elapsed() << " Milliseconds"; emit(_parent->finished()); _parent->_syncMutex.unlock(); } CSYNC *_ctx; QTime _t; CSyncThread *_parent; }; void CSyncThread::handleSyncError(CSYNC *ctx, const char *state) { CSYNC_ERROR_CODE err = csync_get_error( ctx ); const char *errMsg = csync_get_error_string( ctx ); QString errStr = csyncErrorToString(err, errMsg); qDebug() << " #### ERROR during "<< state << ": " << errStr; switch (err) { case CSYNC_ERR_SERVICE_UNAVAILABLE: case CSYNC_ERR_CONNECT: emit csyncUnavailable(); break; default: emit csyncError(errStr); } } void CSyncThread::startSync() { if (!_syncMutex.tryLock()) { qDebug() << Q_FUNC_INFO << "WARNING: Another sync seems to be running. Not starting a new one."; return; } if( ! _csync_ctx ) { qDebug() << "XXXXXXXXXXXXXXXX FAIL: do not have csync_ctx!"; } qDebug() << Q_FUNC_INFO << "Sync started"; qDebug() << "starting to sync " << qApp->thread() << QThread::currentThread(); _mutex.lock(); _syncedItems.clear(); _needsUpdate = false; _mutex.unlock(); // cleans up behind us and emits finished() to ease error handling CSyncRunScopeHelper helper(_csync_ctx, this); // maybe move this somewhere else where it can influence a running sync? MirallConfigFile cfg; int downloadLimit = 0; if (cfg.useDownloadLimit()) { downloadLimit = cfg.downloadLimit(); } csync_set_module_property(_csync_ctx, "bandwidth_limit_download", &downloadLimit); int uploadLimit = -75; // 75% int useUpLimit = cfg.useUploadLimit(); if ( useUpLimit >= 1) { uploadLimit = cfg.uploadLimit(); } else if (useUpLimit == 0) { uploadLimit = 0; } csync_set_module_property(_csync_ctx, "bandwidth_limit_upload", &uploadLimit); csync_set_progress_callback( _csync_ctx, cb_progress ); csync_set_module_property(_csync_ctx, "csync_context", _csync_ctx); csync_set_userdata(_csync_ctx, this); // TODO: This should be a part of this method, but we don't have // any way to get "session_key" module property from csync. Had we // have it, then we could keep this code and remove it from // AbstractCredentials implementations. cfg.getCredentials()->syncContextPreStart(_csync_ctx); // if (_lastAuthCookies.length() > 0) { // // Stuff cookies inside csync, then we can avoid the intermediate HTTP 401 reply // // when https://github.com/owncloud/core/pull/4042 is merged. // QString cookiesAsString; // foreach(QNetworkCookie c, _lastAuthCookies) { // cookiesAsString += c.name(); // cookiesAsString += '='; // cookiesAsString += c.value(); // cookiesAsString += "; "; // } // csync_set_module_property(_csync_ctx, "session_key", cookiesAsString.to // } // csync_set_auth_callback( _csync_ctx, getauth ); qDebug() << "#### Update start #################################################### >>"; if( csync_update(_csync_ctx) < 0 ) { handleSyncError(_csync_ctx, "csync_update"); return; } qDebug() << "<<#### Update end ###########################################################"; if( csync_reconcile(_csync_ctx) < 0 ) { handleSyncError(_csync_ctx, "csync_reconcile"); return; } _hasFiles = false; bool walkOk = true; if( csync_walk_local_tree(_csync_ctx, &treewalkLocal, 0) < 0 ) { qDebug() << "Error in local treewalk."; walkOk = false; } if( walkOk && csync_walk_remote_tree(_csync_ctx, &treewalkRemote, 0) < 0 ) { qDebug() << "Error in remote treewalk."; } if (!_hasFiles && !_syncedItems.isEmpty()) { qDebug() << Q_FUNC_INFO << "All the files are going to be removed, asking the user"; bool cancel = true; emit aboutToRemoveAllFiles(_syncedItems.first()._dir, &cancel); if (cancel) { qDebug() << Q_FUNC_INFO << "Abort sync"; return; } } if (_needsUpdate) emit(started()); if( csync_propagate(_csync_ctx) < 0 ) { handleSyncError(_csync_ctx, "cysnc_reconcile"); return; } if( walkOk ) { if( csync_walk_local_tree(_csync_ctx, &walkFinalize, 0) < 0 || csync_walk_remote_tree(_csync_ctx, &walkFinalize, 0 ) < 0 ) { qDebug() << "Error in finalize treewalk."; } else { // emit the treewalk results. emit treeWalkResult(_syncedItems); } } qDebug() << Q_FUNC_INFO << "Sync finished"; } Progress::Kind CSyncThread::csyncToProgressKind( enum csync_notify_type_e kind ) { Progress::Kind pKind = Progress::Invalid; switch(kind) { case CSYNC_NOTIFY_INVALID: pKind = Progress::Invalid; break; case CSYNC_NOTIFY_START_SYNC_SEQUENCE: pKind = Progress::StartSync; break; case CSYNC_NOTIFY_START_DOWNLOAD: pKind = Progress::StartDownload; break; case CSYNC_NOTIFY_START_UPLOAD: pKind = Progress::StartUpload; break; case CSYNC_NOTIFY_PROGRESS: pKind = Progress::Context; break; case CSYNC_NOTIFY_FINISHED_DOWNLOAD: pKind = Progress::EndDownload; break; case CSYNC_NOTIFY_FINISHED_UPLOAD: pKind = Progress::EndUpload; break; case CSYNC_NOTIFY_FINISHED_SYNC_SEQUENCE: pKind = Progress::EndSync; break; case CSYNC_NOTIFY_START_DELETE: pKind = Progress::StartDelete; break; case CSYNC_NOTIFY_END_DELETE: pKind = Progress::EndDelete; break; case CSYNC_NOTIFY_ERROR: pKind = Progress::Error; break; default: pKind = Progress::Invalid; break; } return pKind; } void CSyncThread::cb_progress( CSYNC_PROGRESS *progress, void *userdata ) { if( !progress ) { qDebug() << "No progress block in progress callback found!"; return; } if( !userdata ) { qDebug() << "No thread given in progress callback!"; return; } Progress::Info pInfo; CSyncThread *thread = static_cast(userdata); pInfo.kind = thread->csyncToProgressKind( progress->kind ); pInfo.current_file = QUrl::fromEncoded( progress->path ).toString(); pInfo.file_size = progress->file_size; pInfo.current_file_bytes = progress->curr_bytes; pInfo.overall_file_count = progress->overall_file_count; pInfo.current_file_no = progress->current_file_no; pInfo.overall_transmission_size = progress->overall_transmission_size; pInfo.overall_current_bytes = progress->current_overall_bytes; pInfo.timestamp = QTime::currentTime(); // Connect to something in folder! thread->transmissionProgress( pInfo ); } } // ns Mirall