diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ec15a250a6..9d4e66bcf4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -267,6 +267,7 @@ set(mirall_SRCS mirall/socketapi.cpp mirall/sslbutton.cpp mirall/syncrunfilelog.cpp + mirall/selectivesyncdialog.cpp ) diff --git a/src/mirall/accountsettings.cpp b/src/mirall/accountsettings.cpp index 6259d44936..4db0c2887a 100644 --- a/src/mirall/accountsettings.cpp +++ b/src/mirall/accountsettings.cpp @@ -26,6 +26,7 @@ #include "mirall/ignorelisteditor.h" #include "mirall/account.h" #include "mirall/quotainfo.h" +#include "selectivesyncdialog.h" #include "creds/abstractcredentials.h" #include @@ -77,6 +78,7 @@ AccountSettings::AccountSettings(QWidget *parent) : ui->_buttonRemove->setEnabled(false); ui->_buttonEnable->setEnabled(false); + ui->_buttonSelectiveSync->setEnabled(false); ui->_buttonAdd->setEnabled(true); QAction *resetFolderAction = new QAction(this); @@ -92,6 +94,7 @@ AccountSettings::AccountSettings(QWidget *parent) : connect(ui->_buttonRemove, SIGNAL(clicked()), this, SLOT(slotRemoveCurrentFolder())); connect(ui->_buttonEnable, SIGNAL(clicked()), this, SLOT(slotEnableCurrentFolder())); connect(ui->_buttonAdd, SIGNAL(clicked()), this, SLOT(slotAddFolder())); + connect(ui->_buttonSelectiveSync, SIGNAL(clicked()), this, SLOT(slotSelectiveSync())); connect(ui->modifyAccountButton, SIGNAL(clicked()), SLOT(slotOpenAccountWizard())); connect(ui->ignoredFilesButton, SIGNAL(clicked()), SLOT(slotIgnoreFilesEditor()));; @@ -152,6 +155,7 @@ void AccountSettings::slotFolderActivated( const QModelIndex& indx ) } ui->_buttonAdd->setEnabled(_account && _account->state() == Account::Connected); ui->_buttonEnable->setEnabled( isValid ); + ui->_buttonSelectiveSync->setEnabled( isValid ); if ( isValid ) { bool folderEnabled = _model->data( indx, FolderStatusDelegate::FolderSyncEnabled).toBool(); @@ -369,6 +373,20 @@ void AccountSettings::slotResetCurrentFolder() } } +void AccountSettings::slotSelectiveSync() +{ + QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); + if( selected.isValid() ) { + QString alias = _model->data( selected, FolderStatusDelegate::FolderAliasRole ).toString(); + FolderMan *folderMan = FolderMan::instance(); + Folder *f = folderMan->folder(alias); + if (f) { + (new SelectiveSyncDialog(f, this))->show(); + } + } + +} + void AccountSettings::slotDoubleClicked( const QModelIndex& indx ) { if( ! indx.isValid() ) return; diff --git a/src/mirall/accountsettings.h b/src/mirall/accountsettings.h index 4e64ea25e6..53c6ad8d51 100644 --- a/src/mirall/accountsettings.h +++ b/src/mirall/accountsettings.h @@ -81,6 +81,7 @@ protected slots: void slotFolderWizardRejected(); void slotOpenAccountWizard(); void slotHideProgress(); + void slotSelectiveSync(); private: QString shortenFilename( const QString& folder, const QString& file ) const; diff --git a/src/mirall/accountsettings.ui b/src/mirall/accountsettings.ui index 133dfaab0a..e62916ddd5 100644 --- a/src/mirall/accountsettings.ui +++ b/src/mirall/accountsettings.ui @@ -70,6 +70,13 @@ + + + + Selective Sync... + + + diff --git a/src/mirall/folder.h b/src/mirall/folder.h index 8a9d554112..52304c8818 100644 --- a/src/mirall/folder.h +++ b/src/mirall/folder.h @@ -121,6 +121,10 @@ public: SyncJournalDb *journalDb() { return &_journal; } CSYNC *csyncContext() { return _csync_ctx; } + QStringList selectiveSyncList() { return _selectiveSyncWhiteList; } + void setSelectiveSyncList(const QStringList &whiteList) + { _selectiveSyncWhiteList = whiteList; } + signals: void syncStateChange(); @@ -188,6 +192,7 @@ private: SyncResult _syncResult; QScopedPointer _engine; QStringList _errors; + QStringList _selectiveSyncWhiteList; bool _csyncError; bool _csyncUnavail; bool _wipeDb; diff --git a/src/mirall/folderman.cpp b/src/mirall/folderman.cpp index 5729c4d35a..554ea34b44 100644 --- a/src/mirall/folderman.cpp +++ b/src/mirall/folderman.cpp @@ -234,7 +234,7 @@ void FolderMan::terminateCurrentSync() #define PAR_O_TAG QLatin1String("__PAR_OPEN__") #define PAR_C_TAG QLatin1String("__PAR_CLOSE__") -QString FolderMan::escapeAlias( const QString& alias ) const +QString FolderMan::escapeAlias( const QString& alias ) { QString a(alias); @@ -312,6 +312,7 @@ Folder* FolderMan::setupFolderFromConfigFile(const QString &file) { QString backend = settings.value(QLatin1String("backend")).toString(); QString targetPath = settings.value( QLatin1String("targetPath")).toString(); bool paused = settings.value( QLatin1String("paused"), false).toBool(); + QStringList whiteList = settings.value( QLatin1String("whiteList")).toStringList(); // QString connection = settings.value( QLatin1String("connection") ).toString(); QString alias = unescapeAlias( escapedAlias ); @@ -326,7 +327,8 @@ Folder* FolderMan::setupFolderFromConfigFile(const QString &file) { } folder = new Folder( alias, path, targetPath, this ); - folder->setConfigFile(file); + folder->setConfigFile(cfgFile.absoluteFilePath()); + folder->setSelectiveSyncList(whiteList); qDebug() << "Adding folder to Folder Map " << folder; _folderMap[alias] = folder; if (paused) { @@ -359,7 +361,7 @@ void FolderMan::slotEnableFolder( const QString& alias, bool enable ) slotScheduleSync(alias); // FIXME: Use MirallConfigFile - QSettings settings(_folderConfigPath + QLatin1Char('/') + f->configFile(), QSettings::IniFormat); + QSettings settings(f->configFile(), QSettings::IniFormat); settings.beginGroup(escapeAlias(f->alias())); if (enable) { settings.remove("paused"); @@ -588,7 +590,7 @@ void FolderMan::removeFolder( const QString& alias ) f->setSyncEnabled(false); // remove the folder configuration - QFile file( _folderConfigPath + QLatin1Char('/') + f->configFile() ); + QFile file(f->configFile() ); if( file.exists() ) { qDebug() << "Remove folder config file " << file.fileName(); file.remove(); diff --git a/src/mirall/folderman.h b/src/mirall/folderman.h index c3a8d0012d..abfaf12534 100644 --- a/src/mirall/folderman.h +++ b/src/mirall/folderman.h @@ -84,6 +84,10 @@ public: void removeMonitorPath( const QString& alias, const QString& path ); void addMonitorPath( const QString& alias, const QString& path ); + // Escaping of the alias which is used in QSettings AND the file + // system, thus need to be escaped. + static QString escapeAlias( const QString& ); + signals: /** * signal to indicate a folder named by alias has changed its sync state. @@ -131,9 +135,6 @@ private: QString getBackupName( const QString& ) const; void registerFolderMonitor( Folder *folder ); - // Escaping of the alias which is used in QSettings AND the file - // system, thus need to be escaped. - QString escapeAlias( const QString& ) const; QString unescapeAlias( const QString& ) const; void removeFolder( const QString& ); diff --git a/src/mirall/logger.cpp b/src/mirall/logger.cpp index 0bc8d1ccb8..4ab9232d3f 100644 --- a/src/mirall/logger.cpp +++ b/src/mirall/logger.cpp @@ -50,7 +50,7 @@ Logger *Logger::instance() Logger::Logger( QObject* parent) : QObject(parent), _showTime(true), _doLogging(false), _doFileFlush(false), _logExpire(0) { - qInstallMessageHandler(mirallLogCatcher); +// qInstallMessageHandler(mirallLogCatcher); } Logger::~Logger() { diff --git a/src/mirall/selectivesyncdialog.cpp b/src/mirall/selectivesyncdialog.cpp new file mode 100644 index 0000000000..56d3999704 --- /dev/null +++ b/src/mirall/selectivesyncdialog.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 "selectivesyncdialog.h" +#include "folder.h" +#include "account.h" +#include "networkjobs.h" +#include "theme.h" +#include "folderman.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Mirall { + +SelectiveSyncDialog::SelectiveSyncDialog(Folder* folder, QWidget* parent, Qt::WindowFlags f) + : QDialog(parent, f), _folder(folder) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + _treeView = new QTreeWidget; + connect(_treeView, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(slotItemExpanded(QTreeWidgetItem*))); + connect(_treeView, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotItemChanged(QTreeWidgetItem*,int))); + layout->addWidget(_treeView); + QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal); + QPushButton *button; + button = buttonBox->addButton(QDialogButtonBox::Ok); + connect(button, SIGNAL(clicked()), this, SLOT(accept())); + button = buttonBox->addButton(QDialogButtonBox::Cancel); + connect(button, SIGNAL(clicked()), this, SLOT(reject())); + layout->addWidget(buttonBox); + + // Make sure we don't get crashes if the folder is destroyed while we are still open + connect(_folder, SIGNAL(destroyed(QObject*)), this, SLOT(deleteLater())); + + refreshFolders(); +} + +void SelectiveSyncDialog::refreshFolders() +{ + LsColJob *job = new LsColJob(AccountManager::instance()->account(), _folder->remotePath(), this); + connect(job, SIGNAL(directoryListing(QStringList)), + this, SLOT(slotUpdateDirectories(QStringList))); + job->start(); + _treeView->clear(); + +} + +static QTreeWidgetItem* findFirstChild(QTreeWidgetItem *parent, const QString& text) +{ + for (int i = 0; i < parent->childCount(); ++i) { + QTreeWidgetItem *child = parent->child(i); + if (child->text(0) == text) { + return child; + } + } + return 0; +} + +void SelectiveSyncDialog::recursiveInsert(QTreeWidgetItem* parent, QStringList pathTrail, + QString path) +{ + QFileIconProvider prov; + QIcon folderIcon = prov.icon(QFileIconProvider::Folder); + if (pathTrail.size() == 0) { + if (path.endsWith('/')) { + path.chop(1); + } + parent->setToolTip(0, path); + parent->setData(0, Qt::UserRole, path); + } else { + QTreeWidgetItem *item = findFirstChild(parent, pathTrail.first()); + if (!item) { + item = new QTreeWidgetItem(parent); + if (parent->checkState(0) == Qt::Checked) { + item->setCheckState(0, Qt::Checked); + } else if (parent->checkState(0) == Qt::Unchecked) { + item->setCheckState(0, Qt::Unchecked); + } else { + item->setCheckState(0, Qt::Unchecked); + foreach(const QString &str , _folder->selectiveSyncList()) { + if (str + "/" == path) { + item->setCheckState(0, Qt::Checked); + break; + } else if (str.startsWith(path)) { + item->setCheckState(0, Qt::PartiallyChecked); + } + } + } + item->setIcon(0, folderIcon); + item->setText(0, pathTrail.first()); +// item->setData(0, Qt::UserRole, pathTrail.first()); + item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); + } + + pathTrail.removeFirst(); + recursiveInsert(item, pathTrail, path); + } +} + +void SelectiveSyncDialog::slotUpdateDirectories(const QStringList &list) +{ + QScopedValueRollback isInserting(_inserting); + _inserting = true; + + QTreeWidgetItem *root = _treeView->topLevelItem(0); + if (!root) { + root = new QTreeWidgetItem(_treeView); + root->setText(0, _folder->alias()); + root->setIcon(0, Theme::instance()->applicationIcon()); + root->setData(0, Qt::UserRole, _folder->remotePath()); + if (_folder->selectiveSyncList().isEmpty() || _folder->selectiveSyncList().contains(QString())) { + root->setCheckState(0, Qt::Checked); + } else { + root->setCheckState(0, Qt::PartiallyChecked); + } + } + const QString folderPath = _folder->remoteUrl().path(); + foreach (QString path, list) { + path.remove(folderPath); + QStringList paths = path.split('/'); + if (paths.last().isEmpty()) paths.removeLast(); + recursiveInsert(root, paths, path); + } + root->setExpanded(true); +} + +void SelectiveSyncDialog::slotItemExpanded(QTreeWidgetItem *item) +{ + QString dir = item->data(0, Qt::UserRole).toString(); + LsColJob *job = new LsColJob(AccountManager::instance()->account(), dir, this); + connect(job, SIGNAL(directoryListing(QStringList)), + SLOT(slotUpdateDirectories(QStringList))); + job->start(); +} + +void SelectiveSyncDialog::slotItemChanged(QTreeWidgetItem *item, int col) +{ + if (col != 0 || _inserting) + return; + + if (item->checkState(0) == Qt::Checked) { + // If we are checked, check that we may need to check the parent as well if + // all the sibilings are also checked + QTreeWidgetItem *parent = item->parent(); + if (parent && parent->checkState(0) != Qt::Checked) { + bool hasUnchecked = false; + for (int i = 0; i < parent->childCount(); ++i) { + if (parent->child(i)->checkState(0) != Qt::Checked) { + hasUnchecked = true; + break; + } + } + if (!hasUnchecked) { + parent->setCheckState(0, Qt::Checked); + } else if (parent->checkState(0) == Qt::Unchecked) { + parent->setCheckState(0, Qt::PartiallyChecked); + } + } + // also check all the childs + for (int i = 0; i < item->childCount(); ++i) { + if (item->child(i)->checkState(0) != Qt::Checked) { + item->child(i)->setCheckState(0, Qt::Checked); + } + } + } + + if (item->checkState(0) == Qt::Unchecked) { + QTreeWidgetItem *parent = item->parent(); + if (parent && parent->checkState(0) != Qt::Unchecked) { + bool hasChecked = false; + for (int i = 0; i < parent->childCount(); ++i) { + if (parent->child(i)->checkState(0) != Qt::Unchecked) { + hasChecked = true; + break; + } + } + if (!hasChecked) { + parent->setCheckState(0, Qt::Unchecked); + } else if (parent->checkState(0) == Qt::Checked) { + parent->setCheckState(0, Qt::PartiallyChecked); + } + } + + // Uncheck all the childs + for (int i = 0; i < item->childCount(); ++i) { + if (item->child(i)->checkState(0) != Qt::Unchecked) { + item->child(i)->setCheckState(0, Qt::Unchecked); + } + } + } + + if (item->checkState(0) == Qt::PartiallyChecked) { + QTreeWidgetItem *parent = item->parent(); + if (parent && parent->checkState(0) != Qt::PartiallyChecked) { + parent->setCheckState(0, Qt::PartiallyChecked); + } + } +} + +QStringList SelectiveSyncDialog::createWhiteList(QTreeWidgetItem* root) const +{ + if (!root) { + root = _treeView->topLevelItem(0); + } + if (!root) return {}; + + switch(root->checkState(0)) { + case Qt::Checked: + return { root->data(0, Qt::UserRole).toString() }; + case Qt::Unchecked: + return {}; + case Qt::PartiallyChecked: + break; + } + + QStringList result; + for (int i = 0; i < root->childCount(); ++i) { + result += createWhiteList(root->child(i)); + } + return result; +} + +void SelectiveSyncDialog::accept() +{ + QStringList whiteList = createWhiteList(); + _folder->setSelectiveSyncList(whiteList); + + // FIXME: Use MirallConfigFile + QSettings settings(_folder->configFile(), QSettings::IniFormat); + settings.beginGroup(FolderMan::escapeAlias(_folder->alias())); + settings.setValue("whiteList", whiteList); + + QDialog::accept(); +} + + + +} + + + diff --git a/src/mirall/selectivesyncdialog.h b/src/mirall/selectivesyncdialog.h new file mode 100644 index 0000000000..a74a7aaa23 --- /dev/null +++ b/src/mirall/selectivesyncdialog.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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. + */ + +#pragma once +#include + +class QTreeWidgetItem; +class QTreeWidget; +namespace Mirall { + +class Folder; + +class SelectiveSyncDialog : public QDialog { + Q_OBJECT +public: + explicit SelectiveSyncDialog(Folder *folder, QWidget* parent = 0, Qt::WindowFlags f = 0); + + virtual void accept() Q_DECL_OVERRIDE; + QStringList createWhiteList(QTreeWidgetItem* root = 0) const; + +private slots: + void refreshFolders(); + void slotUpdateDirectories(const QStringList &); + void slotItemExpanded(QTreeWidgetItem *); + void slotItemChanged(QTreeWidgetItem*,int); + +private: + void recursiveInsert(QTreeWidgetItem* parent, QStringList pathTrail, QString path); + + Folder *_folder; + QTreeWidget *_treeView; + bool _inserting = false; // set to true when we are inserting new items on the list +}; + + +} \ No newline at end of file