/* * 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 #include #include namespace Mirall { SelectiveSyncTreeView::SelectiveSyncTreeView(Account *account, QWidget* parent) : QTreeWidget(parent), _inserting(false), _account(account) { _loading = new QLabel(tr("Loading ..."), this); connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(slotItemExpanded(QTreeWidgetItem*))); connect(this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotItemChanged(QTreeWidgetItem*,int))); header()->hide(); setSortingEnabled(true); sortByColumn(0, Qt::AscendingOrder); } void SelectiveSyncTreeView::refreshFolders() { LsColJob *job = new LsColJob(_account, _folderPath, this); connect(job, SIGNAL(directoryListing(QStringList)), this, SLOT(slotUpdateDirectories(QStringList))); job->start(); clear(); _loading->show(); _loading->move(10,10); } 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 SelectiveSyncTreeView::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 || parent->checkState(0) == Qt::PartiallyChecked) { item->setCheckState(0, Qt::Checked); foreach(const QString &str , _oldBlackList) { if (str == path) { item->setCheckState(0, Qt::Unchecked); break; } else if (str.startsWith(path)) { item->setCheckState(0, Qt::PartiallyChecked); } } } else if (parent->checkState(0) == Qt::Unchecked) { item->setCheckState(0, Qt::Unchecked); } 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 SelectiveSyncTreeView::slotUpdateDirectories(const QStringList&list) { QScopedValueRollback isInserting(_inserting); _inserting = true; _loading->hide(); QTreeWidgetItem *root = topLevelItem(0); if (!root) { root = new QTreeWidgetItem(this); root->setText(0, _rootName); root->setIcon(0, Theme::instance()->applicationIcon()); root->setData(0, Qt::UserRole, QString()); if (_oldBlackList.isEmpty()) { root->setCheckState(0, Qt::Checked); } else { root->setCheckState(0, Qt::PartiallyChecked); } } QUrl url = _account->davUrl(); QString pathToRemove = url.path(); if (!pathToRemove.endsWith('/')) { pathToRemove.append('/'); } pathToRemove.append(_folderPath); if (!_folderPath.isEmpty()) pathToRemove.append('/'); foreach (QString path, list) { path.remove(pathToRemove); QStringList paths = path.split('/'); if (paths.last().isEmpty()) paths.removeLast(); if (paths.isEmpty()) continue; if (!path.endsWith('/')) { path.append('/'); } recursiveInsert(root, paths, path); } root->setExpanded(true); } void SelectiveSyncTreeView::slotItemExpanded(QTreeWidgetItem *item) { QString dir = item->data(0, Qt::UserRole).toString(); if (dir.isEmpty()) return; QString prefix; if (!_folderPath.isEmpty()) { prefix = _folderPath + QLatin1Char('/'); } LsColJob *job = new LsColJob(_account, prefix + dir, this); connect(job, SIGNAL(directoryListing(QStringList)), SLOT(slotUpdateDirectories(QStringList))); job->start(); } void SelectiveSyncTreeView::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 children 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::Checked) { parent->setCheckState(0, Qt::PartiallyChecked); } // Uncheck all the children for (int i = 0; i < item->childCount(); ++i) { if (item->child(i)->checkState(0) != Qt::Unchecked) { item->child(i)->setCheckState(0, Qt::Unchecked); } } // Can't uncheck the root. if (!parent) { item->setCheckState(0, Qt::PartiallyChecked); } } if (item->checkState(0) == Qt::PartiallyChecked) { QTreeWidgetItem *parent = item->parent(); if (parent && parent->checkState(0) != Qt::PartiallyChecked) { parent->setCheckState(0, Qt::PartiallyChecked); } } } QStringList SelectiveSyncTreeView::createBlackList(QTreeWidgetItem* root) const { if (!root) { root = topLevelItem(0); } if (!root) return QStringList(); switch(root->checkState(0)) { case Qt::Unchecked: return QStringList(root->data(0, Qt::UserRole).toString() + "/"); case Qt::Checked: return QStringList(); case Qt::PartiallyChecked: break; } QStringList result; if (root->childCount()) { for (int i = 0; i < root->childCount(); ++i) { result += createBlackList(root->child(i)); } } else { // We did not load from the server so we re-use the one from the old black list QString path = root->data(0, Qt::UserRole).toString(); foreach (const QString & it, _oldBlackList) { if (it.startsWith(path)) result += it; } } return result; } SelectiveSyncDialog::SelectiveSyncDialog(Account * account, Folder* folder, QWidget* parent, Qt::WindowFlags f) : QDialog(parent, f), _folder(folder) { init(account); _treeView->setFolderInfo(_folder->remotePath(), _folder->alias(), _folder->selectiveSyncBlackList()); // 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())); } SelectiveSyncDialog::SelectiveSyncDialog(Account* account, const QStringList& blacklist, QWidget* parent, Qt::WindowFlags f) : QDialog(parent, f), _folder(0) { init(account); _treeView->setFolderInfo(QString(), QString(), blacklist); } void SelectiveSyncDialog::init(Account *account) { setWindowTitle(tr("Choose What to Sync")); QVBoxLayout *layout = new QVBoxLayout(this); _treeView = new SelectiveSyncTreeView(account, this); QLabel *label = new QLabel(tr("Unchecked folders will be removed from your local file system and will not be synchronized to this computer anymore")); label->setWordWrap(true); layout->addWidget(label); 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); } void SelectiveSyncDialog::accept() { if (_folder) { auto oldBlackListSet = _folder->selectiveSyncBlackList().toSet(); QStringList blackList = _treeView->createBlackList(); _folder->setSelectiveSyncBlackList(blackList); // FIXME: Use MirallConfigFile QSettings settings(_folder->configFile(), QSettings::IniFormat); settings.beginGroup(FolderMan::escapeAlias(_folder->alias())); settings.setValue("blackList", blackList); FolderMan *folderMan = FolderMan::instance(); if (_folder->isBusy()) { _folder->slotTerminateSync(); } //The part that changed should not be read from the DB on next sync because there might be new folders // (the ones that are no longer in the blacklist) auto blackListSet = blackList.toSet(); auto changes = (oldBlackListSet - blackListSet) + (blackListSet - oldBlackListSet); foreach(const auto &it, changes) { _folder->journalDb()->avoidReadFromDbOnNextSync(it); } folderMan->slotScheduleSync(_folder->alias()); } QDialog::accept(); } QStringList SelectiveSyncDialog::createBlackList() const { return _treeView->createBlackList(); } }