Merge pull request #4615 from owncloud/socketApiRefactor

Socket API refactor
This commit is contained in:
Olivier Goffart 2016-03-30 13:24:10 +02:00
commit cdbc25ede8
26 changed files with 459 additions and 732 deletions

View file

@ -18,8 +18,6 @@
namespace OCC {
typedef QSharedPointer<AccountState> AccountStatePtr;
/**
@brief The AccountManager class
@ingroup gui

View file

@ -35,6 +35,7 @@
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QListWidgetItem>
#include <QMessageBox>
#include <QAction>

View file

@ -29,11 +29,13 @@ namespace OCC {
class AccountState;
class Account;
typedef QExplicitlySharedDataPointer<AccountState> AccountStatePtr;
/**
* @brief Extra info about an ownCloud server account.
* @ingroup gui
*/
class AccountState : public QObject {
class AccountState : public QObject, public QSharedData {
Q_OBJECT
public:
enum State {
@ -148,6 +150,6 @@ private:
}
Q_DECLARE_METATYPE(OCC::AccountState*)
Q_DECLARE_METATYPE(QSharedPointer<OCC::AccountState>)
Q_DECLARE_METATYPE(OCC::AccountStatePtr)
#endif //ACCOUNTINFO_H

View file

@ -56,9 +56,10 @@ static void csyncLogCatcher(int /*verbosity*/,
Folder::Folder(const FolderDefinition& definition,
AccountState* accountState,
QObject* parent)
: QObject(parent)
, _accountState(0)
, _accountState(accountState)
, _definition(definition)
, _csyncError(false)
, _csyncUnavail(false)
@ -70,6 +71,9 @@ Folder::Folder(const FolderDefinition& definition,
, _consecutiveFollowUpSyncs(0)
, _journal(definition.localPath)
{
qRegisterMetaType<SyncFileItemVector>("SyncFileItemVector");
qRegisterMetaType<SyncFileItem::Direction>("SyncFileItem::Direction");
qsrand(QTime::currentTime().msec());
_timeSinceLastSyncStart.start();
_timeSinceLastSyncDone.start();
@ -84,24 +88,37 @@ Folder::Folder(const FolderDefinition& definition,
checkLocalPath();
_syncResult.setFolder(_definition.alias);
_engine.reset(new SyncEngine(_accountState->account(), path(), remoteUrl(), remotePath(), &_journal));
// pass the setting if hidden files are to be ignored, will be read in csync_update
_engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles);
if (!setIgnoredFiles())
qWarning("Could not read system exclude file");
connect(_engine.data(), SIGNAL(rootEtag(QString)), this, SLOT(etagRetreivedFromSyncEngine(QString)));
connect(_engine.data(), SIGNAL(treeWalkResult(const SyncFileItemVector&)),
this, SLOT(slotThreadTreeWalkResult(const SyncFileItemVector&)), Qt::QueuedConnection);
connect(_engine.data(), SIGNAL(started()), SLOT(slotSyncStarted()), Qt::QueuedConnection);
connect(_engine.data(), SIGNAL(finished(bool)), SLOT(slotSyncFinished(bool)), Qt::QueuedConnection);
connect(_engine.data(), SIGNAL(csyncError(QString)), SLOT(slotSyncError(QString)), Qt::QueuedConnection);
connect(_engine.data(), SIGNAL(csyncUnavailable()), SLOT(slotCsyncUnavailable()), Qt::QueuedConnection);
//direct connection so the message box is blocking the sync.
connect(_engine.data(), SIGNAL(aboutToRemoveAllFiles(SyncFileItem::Direction,bool*)),
SLOT(slotAboutToRemoveAllFiles(SyncFileItem::Direction,bool*)));
connect(_engine.data(), SIGNAL(aboutToRestoreBackup(bool*)),
SLOT(slotAboutToRestoreBackup(bool*)));
connect(_engine.data(), SIGNAL(folderDiscovered(bool,QString)), this, SLOT(slotFolderDiscovered(bool,QString)));
connect(_engine.data(), SIGNAL(transmissionProgress(ProgressInfo)), this, SLOT(slotTransmissionProgress(ProgressInfo)));
connect(_engine.data(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)),
this, SLOT(slotItemCompleted(const SyncFileItem &, const PropagatorJob &)));
connect(_engine.data(), SIGNAL(newBigFolder(QString)), this, SLOT(slotNewBigFolderDiscovered(QString)));
}
Folder::~Folder()
{
if( _engine ) {
_engine->abort();
_engine.reset(0);
}
}
void Folder::setAccountState( AccountState *account )
{
_accountState = account;
}
AccountState* Folder::accountState() const
{
return _accountState;
}
void Folder::checkLocalPath()
@ -192,7 +209,7 @@ QString Folder::cleanPath()
bool Folder::isBusy() const
{
return !_engine.isNull();
return _engine->isSyncRunning();
}
QString Folder::remotePath() const
@ -202,9 +219,6 @@ QString Folder::remotePath() const
QUrl Folder::remoteUrl() const
{
if (!_accountState) {
return QUrl("http://deleted-account");
}
return Account::concatUrlPath(_accountState->account()->davUrl(), remotePath());
}
@ -256,11 +270,6 @@ void Folder::slotRunEtagJob()
{
qDebug() << "* Trying to check" << remoteUrl().toString() << "for changes via ETag check. (time since last sync:" << (_timeSinceLastSyncDone.elapsed() / 1000) << "s)";
if (!_accountState) {
qDebug() << "Can't run EtagJob, account is deleted";
return;
}
AccountPtr account = _accountState->account();
if (!_requestEtagJob.isNull()) {
@ -328,9 +337,7 @@ void Folder::etagRetreived(const QString& etag)
emit scheduleToSync(this);
}
if( _accountState ) {
_accountState->tagLastSuccessfullETagRequest();
}
_accountState->tagLastSuccessfullETagRequest();
}
void Folder::etagRetreivedFromSyncEngine(const QString& etag)
@ -359,7 +366,7 @@ void Folder::bubbleUpSyncResult()
SyncRunFileLog syncFileLog;
syncFileLog.start(path(), _engine ? _engine->stopWatch() : Utility::StopWatch() );
syncFileLog.start(path(), _engine->isSyncRunning() ? _engine->stopWatch() : Utility::StopWatch() );
QElapsedTimer timer;
timer.start();
@ -434,32 +441,32 @@ void Folder::bubbleUpSyncResult()
_syncResult.setWarnCount(ignoredItems);
if( firstItemNew ) {
createGuiLog( firstItemNew->_file, SyncFileStatus::STATUS_NEW, newItems );
createGuiLog( firstItemNew->_file, LogStatusNew, newItems );
}
if( firstItemDeleted ) {
createGuiLog( firstItemDeleted->_file, SyncFileStatus::STATUS_REMOVE, removedItems );
createGuiLog( firstItemDeleted->_file, LogStatusRemove, removedItems );
}
if( firstItemUpdated ) {
createGuiLog( firstItemUpdated->_file, SyncFileStatus::STATUS_UPDATED, updatedItems );
createGuiLog( firstItemUpdated->_file, LogStatusUpdated, updatedItems );
}
if( firstItemRenamed ) {
SyncFileStatus status(SyncFileStatus::STATUS_RENAME);
LogStatus status(LogStatusRename);
// if the path changes it's rather a move
QDir renTarget = QFileInfo(firstItemRenamed->_renameTarget).dir();
QDir renSource = QFileInfo(firstItemRenamed->_file).dir();
if(renTarget != renSource) {
status.set(SyncFileStatus::STATUS_MOVE);
status = LogStatusMove;
}
createGuiLog( firstItemRenamed->_originalFile, status, renamedItems, firstItemRenamed->_renameTarget );
}
createGuiLog( firstItemError->_file, SyncFileStatus::STATUS_ERROR, errorItems );
createGuiLog( firstItemError->_file, LogStatusError, errorItems );
qDebug() << "OO folder slotSyncFinished: result: " << int(_syncResult.status());
}
void Folder::createGuiLog( const QString& filename, SyncFileStatus status, int count,
void Folder::createGuiLog( const QString& filename, LogStatus status, int count,
const QString& renameTarget )
{
if(count > 0) {
@ -468,53 +475,49 @@ void Folder::createGuiLog( const QString& filename, SyncFileStatus status, int c
QString file = QDir::toNativeSeparators(filename);
QString text;
// not all possible values of status are evaluated here because the others
// are not used in the calling function. Please check there.
switch (status.tag()) {
case SyncFileStatus::STATUS_REMOVE:
switch (status) {
case LogStatusRemove:
if( count > 1 ) {
text = tr("%1 and %2 other files have been removed.", "%1 names a file.").arg(file).arg(count-1);
} else {
text = tr("%1 has been removed.", "%1 names a file.").arg(file);
}
break;
case SyncFileStatus::STATUS_NEW:
case LogStatusNew:
if( count > 1 ) {
text = tr("%1 and %2 other files have been downloaded.", "%1 names a file.").arg(file).arg(count-1);
} else {
text = tr("%1 has been downloaded.", "%1 names a file.").arg(file);
}
break;
case SyncFileStatus::STATUS_UPDATED:
case LogStatusUpdated:
if( count > 1 ) {
text = tr("%1 and %2 other files have been updated.").arg(file).arg(count-1);
} else {
text = tr("%1 has been updated.", "%1 names a file.").arg(file);
}
break;
case SyncFileStatus::STATUS_RENAME:
case LogStatusRename:
if( count > 1 ) {
text = tr("%1 has been renamed to %2 and %3 other files have been renamed.").arg(file).arg(renameTarget).arg(count-1);
} else {
text = tr("%1 has been renamed to %2.", "%1 and %2 name files.").arg(file).arg(renameTarget);
}
break;
case SyncFileStatus::STATUS_MOVE:
case LogStatusMove:
if( count > 1 ) {
text = tr("%1 has been moved to %2 and %3 other files have been moved.").arg(file).arg(renameTarget).arg(count-1);
} else {
text = tr("%1 has been moved to %2.").arg(file).arg(renameTarget);
}
break;
case SyncFileStatus::STATUS_ERROR:
case LogStatusError:
if( count > 1 ) {
text = tr("%1 and %2 other files could not be synced due to errors. See the log for details.", "%1 names a file.").arg(file).arg(count-1);
} else {
text = tr("%1 could not be synced due to an error. See the log for details.").arg(file);
}
break;
default:
break;
}
if( !text.isEmpty() ) {
@ -557,7 +560,7 @@ void Folder::slotWatchedPathChanged(const QString& path)
{
// When no sync is running or it's in the prepare phase, we can
// always schedule a new sync.
if (! _engine || _syncResult.status() == SyncResult::SyncPrepare) {
if (! _engine->isSyncRunning() || _syncResult.status() == SyncResult::SyncPrepare) {
emit scheduleToSync(this);
return;
}
@ -585,91 +588,14 @@ void Folder::slotWatchedPathChanged(const QString& path)
}
}
/**
* Whether this item should get an ERROR icon through the Socket API.
*
* The Socket API should only present serious, permanent errors to the user.
* In particular SoftErrors should just retain their 'needs to be synced'
* icon as the problem is most likely going to resolve itself quickly and
* automatically.
*/
static bool showErrorInSocketApi(const SyncFileItem& item)
{
const auto status = item._status;
return status == SyncFileItem::NormalError
|| status == SyncFileItem::FatalError;
}
static void addErroredSyncItemPathsToList(const SyncFileItemVector& items, QSet<QString>* set) {
foreach (const SyncFileItemPtr &item, items) {
if (showErrorInSocketApi(*item)) {
set->insert(item->_file);
}
}
}
void Folder::slotThreadTreeWalkResult(const SyncFileItemVector& items)
{
addErroredSyncItemPathsToList(items, &this->_stateLastSyncItemsWithErrorNew);
_syncResult.setSyncFileItemVector(items);
}
void Folder::slotAboutToPropagate(SyncFileItemVector& items)
{
// empty the tainted list since the status generation code will use the _syncedItems
// (which imply the folder) to generate the syncing state icon now.
_stateTaintedFolders.clear();
addErroredSyncItemPathsToList(items, &this->_stateLastSyncItemsWithErrorNew);
}
bool Folder::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s)
{
if (t == CSYNC_FTW_TYPE_DIR) {
if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) {
qDebug() << Q_FUNC_INFO << "Folder has error" << fn;
s->set(SyncFileStatus::STATUS_ERROR);
return true;
}
// If sync is running, check _syncedItems, possibly give it STATUS_EVAL (=syncing down)
if (!_engine.isNull()) {
if (_engine->estimateState(fn, t, s)) {
return true;
}
}
if(!fn.endsWith(QLatin1Char('/'))) {
fn.append(QLatin1Char('/'));
}
if (Utility::doesSetContainPrefix(_stateTaintedFolders, fn)) {
qDebug() << Q_FUNC_INFO << "Folder is tainted, EVAL!" << fn;
s->set(SyncFileStatus::STATUS_EVAL);
return true;
}
return false;
} else if ( t== CSYNC_FTW_TYPE_FILE) {
// check if errorList has the directory/file
if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) {
s->set(SyncFileStatus::STATUS_ERROR);
return true;
}
// If sync running: _syncedItems -> SyncingState
if (!_engine.isNull()) {
if (_engine->estimateState(fn, t, s)) {
return true;
}
}
}
return false;
}
void Folder::saveToSettings() const
{
if (!_accountState) {
qDebug() << "Can't save folder to settings, account is deleted";
return;
}
auto settings = _accountState->settings();
settings->beginGroup(QLatin1String("Folders"));
FolderDefinition::save(*settings, _definition);
@ -680,11 +606,6 @@ void Folder::saveToSettings() const
void Folder::removeFromSettings() const
{
if (!_accountState) {
qDebug() << "Can't remove folder from settings, account is deleted";
return;
}
auto settings = _accountState->settings();
settings->beginGroup(QLatin1String("Folders"));
settings->remove(_definition.alias);
@ -692,56 +613,19 @@ void Folder::removeFromSettings() const
bool Folder::isFileExcludedAbsolute(const QString& fullPath) const
{
if (!fullPath.startsWith(path())) {
// Mark paths we're not responsible for as excluded...
return true;
}
QString myFullPath = fullPath;
if (myFullPath.endsWith(QLatin1Char('/'))) {
myFullPath.chop(1);
}
QString relativePath = myFullPath.mid(path().size());
auto excl = ExcludedFiles::instance().isExcluded(myFullPath, relativePath, _definition.ignoreHiddenFiles);
return excl != CSYNC_NOT_EXCLUDED;
return _engine->excludedFiles().isExcluded(fullPath, path(), _definition.ignoreHiddenFiles);
}
bool Folder::isFileExcludedRelative(const QString& relativePath) const
{
return isFileExcludedAbsolute(path() + relativePath);
return _engine->excludedFiles().isExcluded(path() + relativePath, path(), _definition.ignoreHiddenFiles);
}
void Folder::watcherSlot(QString fn)
{
// FIXME: On OS X we could not do this "if" since on OS X the file watcher ignores events for ourselves
// however to have the same behaviour atm on all platforms, we don't do it
if (!_engine.isNull()) {
qDebug() << Q_FUNC_INFO << "Sync running, IGNORE event for " << fn;
return;
}
QFileInfo fi(fn);
if (fi.isFile()) {
fn = fi.path(); // depending on OS, file watcher might be for dir or file
}
// Make it a relative path depending on the folder
QString relativePath = fn.remove(0, path().length());
if( !relativePath.endsWith(QLatin1Char('/'))) {
relativePath.append(QLatin1Char('/'));
}
qDebug() << Q_FUNC_INFO << fi.canonicalFilePath() << fn << relativePath;
_stateTaintedFolders.insert(relativePath);
// Notify the SocketAPI?
}
void Folder::slotTerminateSync()
{
qDebug() << "folder " << alias() << " Terminating!";
if( _engine ) {
if( _engine->isSyncRunning() ) {
_engine->abort();
// Do not display an error message, user knows his own actions.
@ -814,11 +698,6 @@ bool Folder::proxyDirty()
void Folder::startSync(const QStringList &pathList)
{
if (!_accountState) {
qDebug() << "Can't startSync, account is deleted";
return;
}
Q_UNUSED(pathList)
if (proxyDirty()) {
setProxyDirty(false);
@ -843,10 +722,6 @@ void Folder::startSync(const QStringList &pathList)
qDebug() << "*** Start syncing " << remoteUrl().toString() << " - client version"
<< qPrintable(Theme::instance()->version());
_engine.reset(new SyncEngine( _accountState->account(), path(), remoteUrl(), remotePath(), &_journal));
// pass the setting if hidden files are to be ignored, will be read in csync_update
_engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles);
if (!setIgnoredFiles())
{
slotSyncError(tr("Could not read system exclude file"));
@ -854,32 +729,6 @@ void Folder::startSync(const QStringList &pathList)
return;
}
qRegisterMetaType<SyncFileItemVector>("SyncFileItemVector");
qRegisterMetaType<SyncFileItem::Direction>("SyncFileItem::Direction");
connect(_engine.data(), SIGNAL(rootEtag(QString)), this, SLOT(etagRetreivedFromSyncEngine(QString)));
connect( _engine.data(), SIGNAL(treeWalkResult(const SyncFileItemVector&)),
this, SLOT(slotThreadTreeWalkResult(const SyncFileItemVector&)), Qt::QueuedConnection);
connect( _engine.data(), SIGNAL(aboutToPropagate(SyncFileItemVector&)),
this, SLOT(slotAboutToPropagate(SyncFileItemVector&)));
connect(_engine.data(), SIGNAL(started()), SLOT(slotSyncStarted()), Qt::QueuedConnection);
connect(_engine.data(), SIGNAL(finished(bool)), SLOT(slotSyncFinished(bool)), Qt::QueuedConnection);
connect(_engine.data(), SIGNAL(csyncError(QString)), SLOT(slotSyncError(QString)), Qt::QueuedConnection);
connect(_engine.data(), SIGNAL(csyncUnavailable()), SLOT(slotCsyncUnavailable()), Qt::QueuedConnection);
//direct connection so the message box is blocking the sync.
connect(_engine.data(), SIGNAL(aboutToRemoveAllFiles(SyncFileItem::Direction,bool*)),
SLOT(slotAboutToRemoveAllFiles(SyncFileItem::Direction,bool*)));
connect(_engine.data(), SIGNAL(aboutToRestoreBackup(bool*)),
SLOT(slotAboutToRestoreBackup(bool*)));
connect(_engine.data(), SIGNAL(folderDiscovered(bool,QString)), this, SLOT(slotFolderDiscovered(bool,QString)));
connect(_engine.data(), SIGNAL(transmissionProgress(ProgressInfo)), this, SLOT(slotTransmissionProgress(ProgressInfo)));
connect(_engine.data(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)),
this, SLOT(slotItemCompleted(const SyncFileItem &, const PropagatorJob &)));
connect(_engine.data(), SIGNAL(syncItemDiscovered(const SyncFileItem &)), this, SLOT(slotSyncItemDiscovered(const SyncFileItem &)));
connect(_engine.data(), SIGNAL(newBigFolder(QString)), this, SLOT(slotNewBigFolderDiscovered(QString)));
setDirtyNetworkLimits();
ConfigFile cfgFile;
@ -896,27 +745,24 @@ void Folder::startSync(const QStringList &pathList)
void Folder::setDirtyNetworkLimits()
{
if (_engine) {
ConfigFile cfg;
int downloadLimit = -75; // 75%
int useDownLimit = cfg.useDownloadLimit();
if (useDownLimit >= 1) {
downloadLimit = cfg.downloadLimit() * 1000;
} else if (useDownLimit == 0) {
downloadLimit = 0;
}
int uploadLimit = -75; // 75%
int useUpLimit = cfg.useUploadLimit();
if ( useUpLimit >= 1) {
uploadLimit = cfg.uploadLimit() * 1000;
} else if (useUpLimit == 0) {
uploadLimit = 0;
}
_engine->setNetworkLimits(uploadLimit, downloadLimit);
ConfigFile cfg;
int downloadLimit = -75; // 75%
int useDownLimit = cfg.useDownloadLimit();
if (useDownLimit >= 1) {
downloadLimit = cfg.downloadLimit() * 1000;
} else if (useDownLimit == 0) {
downloadLimit = 0;
}
int uploadLimit = -75; // 75%
int useUpLimit = cfg.useUploadLimit();
if ( useUpLimit >= 1) {
uploadLimit = cfg.uploadLimit() * 1000;
} else if (useUpLimit == 0) {
uploadLimit = 0;
}
_engine->setNetworkLimits(uploadLimit, downloadLimit);
}
@ -955,20 +801,11 @@ void Folder::slotSyncFinished(bool success)
}
bubbleUpSyncResult();
bool anotherSyncNeeded = false;
if (_engine) {
anotherSyncNeeded = _engine->isAnotherSyncNeeded();
_engine.reset(0);
}
bool anotherSyncNeeded = _engine->isAnotherSyncNeeded();
// _watcher->setEventsEnabledDelayed(2000);
// This is for sync state calculation
_stateLastSyncItemsWithError = _stateLastSyncItemsWithErrorNew;
_stateLastSyncItemsWithErrorNew.clear();
_stateTaintedFolders.clear(); // heuristic: assume the sync had been done, new file watches needed to taint dirs
if (_csyncError) {
_syncResult.setStatus(SyncResult::Error);
qDebug() << " ** error Strings: " << _errors;
@ -1064,10 +901,6 @@ void Folder::slotTransmissionProgress(const ProgressInfo &pi)
// a item is completed: count the errors and forward to the ProgressDispatcher
void Folder::slotItemCompleted(const SyncFileItem &item, const PropagatorJob& job)
{
if (showErrorInSocketApi(item)) {
_stateLastSyncItemsWithErrorNew.insert(item._file);
}
if (Progress::isWarningKind(item._status)) {
// Count all error conditions.
_syncResult.setWarnCount(_syncResult.warnCount()+1);
@ -1075,11 +908,6 @@ void Folder::slotItemCompleted(const SyncFileItem &item, const PropagatorJob& jo
emit ProgressDispatcher::instance()->itemCompleted(alias(), item, job);
}
void Folder::slotSyncItemDiscovered(const SyncFileItem & item)
{
emit ProgressDispatcher::instance()->syncItemDiscovered(alias(), item);
}
void Folder::slotNewBigFolderDiscovered(const QString &newF)
{
auto newFolder = newF;

View file

@ -21,21 +21,13 @@
#include "progressdispatcher.h"
#include "syncjournaldb.h"
#include "clientproxy.h"
#include "syncfilestatus.h"
#include "networkjobs.h"
#include <csync.h>
#include <QDir>
#include <QHash>
#include <QSet>
#include <QObject>
#include <QStringList>
#include <QDebug>
#include <QTimer>
#include <qelapsedtimer.h>
class QThread;
class QSettings;
@ -87,7 +79,7 @@ class Folder : public QObject
Q_OBJECT
public:
Folder(const FolderDefinition& definition, QObject* parent = 0L);
Folder(const FolderDefinition& definition, AccountState* accountState, QObject* parent = 0L);
~Folder();
@ -97,8 +89,7 @@ public:
/**
* The account the folder is configured on.
*/
void setAccountState( AccountState *account );
AccountState* accountState() const;
AccountState* accountState() const { return _accountState.data(); }
/**
* alias or nickname
@ -182,8 +173,7 @@ public:
// Used by the Socket API
SyncJournalDb *journalDb() { return &_journal; }
bool estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s);
SyncEngine &syncEngine() { return *_engine; }
RequestEtagJob *etagJob() { return _requestEtagJob; }
qint64 msecSinceLastSync() const { return _timeSinceLastSyncDone.elapsed(); }
@ -257,18 +247,15 @@ private slots:
void slotFolderDiscovered(bool local, QString folderName);
void slotTransmissionProgress(const ProgressInfo& pi);
void slotItemCompleted(const SyncFileItem&, const PropagatorJob&);
void slotSyncItemDiscovered(const SyncFileItem & item);
void slotRunEtagJob();
void etagRetreived(const QString &);
void etagRetreivedFromSyncEngine(const QString &);
void slotAboutToPropagate(SyncFileItemVector& );
void slotThreadTreeWalkResult(const SyncFileItemVector& ); // after sync is done
void slotEmitFinishedDelayed();
void watcherSlot(QString);
void slotNewBigFolderDiscovered(const QString &);
private:
@ -278,10 +265,19 @@ private:
void checkLocalPath();
void createGuiLog(const QString& filename, SyncFileStatus status, int count,
enum LogStatus {
LogStatusRemove,
LogStatusRename,
LogStatusMove,
LogStatusNew,
LogStatusError,
LogStatusUpdated
};
void createGuiLog(const QString& filename, LogStatus status, int count,
const QString& renameTarget = QString::null );
QPointer<AccountState> _accountState;
AccountStatePtr _accountState;
FolderDefinition _definition;
SyncResult _syncResult;
@ -306,15 +302,6 @@ private:
/// Reset when no follow-up is requested.
int _consecutiveFollowUpSyncs;
// SocketAPI: Cache files and folders that had errors so that they can
// get a red ERROR icon.
QSet<QString> _stateLastSyncItemsWithErrorNew; // gets moved to _stateLastSyncItemsWithError at end of sync
QSet<QString> _stateLastSyncItemsWithError;
// SocketAPI: A folder is tained if we got a file watcher notification
// for it. It's displayed as EVAL.
QSet<QString> _stateTaintedFolders;
SyncJournalDb _journal;
ClientProxy _clientProxy;

View file

@ -32,7 +32,6 @@
#endif
#include <QMessageBox>
#include <QPointer>
#include <QtCore>
#include <QMutableSetIterator>
#include <QSet>
@ -50,7 +49,7 @@ FolderMan::FolderMan(QObject *parent) :
Q_ASSERT(!_instance);
_instance = this;
_socketApi = new SocketApi(this);
_socketApi.reset(new SocketApi);
ConfigFile cfg;
int polltime = cfg.remotePollInterval();
@ -89,9 +88,7 @@ void FolderMan::unloadFolder( Folder *f )
return;
}
if( _socketApi ) {
_socketApi->slotUnregisterPath(f->alias());
}
_socketApi->slotUnregisterPath(f->alias());
if( _folderWatchers.contains(f->alias())) {
_folderWatchers.remove(f->alias());
@ -108,6 +105,8 @@ void FolderMan::unloadFolder( Folder *f )
this, SLOT(slotForwardFolderSyncStateChange()));
disconnect(f, SIGNAL(syncPausedChanged(Folder*,bool)),
this, SLOT(slotFolderSyncPaused(Folder*,bool)));
disconnect(&f->syncEngine().syncFileStatusTracker(), SIGNAL(fileStatusChanged(const QString &, SyncFileStatus)),
_socketApi.data(), SLOT(slotFileStatusChanged(const QString &, SyncFileStatus)));
}
int FolderMan::unloadAndDeleteAllFolders()
@ -147,15 +146,10 @@ void FolderMan::registerFolderMonitor( Folder *folder )
// is lost this way, but we do not need it for the current implementation.
connect(fw, SIGNAL(pathChanged(QString)), folder, SLOT(slotWatchedPathChanged(QString)));
_folderWatchers.insert(folder->alias(), fw);
// This is at the moment only for the behaviour of the SocketApi.
connect(fw, SIGNAL(pathChanged(QString)), folder, SLOT(watcherSlot(QString)));
}
// register the folder with the socket API
if( _socketApi ) {
_socketApi->slotRegisterPath(folder->alias());
}
_socketApi->slotRegisterPath(folder->alias());
}
void FolderMan::addMonitorPath( const QString& alias, const QString& path )
@ -206,9 +200,8 @@ int FolderMan::setupFolders()
foreach (const auto& folderAlias, settings->childGroups()) {
FolderDefinition folderDefinition;
if (FolderDefinition::load(*settings, folderAlias, &folderDefinition)) {
Folder* f = addFolderInternal(folderDefinition);
Folder* f = addFolderInternal(folderDefinition, account.data());
if (f) {
f->setAccountState( account.data() );
slotScheduleSync(f);
emit folderSyncStateChange(f);
}
@ -307,7 +300,7 @@ QString FolderMan::escapeAlias( const QString& alias )
SocketApi *FolderMan::socketApi()
{
return this->_socketApi;
return this->_socketApi.data();
}
QString FolderMan::unescapeAlias( const QString& alias )
@ -395,10 +388,8 @@ Folder* FolderMan::setupFolderFromOldConfigFile(const QString &file, AccountStat
folderDefinition.paused = paused;
folderDefinition.ignoreHiddenFiles = ignoreHiddenFiles();
folder = addFolderInternal(folderDefinition);
folder = addFolderInternal(folderDefinition, accountState);
if (folder) {
folder->setAccountState(accountState);
QStringList blackList = settings.value( QLatin1String("blackList")).toStringList();
if (!blackList.empty()) {
//migrate settings
@ -478,21 +469,17 @@ void FolderMan::slotScheduleSync( Folder *f )
}
auto alias = f->alias();
if( _socketApi ) {
// We want the SocketAPI to already now update so that it can show the EVAL icon
// for files/folders. Only do this when not syncing, else we might get a lot
// of those notifications.
_socketApi->slotUpdateFolderView(f);
}
// We want the SocketAPI to already now update so that it can show the EVAL icon
// for files/folders. Only do this when not syncing, else we might get a lot
// of those notifications.
_socketApi->slotUpdateFolderView(f);
qDebug() << "Schedule folder " << alias << " to sync!";
if( ! _scheduleQueue.contains(f) ) {
if( !f->canSync() ) {
qDebug() << "Folder is not ready to sync, not scheduled!";
if( _socketApi ) {
_socketApi->slotUpdateFolderView(f);
}
_socketApi->slotUpdateFolderView(f);
return;
}
f->prepareToSync();
@ -786,9 +773,8 @@ Folder* FolderMan::addFolder(AccountState* accountState, const FolderDefinition&
return 0;
}
auto folder = addFolderInternal(folderDefinition);
if(folder && accountState) {
folder->setAccountState(accountState);
auto folder = addFolderInternal(folderDefinition, accountState);
if(folder) {
folder->saveToSettings();
emit folderSyncStateChange(folder);
emit folderListChanged(_folderMap);
@ -796,9 +782,9 @@ Folder* FolderMan::addFolder(AccountState* accountState, const FolderDefinition&
return folder;
}
Folder* FolderMan::addFolderInternal(const FolderDefinition& folderDefinition)
Folder* FolderMan::addFolderInternal(const FolderDefinition& folderDefinition, AccountState* accountState)
{
auto folder = new Folder(folderDefinition, this );
auto folder = new Folder(folderDefinition, accountState, this );
qDebug() << "Adding folder to Folder Map " << folder;
_folderMap[folder->alias()] = folder;
@ -812,6 +798,8 @@ Folder* FolderMan::addFolderInternal(const FolderDefinition& folderDefinition)
connect(folder, SIGNAL(syncFinished(SyncResult)), SLOT(slotFolderSyncFinished(SyncResult)));
connect(folder, SIGNAL(syncStateChange()), SLOT(slotForwardFolderSyncStateChange()));
connect(folder, SIGNAL(syncPausedChanged(Folder*,bool)), SLOT(slotFolderSyncPaused(Folder*,bool)));
connect(&folder->syncEngine().syncFileStatusTracker(), SIGNAL(fileStatusChanged(const QString &, SyncFileStatus)),
_socketApi.data(), SLOT(slotFileStatusChanged(const QString &, SyncFileStatus)));
registerFolderMonitor(folder);
return folder;
@ -935,15 +923,13 @@ bool FolderMan::startFromScratch( const QString& localFolder )
}
// Disconnect the socket api from the database to avoid that locking of the
// db file does not allow to move this dir.
if( _socketApi ) {
Folder *f = folderForPath(localFolder);
if(f) {
if( localFolder.startsWith(f->path()) ) {
_socketApi->slotUnregisterPath(f->alias());
}
f->journalDb()->close();
f->slotTerminateSync(); // Normally it should not be running, but viel hilft viel
Folder *f = folderForPath(localFolder);
if(f) {
if( localFolder.startsWith(f->path()) ) {
_socketApi->slotUnregisterPath(f->alias());
}
f->journalDb()->close();
f->slotTerminateSync(); // Normally it should not be running, but viel hilft viel
}
// Make a backup of the folder/file.

View file

@ -19,7 +19,6 @@
#include <QObject>
#include <QQueue>
#include <QList>
#include <QPointer>
#include "folder.h"
#include "folderwatcher.h"
@ -202,7 +201,7 @@ private:
/** Adds a new folder, does not add it to the account settings and
* does not set an account on the new folder.
*/
Folder* addFolderInternal(const FolderDefinition& folderDefinition);
Folder* addFolderInternal(const FolderDefinition& folderDefinition, AccountState* accountState);
/* unloads a folder object, does not delete it */
void unloadFolder( Folder * );
@ -228,7 +227,7 @@ private:
QPointer<RequestEtagJob> _currentEtagJob; // alias of Folder running the current RequestEtagJob
QMap<QString, FolderWatcher*> _folderWatchers;
QPointer<SocketApi> _socketApi;
QScopedPointer<SocketApi> _socketApi;
/** The aliases of folders that shall be synced. */
QQueue<Folder*> _scheduleQueue;

View file

@ -35,6 +35,7 @@
#include "creds/abstractcredentials.h"
#include <QDesktopServices>
#include <QDir>
#include <QMessageBox>
#include <QSignalMapper>
@ -350,7 +351,7 @@ void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *men
bool onePaused = false;
bool allPaused = true;
foreach (Folder* folder, folderMan->map()) {
if (folder->accountState() != accountState) {
if (folder->accountState() != accountState.data()) {
continue;
}

View file

@ -35,7 +35,6 @@ class ShareDialog;
class Application;
class LogBrowser;
class AccountState;
typedef QSharedPointer<AccountState> AccountStatePtr;
/**
* @brief The ownCloudGui class

View file

@ -14,6 +14,7 @@
#include "selectivesyncdialog.h"
#include "folder.h"
#include "account.h"
#include "excludedfiles.h"
#include "networkjobs.h"
#include "theme.h"
#include "folderman.h"
@ -176,21 +177,11 @@ void SelectiveSyncTreeView::slotUpdateDirectories(QStringList list)
pathToRemove.append('/');
// Check for excludes.
//
// We would like to use Folder::isFileExcluded, but the folder doesn't
// exist yet. So we just create one temporarily...
FolderDefinition def;
def.localPath = pathToRemove;
def.ignoreHiddenFiles = FolderMan::instance()->ignoreHiddenFiles();
Folder f(def);
QMutableListIterator<QString> it(list);
while (it.hasNext()) {
it.next();
QString path = it.value();
path.remove(pathToRemove);
if (f.isFileExcludedRelative(path)) {
if (ExcludedFiles::instance().isExcluded(it.value(), pathToRemove, FolderMan::instance()->ignoreHiddenFiles()))
it.remove();
}
}
// Since / cannot be in the blacklist, expand it to the actual

View file

@ -23,6 +23,7 @@
#include "utility.h"
#include "theme.h"
#include "syncjournalfilerecord.h"
#include "syncengine.h"
#include "syncfileitem.h"
#include "filesystem.h"
#include "version.h"
@ -113,10 +114,6 @@ SocketApi::SocketApi(QObject* parent)
// folder watcher
connect(FolderMan::instance(), SIGNAL(folderSyncStateChange(Folder*)), this, SLOT(slotUpdateFolderView(Folder*)));
connect(ProgressDispatcher::instance(), SIGNAL(itemCompleted(QString, const SyncFileItem &, const PropagatorJob &)),
SLOT(slotItemCompleted(QString, const SyncFileItem &)));
connect(ProgressDispatcher::instance(), SIGNAL(syncItemDiscovered(QString, const SyncFileItem &)),
this, SLOT(slotSyncItemDiscovered(QString, const SyncFileItem &)));
}
SocketApi::~SocketApi()
@ -195,24 +192,8 @@ void SocketApi::slotRegisterPath( const QString& alias )
void SocketApi::slotUnregisterPath( const QString& alias )
{
Folder *f = FolderMan::instance()->folder(alias);
if (f) {
if (f)
broadcastMessage(QLatin1String("UNREGISTER_PATH"), f->path(), QString::null, true );
if( _dbQueries.contains(f)) {
auto h = _dbQueries[f];
if( h ) {
h->finish();
}
_dbQueries.remove(f);
}
if( _openDbs.contains(f) ) {
auto db = _openDbs[f];
if( db ) {
db->close();
}
_openDbs.remove(f);
}
}
}
void SocketApi::slotUpdateFolderView(Folder *f)
@ -231,7 +212,7 @@ void SocketApi::slotUpdateFolderView(Folder *f)
f->syncResult().status() == SyncResult::SetupError ) {
broadcastMessage(QLatin1String("STATUS"), f->path() ,
this->fileStatus(f, "").toSocketAPIString());
f->syncEngine().syncFileStatusTracker().fileStatus("").toSocketAPIString());
broadcastMessage(QLatin1String("UPDATE_VIEW"), f->path() );
} else {
@ -240,48 +221,11 @@ void SocketApi::slotUpdateFolderView(Folder *f)
}
}
void SocketApi::slotItemCompleted(const QString &folder, const SyncFileItem &item)
void SocketApi::slotFileStatusChanged(const QString& systemFileName, SyncFileStatus fileStatus)
{
if (_listeners.isEmpty()) {
return;
}
Folder *f = FolderMan::instance()->folder(folder);
if (!f) {
return;
}
auto status = this->fileStatus(f, item.destination());
const QString path = f->path() + item.destination();
broadcastMessage(QLatin1String("STATUS"), path, status.toSocketAPIString());
broadcastMessage(QLatin1String("STATUS"), systemFileName, fileStatus.toSocketAPIString());
}
void SocketApi::slotSyncItemDiscovered(const QString &folder, const SyncFileItem &item)
{
if (_listeners.isEmpty()) {
return;
}
Folder *f = FolderMan::instance()->folder(folder);
if (!f) {
return;
}
QString path = f->path() + item.destination();
// the trailing slash for directories must be appended as the filenames coming in
// from the plugins have that too. Otherwise the matching entry item is not found
// in the plugin.
if( item._type == SyncFileItem::Type::Directory ) {
path += QLatin1Char('/');
}
const QString command = QLatin1String("SYNC");
broadcastMessage(QLatin1String("STATUS"), path, command);
}
void SocketApi::sendMessage(QIODevice *socket, const QString& message, bool doWait)
{
DEBUG << "Sending message: " << message;
@ -346,7 +290,7 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QIODevice*
statusString = QLatin1String("NOP");
} else {
const QString file = QDir::cleanPath(argument).mid(syncFolder->cleanPath().length()+1);
SyncFileStatus fileStatus = this->fileStatus(syncFolder, file);
SyncFileStatus fileStatus = syncFolder->syncEngine().syncFileStatusTracker().fileStatus(file);
statusString = fileStatus.toSocketAPIString();
}
@ -383,11 +327,10 @@ void SocketApi::command_SHARE(const QString& localFile, QIODevice* socket)
} else {
const QString localFileClean = QDir::cleanPath(localFile);
const QString file = localFileClean.mid(shareFolder->cleanPath().length()+1);
SyncFileStatus fileStatus = this->fileStatus(shareFolder, file);
SyncFileStatus fileStatus = shareFolder->syncEngine().syncFileStatusTracker().fileStatus(file);
// Verify the file is on the server (to our knowledge of course)
if (fileStatus.tag() != SyncFileStatus::STATUS_UPTODATE &&
fileStatus.tag() != SyncFileStatus::STATUS_UPDATED) {
if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) {
const QString message = QLatin1String("SHARE:NOTSYNCED:")+QDir::toNativeSeparators(localFile);
sendMessage(socket, message);
return;
@ -402,7 +345,7 @@ void SocketApi::command_SHARE(const QString& localFile, QIODevice* socket)
return;
}
SyncJournalFileRecord rec = dbFileRecord_capi(shareFolder, localFileClean);
SyncJournalFileRecord rec = shareFolder->journalDb()->getFileRecord(localFileClean);
bool allowReshare = true; // lets assume the good
if( rec.isValid() ) {
@ -439,11 +382,10 @@ void SocketApi::command_SHARE_STATUS(const QString &localFile, QIODevice *socket
sendMessage(socket, message);
} else {
const QString file = QDir::cleanPath(localFile).mid(shareFolder->cleanPath().length()+1);
SyncFileStatus fileStatus = this->fileStatus(shareFolder, file);
SyncFileStatus fileStatus = shareFolder->syncEngine().syncFileStatusTracker().fileStatus(file);
// Verify the file is on the server (to our knowledge of course)
if (fileStatus.tag() != SyncFileStatus::STATUS_UPTODATE &&
fileStatus.tag() != SyncFileStatus::STATUS_UPDATED) {
if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) {
const QString message = QLatin1String("SHARE_STATUS:NOTSYNCED:")+QDir::toNativeSeparators(localFile);
sendMessage(socket, message);
return;
@ -494,227 +436,4 @@ QString SocketApi::buildRegisterPathMessage(const QString& path)
return message;
}
SqlQuery* SocketApi::getSqlQuery( Folder *folder )
{
if( !folder ) {
return 0;
}
if( _dbQueries.contains(folder) ) {
return _dbQueries[folder].data();
}
/* No valid sql query object yet for this folder */
int rc;
const QString sql("SELECT inode, modtime, type, md5, fileid, remotePerm FROM "
"metadata WHERE phash=?1");
QString dbFileName = folder->journalDb()->databaseFilePath();
QFileInfo fi(dbFileName);
if( fi.exists() ) {
auto db = QSharedPointer<SqlDatabase>::create();
if( db && db->openReadOnly(dbFileName) ) {
_openDbs.insert(folder, db);
QSharedPointer<SqlQuery> query(new SqlQuery(*db));
rc = query->prepare(sql);
if( rc != SQLITE_OK ) {
qDebug() << "Unable to prepare the query statement:" << rc;
return 0; // do not insert into hash
}
_dbQueries.insert( folder, query);
return query.data();
} else {
qDebug() << "Unable to open db" << dbFileName;
}
} else {
qDebug() << Q_FUNC_INFO << "Journal to query does not yet exist.";
}
return 0;
}
SyncJournalFileRecord SocketApi::dbFileRecord_capi( Folder *folder, QString fileName )
{
if( !(folder && folder->journalDb()) ) {
return SyncJournalFileRecord();
}
if( fileName.startsWith( folder->path() )) {
fileName.remove(0, folder->path().length());
}
// remove trailing slash
if( fileName.endsWith( QLatin1Char('/') ) ) {
fileName.truncate(fileName.length()-1);
}
SqlQuery *query = getSqlQuery(folder);
SyncJournalFileRecord rec;
if( query ) {
qlonglong phash = SyncJournalDb::getPHash( fileName );
query->bindValue(1, phash);
// int column_count = sqlite3_column_count(stmt);
if (query->next()) {
rec._path = fileName;
rec._inode = query->int64Value(0);
rec._modtime = Utility::qDateTimeFromTime_t( query->int64Value(1));
rec._type = query->intValue(2);
rec._etag = query->baValue(3);
rec._fileId = query->baValue(4);
rec._remotePerm = query->baValue(5);
}
query->reset_and_clear_bindings();
}
return rec;
}
/**
* Get status about a single file.
*/
SyncFileStatus SocketApi::fileStatus(Folder *folder, const QString& systemFileName)
{
QString file = folder->path();
QString fileName = systemFileName.normalized(QString::NormalizationForm_C);
QString fileNameSlash = fileName;
if(fileName != QLatin1String("/") && !fileName.isEmpty()) {
file += fileName;
}
if( fileName.endsWith(QLatin1Char('/')) ) {
fileName.truncate(fileName.length()-1);
qDebug() << "Removed trailing slash: " << fileName;
} else {
fileNameSlash += QLatin1Char('/');
}
const QFileInfo fi(file);
if( !FileSystem::fileExists(file, fi) ) {
qDebug() << "OO File " << file << " is not existing";
return SyncFileStatus(SyncFileStatus::STATUS_STAT_ERROR);
}
// file is ignored?
// Qt considers .lnk files symlinks on Windows so we need to work
// around that here.
if( fi.isSymLink()
#ifdef Q_OS_WIN
&& fi.suffix() != "lnk"
#endif
) {
return SyncFileStatus(SyncFileStatus::STATUS_IGNORE);
}
csync_ftw_type_e type = CSYNC_FTW_TYPE_FILE;
if( fi.isDir() ) {
type = CSYNC_FTW_TYPE_DIR;
}
// Is it excluded?
if( folder->isFileExcludedRelative(fileName) ) {
return SyncFileStatus(SyncFileStatus::STATUS_IGNORE);
}
// Error if it is in the selective sync blacklist
foreach(const auto &s, folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList)) {
if (fileNameSlash.startsWith(s)) {
return SyncFileStatus(SyncFileStatus::STATUS_ERROR);
}
}
SyncFileStatus status(SyncFileStatus::STATUS_NONE);
SyncJournalFileRecord rec = dbFileRecord_capi(folder, fileName );
if (folder->estimateState(fileName, type, &status)) {
qDebug() << "Folder estimated status for" << fileName << "to" << status.toSocketAPIString();
} else if (fileName == "") {
// sync folder itself
switch (folder->syncResult().status()) {
case SyncResult::Undefined:
case SyncResult::NotYetStarted:
case SyncResult::SyncPrepare:
case SyncResult::SyncRunning:
status.set(SyncFileStatus::STATUS_EVAL);
return status;
case SyncResult::Success:
case SyncResult::Problem:
status.set(SyncFileStatus::STATUS_UPTODATE);
return status;
case SyncResult::Error:
case SyncResult::SetupError:
case SyncResult::SyncAbortRequested:
status.set(SyncFileStatus::STATUS_ERROR);
return status;
case SyncResult::Paused:
status.set(SyncFileStatus::STATUS_IGNORE);
return status;
}
} else if (type == CSYNC_FTW_TYPE_DIR) {
if (rec.isValid()) {
status.set(SyncFileStatus::STATUS_UPTODATE);
} else {
qDebug() << "Could not determine state for folder" << fileName << "will set STATUS_NEW";
status.set(SyncFileStatus::STATUS_NEW);
}
} else if (type == CSYNC_FTW_TYPE_FILE) {
if (rec.isValid()) {
if( FileSystem::getModTime(fi.absoluteFilePath()) == Utility::qDateTimeToTime_t(rec._modtime) ) {
status.set(SyncFileStatus::STATUS_UPTODATE);
} else {
if (rec._remotePerm.isNull() || rec._remotePerm.contains("W") ) {
status.set(SyncFileStatus::STATUS_EVAL);
} else {
status.set(SyncFileStatus::STATUS_ERROR);
}
}
} else {
qDebug() << "Could not determine state for file" << fileName << "will set STATUS_NEW";
status.set(SyncFileStatus::STATUS_NEW);
}
}
if (rec.isValid()) {
if (rec._remotePerm.isNull()) {
// probably owncloud 6, that does not have permissions flag yet.
QString url = folder->remoteUrl().toString() + fileName;
if (url.contains( folder->accountState()->account()->davPath() + QLatin1String("Shared/") )) {
status.setSharedWithMe(true);
}
} else if (rec._remotePerm.contains("S")) {
status.setSharedWithMe(true);
}
}
if (status.tag() == SyncFileStatus::STATUS_NEW) {
// check the parent folder if it is shared and if it is allowed to create a file/dir within
QDir d( fi.path() );
auto parentPath = d.path();
auto dirRec = dbFileRecord_capi(folder, parentPath);
bool isDir = type == CSYNC_FTW_TYPE_DIR;
while( !d.isRoot() && !(d.exists() && dirRec.isValid()) ) {
d.cdUp(); // returns true if the dir exists.
parentPath = d.path();
// cut the folder path
dirRec = dbFileRecord_capi(folder, parentPath);
isDir = true;
}
if( dirRec.isValid() && !dirRec._remotePerm.isNull()) {
if( (isDir && !dirRec._remotePerm.contains("K"))
|| (!isDir && !dirRec._remotePerm.contains("C")) ) {
status.set(SyncFileStatus::STATUS_ERROR);
}
}
}
return status;
}
} // namespace OCC

View file

@ -17,7 +17,7 @@
#define SOCKETAPI_H
#include "syncfileitem.h"
#include "syncjournalfilerecord.h"
#include "syncfilestatus.h"
#include "ownsql.h"
#if defined(Q_OS_MAC)
@ -45,7 +45,7 @@ class SocketApi : public QObject
Q_OBJECT
public:
SocketApi(QObject* parent);
explicit SocketApi(QObject* parent = 0);
virtual ~SocketApi();
public slots:
@ -61,14 +61,9 @@ private slots:
void slotNewConnection();
void onLostConnection();
void slotReadSocket();
void slotItemCompleted(const QString &, const SyncFileItem &);
void slotSyncItemDiscovered(const QString &, const SyncFileItem &);
void slotFileStatusChanged(const QString& systemFileName, SyncFileStatus fileStatus);
private:
SyncFileStatus fileStatus(Folder *folder, const QString& systemFileName);
SyncJournalFileRecord dbFileRecord_capi( Folder *folder, QString fileName );
SqlQuery *getSqlQuery( Folder *folder );
void sendMessage(QIODevice* socket, const QString& message, bool doWait = false);
void broadcastMessage(const QString& verb, const QString &path, const QString &status = QString::null, bool doWait = false);
@ -84,8 +79,6 @@ private:
QList<QIODevice*> _listeners;
SocketApiServer _localServer;
QHash<Folder*, QSharedPointer<SqlQuery>> _dbQueries;
QHash<Folder*, QSharedPointer<SqlDatabase>> _openDbs;
};
}

View file

@ -56,6 +56,7 @@ set(libsync_SRCS
propagateremotemkdir.cpp
syncengine.cpp
syncfilestatus.cpp
syncfilestatustracker.cpp
syncjournaldb.cpp
syncjournalfilerecord.cpp
syncresult.cpp

View file

@ -21,7 +21,7 @@ namespace OCC {
class Account;
typedef QSharedPointer<Account> AccountPtr;
class AccountState;
typedef QSharedPointer<AccountState> AccountStatePtr;
typedef QExplicitlySharedDataPointer<AccountState> AccountStatePtr;
} // namespace OCC

View file

@ -14,8 +14,6 @@
#include "excludedfiles.h"
#include <QFileInfo>
#include <QReadLocker>
#include <QWriteLocker>
extern "C" {
#include "std/c_string.h"
@ -44,13 +42,11 @@ ExcludedFiles& ExcludedFiles::instance()
void ExcludedFiles::addExcludeFilePath(const QString& path)
{
QWriteLocker locker(&_mutex);
_excludeFiles.append(path);
}
bool ExcludedFiles::reloadExcludes()
{
QWriteLocker locker(&_mutex);
c_strlist_destroy(*_excludesPtr);
*_excludesPtr = NULL;
@ -62,16 +58,20 @@ bool ExcludedFiles::reloadExcludes()
return success;
}
CSYNC_EXCLUDE_TYPE ExcludedFiles::isExcluded(
const QString& fullPath,
const QString& relativePath,
bool ExcludedFiles::isExcluded(
const QString& filePath,
const QString& basePath,
bool excludeHidden) const
{
QFileInfo fi(fullPath);
if (!filePath.startsWith(basePath)) {
// Mark paths we're not responsible for as excluded...
return true;
}
QFileInfo fi(filePath);
if( excludeHidden ) {
if( fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.')) ) {
return CSYNC_FILE_EXCLUDE_HIDDEN;
return true;
}
}
@ -79,6 +79,11 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::isExcluded(
if (fi.isDir()) {
type = CSYNC_FTW_TYPE_DIR;
}
QReadLocker lock(&_mutex);
return csync_excluded_no_ctx(*_excludesPtr, relativePath.toUtf8(), type);
QString relativePath = filePath.mid(basePath.size());
if (relativePath.endsWith(QLatin1Char('/'))) {
relativePath.chop(1);
}
return csync_excluded_no_ctx(*_excludesPtr, relativePath.toUtf8(), type) != CSYNC_NOT_EXCLUDED;
}

View file

@ -16,7 +16,6 @@
#include "owncloudlib.h"
#include <QObject>
#include <QReadWriteLock>
#include <QStringList>
extern "C" {
@ -49,14 +48,12 @@ public:
/**
* Checks whether a file or directory should be excluded.
*
* @param fullPath the absolute path to the file
* @param relativePath path relative to the folder
*
* For directories, the paths must not contain a trailing /.
* @param filePath the absolute path to the file
* @param basePath folder path from which to apply exclude rules
*/
CSYNC_EXCLUDE_TYPE isExcluded(
const QString& fullPath,
const QString& relativePath,
bool isExcluded(
const QString& filePath,
const QString& basePath,
bool excludeHidden) const;
public slots:
@ -70,7 +67,6 @@ private:
// but the pointer can be in a csync_context so that it can itself also query the list.
c_strlist_t** _excludesPtr;
QStringList _excludeFiles;
mutable QReadWriteLock _mutex;
};
} // namespace OCC

View file

@ -239,8 +239,6 @@ signals:
const SyncFileItem & item,
const PropagatorJob & job);
void syncItemDiscovered(const QString &folder, const SyncFileItem & item);
protected:
void setProgressInfo(const QString& folder, const ProgressInfo& progress);

View file

@ -52,7 +52,7 @@ extern "C" const char *csync_instruction_str(enum csync_instructions_e instr);
namespace OCC {
bool SyncEngine::_syncRunning = false;
bool SyncEngine::s_anySyncRunning = false;
qint64 SyncEngine::minimumFileAgeForUpload = 2000;
@ -60,6 +60,7 @@ SyncEngine::SyncEngine(AccountPtr account, const QString& localPath,
const QUrl& remoteURL, const QString& remotePath, OCC::SyncJournalDb* journal)
: _account(account)
, _needsUpdate(false)
, _syncRunning(false)
, _localPath(localPath)
, _remoteUrl(remoteURL)
, _remotePath(remotePath)
@ -92,13 +93,14 @@ SyncEngine::SyncEngine(AccountPtr account, const QString& localPath,
csync_create(&_csync_ctx, localPath.toUtf8().data(), url_string.toUtf8().data());
csync_init(_csync_ctx);
_excludedFiles.reset(new ExcludedFiles(&_csync_ctx->excludes));
_syncFileStatusTracker.reset(new SyncFileStatusTracker(this));
_thread.setObjectName("SyncEngine_Thread");
_thread.start();
}
SyncEngine::~SyncEngine()
{
abort();
_excludedFiles.reset();
csync_destroy(_csync_ctx);
_thread.quit();
@ -662,10 +664,11 @@ void SyncEngine::startSync()
}
}
Q_ASSERT(!s_anySyncRunning);
Q_ASSERT(!_syncRunning);
s_anySyncRunning = true;
_syncRunning = true;
Q_ASSERT(_csync_ctx);
_anotherSyncNeeded = false;
if (!QDir(_localPath).exists()) {
// No _tr, it should only occur in non-mirall
@ -741,6 +744,7 @@ void SyncEngine::startSync()
qDebug() << "#### Discovery start #################################################### >>";
_thread.start();
_discoveryMainThread = new DiscoveryMainThread(account());
_discoveryMainThread->setParent(this);
connect(this, SIGNAL(finished(bool)), _discoveryMainThread, SLOT(deleteLater()));
@ -1000,6 +1004,7 @@ void SyncEngine::finalize(bool success)
qDebug() << "CSync run took " << _stopWatch.addLapTime(QLatin1String("Sync Finished"));
_stopWatch.stop();
s_anySyncRunning = false;
_syncRunning = false;
emit finished(success);
@ -1301,33 +1306,14 @@ void SyncEngine::restoreOldFiles()
}
}
bool SyncEngine::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s)
SyncFileItem* SyncEngine::findSyncItem(const QString &fileName) const
{
Q_UNUSED(t);
QString pat(fn);
if( t == CSYNC_FTW_TYPE_DIR && ! fn.endsWith(QLatin1Char('/'))) {
pat.append(QLatin1Char('/'));
}
Q_FOREACH(const SyncFileItemPtr &item, _syncedItems) {
//qDebug() << Q_FUNC_INFO << fn << item->_status << item->_file << fn.startsWith(item->_file) << item->_file.startsWith(fn);
if (item->_file.startsWith(pat) ||
item->_file == fn || item->_renameTarget == fn /* the same directory or file */) {
if (item->_status == SyncFileItem::NormalError
|| item->_status == SyncFileItem::FatalError)
s->set(SyncFileStatus::STATUS_ERROR);
else if (item->_status == SyncFileItem::FileIgnored)
s->set(SyncFileStatus::STATUS_IGNORE);
else if (item->_status == SyncFileItem::Success)
s->set(SyncFileStatus::STATUS_UPDATED);
else
s->set(SyncFileStatus::STATUS_EVAL);
qDebug() << Q_FUNC_INFO << "Setting" << fn << "to" << s->toSocketAPIString();
return true;
}
// Directories will appear in this list as well, and will get their status set once all children have been propagated
if ((item->_file == fileName || item->_renameTarget == fileName))
return item.data();
}
return false;
return 0;
}
qint64 SyncEngine::timeSinceFileTouched(const QString& fn) const

View file

@ -35,7 +35,7 @@
#include "syncfileitem.h"
#include "progressdispatcher.h"
#include "utility.h"
#include "syncfilestatus.h"
#include "syncfilestatustracker.h"
#include "accountfwd.h"
#include "discoveryphase.h"
#include "checksums.h"
@ -69,19 +69,23 @@ public:
/* Abort the sync. Called from the main thread */
void abort();
bool isSyncRunning() const { return _syncRunning; }
/* Set the maximum size a folder can have without asking for confirmation
* -1 means infinite
*/
void setNewBigFolderSizeLimit(qint64 limit) { _newBigFolderSizeLimit = limit; }
bool ignoreHiddenFiles() const { return _csync_ctx->ignore_hidden_files; }
void setIgnoreHiddenFiles(bool ignore) { _csync_ctx->ignore_hidden_files = ignore; }
ExcludedFiles &excludedFiles() { return *_excludedFiles; }
Utility::StopWatch &stopWatch() { return _stopWatch; }
SyncFileStatusTracker &syncFileStatusTracker() { return *_syncFileStatusTracker; }
/* Return true if we detected that another sync is needed to complete the sync */
bool isAnotherSyncNeeded() { return _anotherSyncNeeded; }
bool estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s);
SyncFileItem* findSyncItem(const QString &fileName) const;
/** Get the ms since a file was touched, or -1 if it wasn't.
*
@ -91,7 +95,7 @@ public:
AccountPtr account() const;
SyncJournalDb *journal() const { return _journal; }
QString localPath() const { return _localPath; }
/**
* Minimum age, in milisecond, of a file that can be uploaded.
* Files more recent than that are not going to be uploaeded as they are considered
@ -168,7 +172,7 @@ private:
// cleanup and emit the finished signal
void finalize(bool success);
static bool _syncRunning; //true when one sync is running somewhere (for debugging)
static bool s_anySyncRunning; //true when one sync is running somewhere (for debugging)
// Must only be acessed during update and reconcile
QMap<QString, SyncFileItemPtr> _syncItemMap;
@ -180,6 +184,7 @@ private:
AccountPtr _account;
CSYNC *_csync_ctx;
bool _needsUpdate;
bool _syncRunning;
QString _localPath;
QUrl _remoteUrl;
QString _remotePath;
@ -208,6 +213,7 @@ private:
QScopedPointer<ProgressInfo> _progressInfo;
QScopedPointer<ExcludedFiles> _excludedFiles;
QScopedPointer<SyncFileStatusTracker> _syncFileStatusTracker;
Utility::StopWatch _stopWatch;
// maps the origin and the target of the folders that have been renamed

View file

@ -17,7 +17,7 @@
namespace OCC {
SyncFileStatus::SyncFileStatus()
:_tag(STATUS_NONE), _sharedWithMe(false)
:_tag(StatusNone), _sharedWithMe(false)
{
}
@ -53,30 +53,22 @@ QString SyncFileStatus::toSocketAPIString() const
switch(_tag)
{
case STATUS_NONE:
case StatusNone:
statusString = QLatin1String("NONE");
break;
case STATUS_EVAL:
case StatusSync:
statusString = QLatin1String("SYNC");
break;
case STATUS_NEW:
statusString = QLatin1String("NEW");
break;
case STATUS_IGNORE:
case StatusWarning:
// The protocol says IGNORE, but all implementations show a yellow warning sign.
statusString = QLatin1String("IGNORE");
break;
case STATUS_UPTODATE:
case STATUS_UPDATED:
case StatusUpToDate:
statusString = QLatin1String("OK");
break;
case STATUS_STAT_ERROR:
case STATUS_ERROR:
case StatusError:
statusString = QLatin1String("ERROR");
break;
default:
qWarning() << "This status should not be here:" << _tag;
Q_ASSERT(false);
statusString = QLatin1String("NONE");
}
if(_sharedWithMe) {
statusString += QLatin1String("+SWM");

View file

@ -28,18 +28,11 @@ class OWNCLOUDSYNC_EXPORT SyncFileStatus
{
public:
enum SyncFileStatusTag {
STATUS_NONE,
STATUS_EVAL,
STATUS_REMOVE,
STATUS_RENAME,
STATUS_MOVE,
STATUS_NEW,
STATUS_CONFLICT,
STATUS_IGNORE,
STATUS_UPTODATE,
STATUS_STAT_ERROR,
STATUS_ERROR,
STATUS_UPDATED
StatusNone,
StatusSync,
StatusWarning,
StatusUpToDate,
StatusError,
};
SyncFileStatus();

View file

@ -0,0 +1,204 @@
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
* Copyright (C) by Jocelyn Turcotte <jturcotte@woboq.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; version 2 of the License.
*
* 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 "syncfilestatustracker.h"
#include "syncengine.h"
#include "syncjournaldb.h"
#include "syncjournalfilerecord.h"
namespace OCC {
static SyncFileStatus::SyncFileStatusTag lookupProblem(const QString &pathToMatch, const std::map<QString, SyncFileStatus::SyncFileStatusTag> &problemMap)
{
auto lower = problemMap.lower_bound(pathToMatch);
for (auto it = lower; it != problemMap.cend(); ++it) {
const QString &problemPath = it->first;
SyncFileStatus::SyncFileStatusTag severity = it->second;
// qDebug() << Q_FUNC_INFO << pathToMatch << severity << problemPath;
if (problemPath == pathToMatch) {
return severity;
} else if (severity == SyncFileStatus::StatusError && problemPath.startsWith(pathToMatch) && problemPath.at(pathToMatch.size()) == '/') {
Q_ASSERT(!pathToMatch.endsWith('/'));
return SyncFileStatus::StatusWarning;
} else if (!problemPath.startsWith(pathToMatch)) {
// Starting at lower_bound we get the first path that is not smaller,
// since: "/a/" < "/a/aa" < "/a/aa/aaa" < "/a/ab/aba"
// If problemMap keys are ["/a/aa/aaa", "/a/ab/aba"] and pathToMatch == "/a/aa",
// lower_bound(pathToMatch) will point to "/a/aa/aaa", and the moment that
// problemPath.startsWith(pathToMatch) == false, we know that we've looked
// at everything that interest us.
break;
}
}
return SyncFileStatus::StatusNone;
}
/**
* Whether this item should get an ERROR icon through the Socket API.
*
* The Socket API should only present serious, permanent errors to the user.
* In particular SoftErrors should just retain their 'needs to be synced'
* icon as the problem is most likely going to resolve itself quickly and
* automatically.
*/
static inline bool showErrorInSocketApi(const SyncFileItem& item)
{
const auto status = item._status;
return status == SyncFileItem::NormalError
|| status == SyncFileItem::FatalError
|| item._hasBlacklistEntry;
}
static inline bool showWarningInSocketApi(const SyncFileItem& item)
{
const auto status = item._status;
return status == SyncFileItem::FileIgnored
|| status == SyncFileItem::Conflict
|| status == SyncFileItem::Restoration;
}
SyncFileStatusTracker::SyncFileStatusTracker(SyncEngine *syncEngine)
: _syncEngine(syncEngine)
{
connect(syncEngine, SIGNAL(aboutToPropagate(SyncFileItemVector&)),
this, SLOT(slotAboutToPropagate(SyncFileItemVector&)));
connect(syncEngine, SIGNAL(itemCompleted(const SyncFileItem&, const PropagatorJob&)),
this, SLOT(slotItemCompleted(const SyncFileItem&)));
}
SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& systemFileName)
{
QString fileName = systemFileName.normalized(QString::NormalizationForm_C);
if( fileName.endsWith(QLatin1Char('/')) ) {
fileName.truncate(fileName.length()-1);
qDebug() << "Removed trailing slash: " << fileName;
}
// The SyncEngine won't notify us at all for CSYNC_FILE_SILENTLY_EXCLUDED
// and CSYNC_FILE_EXCLUDE_AND_REMOVE excludes. Even though it's possible
// that the status of CSYNC_FILE_EXCLUDE_LIST excludes will change if the user
// update the exclude list at runtime and doing it statically here removes
// our ability to notify changes through the fileStatusChanged signal,
// it's an acceptable compromize to treat all exclude types the same.
if( _syncEngine->excludedFiles().isExcluded(_syncEngine->localPath() + fileName, _syncEngine->localPath(), _syncEngine->ignoreHiddenFiles()) )
return SyncFileStatus(SyncFileStatus::StatusWarning);
SyncFileItem* item = _syncEngine->findSyncItem(fileName);
if (item)
return fileStatus(*item);
// If we're not currently syncing that file, look it up in the database to know if it's shared
SyncJournalFileRecord rec = _syncEngine->journal()->getFileRecord(fileName);
if (rec.isValid())
return fileStatus(rec.toSyncFileItem());
// Must be a new file, wait for the filesystem watcher to trigger a sync
return SyncFileStatus();
}
void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemVector& items)
{
std::map<QString, SyncFileStatus::SyncFileStatusTag> oldProblems;
std::swap(_syncProblems, oldProblems);
foreach (const SyncFileItemPtr &item, items) {
// qDebug() << Q_FUNC_INFO << "Investigating" << item->destination() << item->_status;
if (showErrorInSocketApi(*item))
_syncProblems[item->_file] = SyncFileStatus::StatusError;
else if (showWarningInSocketApi(*item))
_syncProblems[item->_file] = SyncFileStatus::StatusWarning;
emit fileStatusChanged(getSystemDestination(*item), fileStatus(*item));
}
// Make sure to push any status that might have been resolved indirectly since the last sync
// (like an error file being deleted from disk)
for (auto it = _syncProblems.begin(); it != _syncProblems.end(); ++it)
oldProblems.erase(it->first);
for (auto it = oldProblems.begin(); it != oldProblems.end(); ++it) {
const QString &path = it->first;
SyncFileStatus::SyncFileStatusTag severity = it->second;
if (severity == SyncFileStatus::StatusError)
invalidateParentPaths(path);
emit fileStatusChanged(_syncEngine->localPath() + path, fileStatus(path));
}
}
void SyncFileStatusTracker::slotItemCompleted(const SyncFileItem &item)
{
// qDebug() << Q_FUNC_INFO << item.destination() << item._status;
if (showErrorInSocketApi(item)) {
_syncProblems[item._file] = SyncFileStatus::StatusError;
invalidateParentPaths(item.destination());
} else if (showWarningInSocketApi(item)) {
_syncProblems[item._file] = SyncFileStatus::StatusWarning;
} else {
// There is currently no situation where an error status set during discovery/update is fixed by propagation.
Q_ASSERT(_syncProblems.find(item._file) == _syncProblems.end());
}
emit fileStatusChanged(getSystemDestination(item), fileStatus(item));
}
SyncFileStatus SyncFileStatusTracker::fileStatus(const SyncFileItem& item)
{
// Hack to know if the item was taken from the sync engine (Sync), or from the database (UpToDate)
bool waitingForPropagation = item._direction != SyncFileItem::None && item._status == SyncFileItem::NoStatus;
SyncFileStatus status(SyncFileStatus::StatusUpToDate);
if (waitingForPropagation) {
status.set(SyncFileStatus::StatusSync);
} else if (showErrorInSocketApi(item)) {
status.set(SyncFileStatus::StatusError);
} else if (showWarningInSocketApi(item)) {
status.set(SyncFileStatus::StatusWarning);
} else {
// After a sync finished, we need to show the users issues from that last sync like the activity list does.
// Also used for parent directories showing a warning for an error child.
SyncFileStatus::SyncFileStatusTag problemStatus = lookupProblem(item.destination(), _syncProblems);
if (problemStatus != SyncFileStatus::StatusNone)
status.set(problemStatus);
}
if (item._remotePerm.contains("S"))
status.setSharedWithMe(true);
return status;
}
void SyncFileStatusTracker::invalidateParentPaths(const QString& path)
{
QStringList splitPath = path.split('/', QString::SkipEmptyParts);
for (int i = 0; i < splitPath.size(); ++i) {
QString parentPath = splitPath.mid(0, i).join('/');
emit fileStatusChanged(_syncEngine->localPath() + parentPath, fileStatus(parentPath));
}
}
QString SyncFileStatusTracker::getSystemDestination(const SyncFileItem& item)
{
QString systemFileName = _syncEngine->localPath() + item.destination();
// the trailing slash for directories must be appended as the filenames coming in
// from the plugins have that too. Otherwise the matching entry item is not found
// in the plugin.
if( item._type == SyncFileItem::Type::Directory ) {
systemFileName += QLatin1Char('/');
}
return systemFileName;
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
* Copyright (C) by Jocelyn Turcotte <jturcotte@woboq.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; version 2 of the License.
*
* 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.
*/
#ifndef SYNCFILESTATUSTRACKER_H
#define SYNCFILESTATUSTRACKER_H
#include "ownsql.h"
#include "syncfileitem.h"
#include "syncfilestatus.h"
#include <map>
namespace OCC {
class SyncEngine;
/**
* @brief Takes care of tracking the status of individual files as they
* go through the SyncEngine, to be reported as overlay icons in the shell.
* @ingroup libsync
*/
class OWNCLOUDSYNC_EXPORT SyncFileStatusTracker : public QObject
{
Q_OBJECT
public:
explicit SyncFileStatusTracker(SyncEngine* syncEngine);
SyncFileStatus fileStatus(const QString& systemFileName);
signals:
void fileStatusChanged(const QString& systemFileName, SyncFileStatus fileStatus);
private slots:
void slotAboutToPropagate(SyncFileItemVector& items);
void slotItemCompleted(const SyncFileItem& item);
private:
SyncFileStatus fileStatus(const SyncFileItem& item);
void invalidateParentPaths(const QString& path);
QString getSystemDestination(const SyncFileItem& syncEnginePath);
SyncEngine* _syncEngine;
std::map<QString, SyncFileStatus::SyncFileStatusTag> _syncProblems;
};
}
#endif

View file

@ -247,18 +247,6 @@ QString Utility::toCSyncScheme(const QString &urlStr)
return url.toString();
}
bool Utility::doesSetContainPrefix(const QSet<QString> &l, const QString &p) {
Q_FOREACH (const QString &setPath, l) {
//qDebug() << Q_FUNC_INFO << p << setPath << setPath.startsWith(p);
if (setPath.startsWith(p)) {
return true;
}
}
//qDebug() << "-> NOOOOO!!!" << p << l.count();
return false;
}
QString Utility::escape(const QString &in)
{
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)

View file

@ -40,11 +40,6 @@ namespace Utility
OWNCLOUDSYNC_EXPORT void setLaunchOnStartup(const QString &appName, const QString& guiName, bool launch);
OWNCLOUDSYNC_EXPORT qint64 freeDiskSpace(const QString &path);
OWNCLOUDSYNC_EXPORT QString toCSyncScheme(const QString &urlStr);
/** Like QLocale::toString(double, 'f', prec), but drops trailing zeros after the decimal point */
OWNCLOUDSYNC_EXPORT bool doesSetContainPrefix(const QSet<QString> &l, const QString &p);
/**
* @brief compactFormatDouble - formats a double value human readable.

View file

@ -15,6 +15,7 @@
#include "utility.h"
#include "folderman.h"
#include "account.h"
#include "accountstate.h"
using namespace OCC;
@ -52,10 +53,11 @@ private slots:
f.write("hello");
}
AccountStatePtr newAccountState(new AccountState(Account::create()));
FolderMan *folderman = FolderMan::instance();
QCOMPARE(folderman, &_fm);
QVERIFY(folderman->addFolder(0, folderDefinition(dir.path() + "/sub/ownCloud1")));
QVERIFY(folderman->addFolder(0, folderDefinition(dir.path() + "/ownCloud2")));
QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dir.path() + "/sub/ownCloud1")));
QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dir.path() + "/ownCloud2")));
// those should be allowed