seafile-client/src/ui/repo-tree-view.cpp
Shuai Lin eb8f79ce96 Show sync errors in a dialog (#855)
* Show sync errors in a dialog.

* Fixed a compile warning.

* Improve windows bintray description.

* Improve sync errors dialog ui.

* Commented out debug code.

* Add dialog header to sync errors dialog.

TODO: There are lots of common logic used in FileBrowserDialog and
SyncErrorsDialog. We should refactor out a base dialog class.

* Load last 50 errors by default.

* Fixed the styles of sync errors dialog.

* Updated sync error messages.

* Double click to open the problematic repo.

And improved dialog header style.

* Ignore close event since we always use one instance of the dialog.

* Only update the model when errors are changed.
2017-01-17 03:37:28 +00:00

1124 lines
38 KiB
C++

#include <QtGlobal>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
#include <QtWidgets>
#else
#include <QtGui>
#endif
#include <QHeaderView>
#include <QDesktopServices>
#include <QEvent>
#include <QShowEvent>
#include <QHideEvent>
#include <QInputDialog>
#include <Qt>
#include "utils/utils.h"
#include "seafile-applet.h"
#include "settings-mgr.h"
#include "account-mgr.h"
#include "rpc/rpc-client.h"
#include "rpc/local-repo.h"
#include "download-repo-dialog.h"
#include "clone-tasks-dialog.h"
#include "repo-item.h"
#include "repo-item-delegate.h"
#include "repo-tree-model.h"
#include "repo-detail-dialog.h"
#include "utils/paint-utils.h"
#include "repo-service.h"
#include "auto-login-service.h"
#include "filebrowser/file-browser-manager.h"
#include "filebrowser/file-browser-dialog.h"
#include "utils/utils-mac.h"
#include "filebrowser/tasks.h"
#include "filebrowser/progress-dialog.h"
#include "ui/set-repo-password-dialog.h"
#include "ui/private-share-dialog.h"
#include "repo-tree-view.h"
namespace {
const char *kRepoTreeViewSettingsGroup = "RepoTreeView";
const char *kRepoTreeViewSettingsExpandedCategories = "expandedCategories";
const int kRepoCategoryIndicatorWidth = 16;
const char *kSyncIntervalProperty = "sync-interval";
QString buildMoreInfo(ServerRepo& repo, const QUrl& url_in)
{
json_t *object = NULL;
char *info = NULL;
object = json_object();
json_object_set_new(object, "is_readonly", json_integer(repo.readonly));
QUrl url(url_in);
url.setPath("/");
json_object_set_new(
object, "server_url", json_string(url.toString().toUtf8().data()));
info = json_dumps(object, 0);
QString ret = QString::fromUtf8(info);
json_decref (object);
free (info);
return ret;
}
QString getRepoProperty(const QString& repo_id, const QString& name)
{
QString value;
seafApplet->rpcClient()->getRepoProperty(repo_id, name, &value);
return value;
}
// A Helper Class to copy file
//
class FileCopyHelper : public QRunnable {
public:
FileCopyHelper(const QString &source, const QString &target,
RepoTreeView *parent)
: source_(source),
target_(target),
parent_(parent) {
}
signals:
void success(bool);
private:
void run() {
if (!QFile::copy(source_, target_)) {
// cannot do GUI operations in this thread
// callback to the main thread
QMetaObject::invokeMethod(parent_, "copyFileFailed");
}
}
bool autoDelete() {
return true;
}
const QString source_;
const QString target_;
RepoTreeView *parent_;
};
FileCopyHelper* copyFile(const QString &source, const QString &target, RepoTreeView *parent) {
FileCopyHelper* helper = new FileCopyHelper(source, target, parent);
QThreadPool *pool = QThreadPool::globalInstance();
pool->start(helper);
return helper;
}
} // anonymous namespace
static ServerRepo selected_repo_;
// TODO save localrepo as well to avoid many copys
RepoTreeView::RepoTreeView(QWidget *parent)
: QTreeView(parent)
{
header()->hide();
createActions();
// We draw the indicator ourselves
setIndentation(0);
// We handle the click oursevles
setExpandsOnDoubleClick(false);
connect(this, SIGNAL(clicked(const QModelIndex&)),
this, SLOT(onItemClicked(const QModelIndex&)));
connect(this, SIGNAL(doubleClicked(const QModelIndex&)),
this, SLOT(onItemDoubleClicked(const QModelIndex&)));
#if defined(Q_OS_MAC)
this->setAttribute(Qt::WA_MacShowFocusRect, 0);
#endif
loadExpandedCategries();
connect(qApp, SIGNAL(aboutToQuit()),
this, SLOT(saveExpandedCategries()));
connect(seafApplet->accountManager(), SIGNAL(beforeAccountChanged()),
this, SLOT(saveExpandedCategries()));
connect(seafApplet->accountManager(), SIGNAL(accountsChanged()),
this, SLOT(loadExpandedCategries()));
connect(seafApplet->settingsManager(), SIGNAL(autoSyncChanged(bool)),
this, SLOT(update()));
setAcceptDrops(true);
setDefaultDropAction(Qt::CopyAction);
}
void RepoTreeView::loadExpandedCategries()
{
Account account = seafApplet->accountManager()->currentAccount();
if (!account.isValid()) {
return;
}
expanded_categroies_.clear();
QSettings settings;
settings.beginGroup(kRepoTreeViewSettingsGroup);
QString key = QString(kRepoTreeViewSettingsExpandedCategories) + "-" + account.getSignature();
if (settings.contains(key)) {
QString cats = settings.value(key, "").toString();
expanded_categroies_ = QSet<QString>::fromList(cats.split("\t", QString::SkipEmptyParts));
} else {
// Expand "recent updated" on first use
expanded_categroies_.insert(tr("Recently Updated"));
}
settings.endGroup();
}
void RepoTreeView::contextMenuEvent(QContextMenuEvent *event)
{
QPoint pos = event->pos();
QModelIndex index = indexAt(pos);
if (!index.isValid()) {
// Not clicked at a repo item
return;
}
QStandardItem *item = getRepoItem(index);
if (!item || item->type() != REPO_ITEM_TYPE) {
return;
}
updateRepoActions();
QMenu *menu = prepareContextMenu((RepoItem *)item);
pos = viewport()->mapToGlobal(pos);
menu->exec(pos);
menu->deleteLater();
}
QMenu* RepoTreeView::prepareContextMenu(const RepoItem *item)
{
QMenu *menu = new QMenu(this);
if (item->localRepo().isValid()) {
menu->addAction(open_local_folder_action_);
}
if (item->repoDownloadable()) {
menu->addAction(download_action_);
}
menu->addAction(view_on_web_action_);
menu->addAction(open_in_filebrowser_action_);
const Account& account = seafApplet->accountManager()->currentAccount();
if (account.isPro() && account.username == item->repo().owner) {
menu->addSeparator();
menu->addAction(share_repo_to_user_action_);
menu->addAction(share_repo_to_group_action_);
menu->addSeparator();
}
if (item->localRepo().isValid()) {
menu->addSeparator();
menu->addAction(toggle_auto_sync_action_);
menu->addAction(sync_now_action_);
menu->addAction(set_sync_interval_action_);
#if defined(Q_OS_WIN32)
menu->addAction(map_netdrive_action_);
#endif
menu->addSeparator();
}
menu->addAction(show_detail_action_);
if (item->cloneTask().isCancelable()) {
menu->addAction(cancel_download_action_);
}
if (item->localRepo().isValid()) {
menu->addAction(unsync_action_);
menu->addAction(resync_action_);
}
return menu;
}
void RepoTreeView::updateRepoActions()
{
RepoItem *item = NULL;
QItemSelection selected = selectionModel()->selection();
QModelIndexList indexes = selected.indexes();
if (indexes.size() != 0) {
const QModelIndex& index = indexes.at(0);
QSortFilterProxyModel *proxy = (QSortFilterProxyModel *)model();
RepoTreeModel *tree_model = (RepoTreeModel *)(proxy->sourceModel());
QStandardItem *it = tree_model->itemFromIndex(proxy->mapToSource(index));
if (it && it->type() == REPO_ITEM_TYPE) {
item = (RepoItem *)it;
}
}
if (!item) {
// No repo item is selected
download_action_->setEnabled(false);
download_toolbar_action_->setEnabled(false);
sync_now_action_->setEnabled(false);
open_local_folder_action_->setEnabled(false);
open_local_folder_toolbar_action_->setEnabled(false);
unsync_action_->setEnabled(false);
resync_action_->setEnabled(false);
set_sync_interval_action_->setEnabled(false);
map_netdrive_action_->setEnabled(false);
toggle_auto_sync_action_->setEnabled(false);
view_on_web_action_->setEnabled(false);
open_in_filebrowser_action_->setEnabled(false);
show_detail_action_->setEnabled(false);
return;
}
LocalRepo r;
seafApplet->rpcClient()->getLocalRepo(item->repo().id, &r);
item->setLocalRepo(r);
if (item->localRepo().isValid()) {
const LocalRepo& local_repo = item->localRepo();
download_action_->setEnabled(false);
download_toolbar_action_->setEnabled(false);
sync_now_action_->setEnabled(true);
sync_now_action_->setData(QVariant::fromValue(local_repo));
open_local_folder_action_->setData(QVariant::fromValue(local_repo));
open_local_folder_action_->setEnabled(true);
open_local_folder_toolbar_action_->setData(QVariant::fromValue(local_repo));
open_local_folder_toolbar_action_->setEnabled(true);
unsync_action_->setData(QVariant::fromValue(local_repo));
unsync_action_->setEnabled(true);
resync_action_->setData(QVariant::fromValue(local_repo));
resync_action_->setEnabled(true);
set_sync_interval_action_->setData(QVariant::fromValue(local_repo));
set_sync_interval_action_->setEnabled(true);
map_netdrive_action_->setData(QVariant::fromValue(local_repo));
map_netdrive_action_->setEnabled(true);
if (seafApplet->settingsManager()->autoSync()) {
toggle_auto_sync_action_->setData(QVariant::fromValue(local_repo));
toggle_auto_sync_action_->setEnabled(true);
} else {
toggle_auto_sync_action_->setEnabled(false);
}
if (local_repo.auto_sync) {
toggle_auto_sync_action_->setText(tr("Disable auto sync"));
toggle_auto_sync_action_->setToolTip(tr("Disable auto sync"));
toggle_auto_sync_action_->setIcon(QIcon(":/images/pause-gray.png"));
} else {
toggle_auto_sync_action_->setText(tr("Enable auto sync"));
toggle_auto_sync_action_->setToolTip(tr("Enable auto sync"));
toggle_auto_sync_action_->setIcon(QIcon(":/images/play-gray.png"));
}
} else {
if (item->repoDownloadable()) {
download_action_->setEnabled(true);
download_toolbar_action_->setEnabled(true);
} else {
download_action_->setEnabled(false);
download_toolbar_action_->setEnabled(false);
}
sync_now_action_->setEnabled(false);
open_local_folder_action_->setEnabled(false);
open_local_folder_toolbar_action_->setEnabled(false);
unsync_action_->setEnabled(false);
resync_action_->setEnabled(false);
set_sync_interval_action_->setEnabled(false);
map_netdrive_action_->setEnabled(false);
toggle_auto_sync_action_->setEnabled(false);
}
selected_repo_= item->repo();
view_on_web_action_->setEnabled(true);
open_in_filebrowser_action_->setEnabled(true);
show_detail_action_->setEnabled(true);
if (item->cloneTask().isCancelable()) {
cancel_download_action_->setEnabled(true);
} else {
cancel_download_action_->setEnabled(false);
}
emit dataChanged(indexes.at(0), indexes.at(0));
}
QStandardItem* RepoTreeView::getRepoItem(const QModelIndex &index) const
{
if (!index.isValid()) {
return NULL;
}
QSortFilterProxyModel *proxy = (QSortFilterProxyModel *)model();
RepoTreeModel *tree_model = (RepoTreeModel *)(proxy->sourceModel());
QStandardItem *item = tree_model->itemFromIndex(proxy->mapToSource(index));
if (item->type() != REPO_ITEM_TYPE &&
item->type() != REPO_CATEGORY_TYPE) {
return NULL;
}
return item;
}
void RepoTreeView::createActions()
{
show_detail_action_ = new QAction(tr("Show &details"), this);
show_detail_action_->setIcon(QIcon(":/images/info-gray.png"));
show_detail_action_->setStatusTip(tr("Show details of this library"));
show_detail_action_->setIconVisibleInMenu(true);
connect(show_detail_action_, SIGNAL(triggered()), this, SLOT(showRepoDetail()));
download_action_ = new QAction(tr("&Sync this library"), this);
download_action_->setIcon(QIcon(":/images/toolbar/download-gray.png"));
download_action_->setStatusTip(tr("Sync this library"));
download_action_->setIconVisibleInMenu(true);
connect(download_action_, SIGNAL(triggered()), this, SLOT(downloadRepo()));
download_toolbar_action_ = new QAction(tr("&Sync this library"), this);
download_toolbar_action_->setIcon(QIcon(":/images/toolbar/download.png"));
download_toolbar_action_->setStatusTip(tr("Sync this library"));
download_toolbar_action_->setIconVisibleInMenu(false);
connect(download_toolbar_action_, SIGNAL(triggered()), this, SLOT(downloadRepo()));
sync_now_action_ = new QAction(tr("Sync &now"), this);
sync_now_action_->setIcon(QIcon(":/images/sync_now-gray.png"));
sync_now_action_->setStatusTip(tr("Sync this library immediately"));
sync_now_action_->setIconVisibleInMenu(true);
connect(sync_now_action_, SIGNAL(triggered()), this, SLOT(syncRepoImmediately()));
cancel_download_action_ = new QAction(tr("&Cancel download"), this);
cancel_download_action_->setIcon(QIcon(":/images/remove-gray.png"));
cancel_download_action_->setStatusTip(tr("Cancel download of this library"));
cancel_download_action_->setIconVisibleInMenu(true);
connect(cancel_download_action_, SIGNAL(triggered()), this, SLOT(cancelDownload()));
open_local_folder_action_ = new QAction(tr("&Open folder"), this);
open_local_folder_action_->setIcon(QIcon(":/images/toolbar/file-gray.png"));
open_local_folder_action_->setStatusTip(tr("open local folder"));
open_local_folder_action_->setIconVisibleInMenu(true);
connect(open_local_folder_action_, SIGNAL(triggered()), this, SLOT(openLocalFolder()));
open_local_folder_toolbar_action_ = new QAction(tr("&Open folder"), this);
open_local_folder_toolbar_action_->setIcon(QIcon(":/images/toolbar/file.png"));
open_local_folder_toolbar_action_->setStatusTip(tr("open local folder"));
open_local_folder_toolbar_action_->setIconVisibleInMenu(true);
connect(open_local_folder_toolbar_action_, SIGNAL(triggered()), this, SLOT(openLocalFolder()));
unsync_action_ = new QAction(tr("&Unsync"), this);
unsync_action_->setStatusTip(tr("unsync this library"));
unsync_action_->setIcon(QIcon(":/images/minus-gray.png"));
unsync_action_->setIconVisibleInMenu(true);
connect(unsync_action_, SIGNAL(triggered()), this, SLOT(unsyncRepo()));
toggle_auto_sync_action_ = new QAction(tr("Enable auto sync"), this);
toggle_auto_sync_action_->setStatusTip(tr("Enable auto sync"));
toggle_auto_sync_action_->setIconVisibleInMenu(true);
connect(toggle_auto_sync_action_, SIGNAL(triggered()), this, SLOT(toggleRepoAutoSync()));
view_on_web_action_ = new QAction(tr("&View on cloud"), this);
view_on_web_action_->setIcon(QIcon(":/images/cloud-gray.png"));
view_on_web_action_->setStatusTip(tr("view this library on seahub"));
view_on_web_action_->setIconVisibleInMenu(true);
connect(view_on_web_action_, SIGNAL(triggered()), this, SLOT(viewRepoOnWeb()));
share_repo_to_user_action_ = new QAction(tr("Share to user"), this);
share_repo_to_user_action_->setIcon(QIcon(":/images/share.png"));
share_repo_to_user_action_->setStatusTip(tr("Share this library to a user"));
share_repo_to_user_action_->setIconVisibleInMenu(true);
connect(share_repo_to_user_action_, SIGNAL(triggered()), this, SLOT(shareRepoToUser()));
share_repo_to_group_action_ = new QAction(tr("Share to group"), this);
share_repo_to_group_action_->setIcon(QIcon(":/images/share.png"));
share_repo_to_group_action_->setStatusTip(tr("Share this library to a group"));
share_repo_to_group_action_->setIconVisibleInMenu(true);
connect(share_repo_to_group_action_, SIGNAL(triggered()), this, SLOT(shareRepoToGroup()));
QString open_action_text = tr("&Open cloud file browser");
if (open_action_text.contains("%")) {
// In German translation there is a "seafile" string, so need to use tr("..").arg(..) here
open_action_text = open_action_text.arg(getBrand());
}
open_in_filebrowser_action_ = new QAction(open_action_text, this);
open_in_filebrowser_action_->setIcon(QIcon(":/images/cloud-gray.png"));
open_in_filebrowser_action_->setStatusTip(tr("open this library in embedded Cloud File Browser"));
open_in_filebrowser_action_->setIconVisibleInMenu(true);
connect(open_in_filebrowser_action_, SIGNAL(triggered()), this, SLOT(openInFileBrowser()));
resync_action_ = new QAction(tr("&Resync this library"), this);
resync_action_->setIcon(QIcon(":/images/resync.png"));
resync_action_->setStatusTip(tr("unsync and resync this library"));
connect(resync_action_, SIGNAL(triggered()), this, SLOT(resyncRepo()));
set_sync_interval_action_ = new QAction(tr("Set sync &Interval"), this);
set_sync_interval_action_->setIcon(QIcon(":/images/clock.png"));
set_sync_interval_action_->setStatusTip(tr("unsync and resync this library"));
connect(set_sync_interval_action_, SIGNAL(triggered()), this, SLOT(setRepoSyncInterval()));
map_netdrive_action_ = new QAction(tr("&Map as a network drive"), this);
map_netdrive_action_->setIcon(QIcon(":/images/disk.png"));
map_netdrive_action_->setStatusTip(tr("map as a network drive"));
#if defined(Q_OS_WIN32)
connect(map_netdrive_action_, SIGNAL(triggered()), this, SLOT(mapLibraryAsNetworkDrive()));
#endif
}
void RepoTreeView::downloadRepo()
{
DownloadRepoDialog dialog(seafApplet->accountManager()->currentAccount(), selected_repo_, QString(), this);
dialog.exec();
updateRepoActions();
}
void RepoTreeView::showRepoDetail()
{
RepoDetailDialog dialog(selected_repo_, this);
dialog.exec();
}
void RepoTreeView::openLocalFolder()
{
LocalRepo repo = qvariant_cast<LocalRepo>(open_local_folder_action_->data());
QDesktopServices::openUrl(QUrl::fromLocalFile(repo.worktree));
}
void RepoTreeView::toggleRepoAutoSync()
{
LocalRepo repo = qvariant_cast<LocalRepo>(toggle_auto_sync_action_->data());
seafApplet->rpcClient()->setRepoAutoSync(repo.id, !repo.auto_sync);
updateRepoActions();
}
void RepoTreeView::unsyncRepo()
{
LocalRepo repo = qvariant_cast<LocalRepo>(toggle_auto_sync_action_->data());
QString question = tr("Are you sure to unsync library \"%1\"?").arg(repo.name);
if (!seafApplet->yesOrCancelBox(question, this, false)) {
return;
}
if (seafApplet->rpcClient()->unsync(repo.id) < 0) {
seafApplet->warningBox(tr("Failed to unsync library \"%1\"").arg(repo.name), this);
}
ServerRepo server_repo = RepoService::instance()->getRepo(repo.id);
if (server_repo.isValid() && server_repo.isSubfolder())
RepoService::instance()->removeSyncedSubfolder(repo.id);
updateRepoActions();
}
void RepoTreeView::onItemClicked(const QModelIndex& index)
{
QStandardItem *item = getRepoItem(index);
if (!item) {
return;
}
if (item->type() == REPO_ITEM_TYPE) {
return;
} else {
// A repo category item
if (isExpanded(index)) {
collapse(index);
} else {
expand(index);
}
}
}
void RepoTreeView::onItemDoubleClicked(const QModelIndex& index)
{
QStandardItem *item = getRepoItem(index);
if (!item) {
return;
}
if (item->type() == REPO_ITEM_TYPE) {
RepoItem *it = (RepoItem *)item;
const LocalRepo& local_repo = it->localRepo();
if (local_repo.isValid()) {
// open local folder for downloaded repo
QDesktopServices::openUrl(QUrl::fromLocalFile(local_repo.worktree));
} else {
// open seahub repo page for not downloaded repo
FileBrowserManager::getInstance()->openOrActivateDialog(
seafApplet->accountManager()->currentAccount(),
it->repo());
}
}
}
void RepoTreeView::viewRepoOnWeb()
{
const Account account = seafApplet->accountManager()->currentAccount();
if (account.isValid()) {
// we adopt a new format of cloud view url from server version 4.2.0
if (!account.isAtLeastVersion(4, 2, 0)) {
QDesktopServices::openUrl(account.getAbsoluteUrl("repo/" + selected_repo_.id));
} else {
AutoLoginService::instance()->startAutoLogin("/#common/lib/" + selected_repo_.id + "/");
}
}
}
void RepoTreeView::shareRepo(bool to_group)
{
const Account account = seafApplet->accountManager()->currentAccount();
PrivateShareDialog dialog(account, selected_repo_.id, selected_repo_.name,
"/", to_group,
this);
dialog.exec();
}
void RepoTreeView::shareRepoToUser()
{
shareRepo(false);
}
void RepoTreeView::shareRepoToGroup()
{
shareRepo(true);
}
void RepoTreeView::openInFileBrowser()
{
const Account account = seafApplet->accountManager()->currentAccount();
if (account.isValid()) {
FileBrowserManager::getInstance()->openOrActivateDialog(
seafApplet->accountManager()->currentAccount(),
selected_repo_);
}
}
bool RepoTreeView::viewportEvent(QEvent *event)
{
if (event->type() != QEvent::ToolTip && event->type() != QEvent::WhatsThis && event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseButtonRelease) {
return QTreeView::viewportEvent(event);
}
QPoint global_pos = QCursor::pos();
QPoint viewport_pos = viewport()->mapFromGlobal(global_pos);
QModelIndex index = indexAt(viewport_pos);
if (!index.isValid()) {
return true;
}
QStandardItem *item = getRepoItem(index);
if (!item) {
return true;
}
// handle the event in the top
const QModelIndex top_index = indexAt(QPoint(0, 0));
if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) {
if (index == top_index && top_index.parent().isValid() && viewport_pos.y() <= kRepoCategoryIndicatorWidth) {
QMouseEvent *ev = static_cast<QMouseEvent*>(event);
if (!(ev->buttons() & Qt::LeftButton))
return true;
const QModelIndex parent = top_index.parent();
setExpanded(parent, !isExpanded(parent));
return true;
}
return QTreeView::viewportEvent(event);
}
QRect item_rect = visualRect(index);
if (item->type() == REPO_ITEM_TYPE) {
showRepoItemToolTip((RepoItem *)item, global_pos, item_rect);
} else {
showRepoCategoryItemToolTip((RepoCategoryItem *)item, global_pos, item_rect);
}
return true;
}
void RepoTreeView::showRepoItemToolTip(const RepoItem *item,
const QPoint& pos,
const QRect& rect)
{
RepoItemDelegate *delegate = (RepoItemDelegate *)itemDelegate();
delegate->showRepoItemToolTip(item, pos, viewport(), rect);
}
void RepoTreeView::showRepoCategoryItemToolTip(const RepoCategoryItem *item,
const QPoint& pos,
const QRect& rect)
{
QToolTip::showText(pos, item->name(), viewport(), rect);
// QToolTip::showText(pos, item->name());
}
std::vector<QAction*> RepoTreeView::getToolBarActions()
{
std::vector<QAction*> actions;
updateRepoActions();
actions.push_back(download_toolbar_action_);
actions.push_back(open_local_folder_toolbar_action_);
return actions;
}
void RepoTreeView::selectionChanged(const QItemSelection &selected,
const QItemSelection &deselected)
{
updateRepoActions();
}
void RepoTreeView::hideEvent(QHideEvent *event)
{
download_action_->setEnabled(false);
download_toolbar_action_->setEnabled(false);
open_local_folder_action_->setEnabled(false);
open_local_folder_toolbar_action_->setEnabled(false);
unsync_action_->setEnabled(false);
resync_action_->setEnabled(false);
set_sync_interval_action_->setEnabled(false);
toggle_auto_sync_action_->setEnabled(false);
view_on_web_action_->setEnabled(false);
open_in_filebrowser_action_->setEnabled(false);
show_detail_action_->setEnabled(false);
}
void RepoTreeView::saveExpandedCategries()
{
Account account = seafApplet->accountManager()->currentAccount();
if (!account.isValid()) {
return;
}
QSettings settings;
QStringList cats = expanded_categroies_.toList();
settings.beginGroup(kRepoTreeViewSettingsGroup);
QString key = QString(kRepoTreeViewSettingsExpandedCategories) + "-" + account.getSignature();
settings.setValue(key, cats.join("\t"));
settings.endGroup();
}
void RepoTreeView::showEvent(QShowEvent *event)
{
updateRepoActions();
}
void RepoTreeView::syncRepoImmediately()
{
LocalRepo repo = qvariant_cast<LocalRepo>(sync_now_action_->data());
seafApplet->rpcClient()->syncRepoImmediately(repo.id);
QSortFilterProxyModel *proxy = (QSortFilterProxyModel *)model();
RepoTreeModel *tree_model = (RepoTreeModel *)(proxy->sourceModel());
tree_model->updateRepoItemAfterSyncNow(repo.id);
}
void RepoTreeView::cancelDownload()
{
QString error;
if (seafApplet->rpcClient()->cancelCloneTask(selected_repo_.id, &error) < 0) {
seafApplet->warningBox(tr("Failed to cancel this task:\n\n %1").arg(error), this);
} else {
seafApplet->messageBox(tr("The download has been canceled"), this);
}
}
void RepoTreeView::expand(const QModelIndex& index, bool remember)
{
QTreeView::expand(index);
if (remember) {
QStandardItem *item = getRepoItem(index);
if (item->type() == REPO_CATEGORY_TYPE) {
expanded_categroies_.insert(item->data(Qt::DisplayRole).toString());
}
}
}
void RepoTreeView::collapse(const QModelIndex& index, bool remember)
{
QTreeView::collapse(index);
if (remember) {
QStandardItem *item = getRepoItem(index);
if (item->type() == REPO_CATEGORY_TYPE) {
expanded_categroies_.remove(item->data(Qt::DisplayRole).toString());
}
}
}
void RepoTreeView::restoreExpandedCategries()
{
QSortFilterProxyModel *proxy_model =
(QSortFilterProxyModel *)(this->model());
RepoTreeModel *tree_model = (RepoTreeModel *)(proxy_model->sourceModel());
for (int i = 0; i < proxy_model->rowCount(); i++) {
QModelIndex index = proxy_model->index(i, 0);
QString category = proxy_model->data(index).toString();
if (expanded_categroies_.contains(category)) {
expand(index, false);
} else {
collapse(index, false);
}
QStandardItem *item =
tree_model->itemFromIndex(proxy_model->mapToSource(index));
// We need to go one level down if this item is the groups root.
if (item->type() == REPO_CATEGORY_TYPE &&
((RepoCategoryItem *)item)->isGroupsRoot()) {
for (int j = 0; j < item->rowCount(); j++) {
RepoCategoryItem *category_item =
(RepoCategoryItem *)(item->child(j));
QModelIndex index = proxy_model->mapFromSource(
tree_model->indexFromItem(category_item));
QString category = proxy_model->data(index).toString();
if (expanded_categroies_.contains(category)) {
expand(index, false);
} else {
collapse(index, false);
}
}
}
}
}
void RepoTreeView::resyncRepo()
{
LocalRepo local_repo = qvariant_cast<LocalRepo>(unsync_action_->data());
ServerRepo server_repo = RepoService::instance()->getRepo(local_repo.id);
SeafileRpcClient *rpc = seafApplet->rpcClient();
if (!seafApplet->yesOrNoBox(
tr("Are you sure to unsync and resync library \"%1\"?").arg(server_repo.name),
this)) {
return;
}
// must read these before unsync
QString token = getRepoProperty(local_repo.id, "token");
QString relay_addr = getRepoProperty(local_repo.id, "relay-address");
QString relay_port = getRepoProperty(local_repo.id, "relay-port");
if (rpc->unsync(server_repo.id) < 0) {
seafApplet->warningBox(tr("Failed to unsync library \"%1\"").arg(server_repo.name));
return;
}
if (server_repo.encrypted) {
DownloadRepoDialog dialog(seafApplet->accountManager()->currentAccount(),
RepoService::instance()->getRepo(server_repo.id), QString(), this);
dialog.setMergeWithExisting(QFileInfo(local_repo.worktree).dir().absolutePath());
dialog.exec();
return;
} else {
const Account account = seafApplet->accountManager()->currentAccount();
QString more_info = buildMoreInfo(server_repo, account.serverUrl);
QString email = account.username;
QString error;
// unused fields
QString magic, passwd, random_key; // all null
int enc_version = 0;
if (rpc->cloneRepo(local_repo.id,
local_repo.version, local_repo.relay_id,
server_repo.name, local_repo.worktree,
token, passwd,
magic, relay_addr,
relay_port, email,
random_key, enc_version,
more_info, &error) < 0) {
seafApplet->warningBox(tr("Failed to add download task:\n %1").arg(error));
}
}
updateRepoActions();
}
void RepoTreeView::dropEvent(QDropEvent *event)
{
const QModelIndex index = indexAt(event->pos());
QStandardItem *standard_item = getRepoItem(index);
if (!standard_item || standard_item->type() != REPO_ITEM_TYPE) {
return;
}
event->accept();
RepoItem *item = static_cast<RepoItem*>(standard_item);
const ServerRepo &repo = item->repo();
const QUrl url = event->mimeData()->urls().at(0);
QString local_path = url.toLocalFile();
#if defined(Q_OS_MAC) && (QT_VERSION <= QT_VERSION_CHECK(5, 4, 0))
local_path = utils::mac::fix_file_id_url(local_path);
#endif
const QString file_name = QFileInfo(local_path).fileName();
// if the repo is synced
LocalRepo local_repo;
if (seafApplet->rpcClient()->getLocalRepo(repo.id, &local_repo) >= 0) {
QString target_path = QDir(local_repo.worktree).absoluteFilePath(file_name);
if (QFileInfo(target_path) == QFileInfo(local_path)) {
seafApplet->warningBox(tr("Unable to overwrite file \"%1\" with itself").arg(file_name));
return;
}
if (QFileInfo(target_path).exists()) {
if (!seafApplet->yesOrNoBox(tr("Are you sure to overwrite file \"%1\"").arg(file_name)))
return;
if (!QFile(target_path).remove()) {
seafApplet->warningBox(tr("Unable to delete file \"%1\"").arg(file_name));
return;
}
}
copyFile(local_path, target_path, this);
return;
}
FileUploadTask *task = new FileUploadTask(seafApplet->accountManager()->currentAccount(),
repo.id, "/", local_path, file_name);
uploadFileStart(task);
}
void RepoTreeView::dragMoveEvent(QDragMoveEvent *event)
{
DropIndicatorPosition position = dropIndicatorPosition();
if (position == QAbstractItemView::OnItem) {
event->setDropAction(Qt::CopyAction);
event->accept();
//TODO highlight the selected item, and dehightlight when it's over
// const QModelIndex index = indexAt(event->pos());
// RepoItem *item = static_cast<RepoItem*>(getRepoItem(index));
// if (!item || item->type() != REPO_ITEM_TYPE) {
// return;
// }
} else {
event->setDropAction(Qt::IgnoreAction);
event->accept();
}
}
void RepoTreeView::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) {
const QUrl url = event->mimeData()->urls().at(0);
if (url.scheme() == "file") {
QString file_name = url.toLocalFile();
#if defined(Q_OS_MAC) && (QT_VERSION <= QT_VERSION_CHECK(5, 4, 0))
file_name = utils::mac::fix_file_id_url(file_name);
#endif
if (QFileInfo(file_name).isFile()) {
event->setDropAction(Qt::CopyAction);
event->accept();
}
}
}
}
void RepoTreeView::uploadFileStart(FileUploadTask *task)
{
connect(task, SIGNAL(finished(bool)),
this, SLOT(uploadFileFinished(bool)));
FileBrowserProgressDialog *dialog = new FileBrowserProgressDialog(task, this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
const QPoint global = this->mapToGlobal(rect().center());
dialog->move(global.x() - dialog->width() / 2,
global.y() - dialog->height() / 2);
dialog->raise();
dialog->activateWindow();
task->start();
}
void RepoTreeView::uploadFileFinished(bool success)
{
FileUploadTask *task = qobject_cast<FileUploadTask *>(sender());
if (task == NULL)
return;
if (!success) {
// if the user cancel the task, don't bother him(or her) with it
if (task->error() == FileNetworkTask::TaskCanceled)
return;
// failed and it is a encrypted repository
ServerRepo repo = RepoService::instance()->getRepo(task->repoId());
if (repo.encrypted && task->httpErrorCode() == 400) {
SetRepoPasswordDialog password_dialog(repo, this);
if (password_dialog.exec()) {
FileUploadTask *new_task = new FileUploadTask(*task);
uploadFileStart(new_task);
}
return;
}
QString msg = tr("Failed to upload file: %1").arg(task->errorString());
seafApplet->warningBox(msg, this);
}
}
void RepoTreeView::copyFileFailed()
{
QString msg = QObject::tr("copy failed");
seafApplet->warningBox(msg);
}
void RepoTreeView::setRepoSyncInterval()
{
LocalRepo local_repo =
qvariant_cast<LocalRepo>(set_sync_interval_action_->data());
int default_interval = 0;
QString value;
if (seafApplet->rpcClient()->getRepoProperty(
local_repo.id, kSyncIntervalProperty, &value) == 0) {
default_interval = value.toInt();
}
QInputDialog dialog(this);
dialog.setInputMode(QInputDialog::IntInput);
dialog.setIntMinimum(0);
dialog.setIntMaximum(2147483647);
dialog.setIntStep(10);
dialog.setIntValue(default_interval);
dialog.setObjectName("syncIntervalDialog");
dialog.setLabelText(tr("Sync Interval (In seconds):"));
dialog.setWindowTitle(
tr("Set Sync Internval For Library \"%1\"").arg(local_repo.name));
dialog.setWindowIcon(QIcon(":/images/seafile.png"));
dialog.resize(400, 100);
if (dialog.exec() != QDialog::Accepted) {
return;
}
int interval = dialog.intValue();
if (interval != 0 && interval == default_interval) {
return;
}
seafApplet->rpcClient()->setRepoProperty(
local_repo.id, kSyncIntervalProperty, QString::number(interval));
}
#if defined(Q_OS_WIN32)
static bool findNextAvailableDrive(QString* drive)
{
DWORD bitmap = GetLogicalDrives();
if (bitmap == 0) {
return false;
}
char disk = 'A';
DWORD mask = 1;
while (disk <= 'Z') {
if (!(bitmap & mask)) {
*drive = QString(disk) + ":";
return true;
}
mask = mask << 1;
disk += 1;
}
return false;
}
static QString getDriveNetworkPath(const QString& drive)
{
wchar_t wbuf[8192];
DWORD size = sizeof(wbuf) / sizeof(wchar_t);
if (WNetGetConnectionW(drive.toStdWString().c_str(), wbuf, &size) ==
NO_ERROR) {
return QString::fromWCharArray(wbuf);
}
return QString();
}
static bool alreadyMappedAsNetworkDrive(const QString& path, QString* drive)
{
DWORD bitmap = GetLogicalDrives();
if (bitmap == 0) {
return false;
}
char disk = 'A';
DWORD mask = 1;
while (disk <= 'Z') {
if (bitmap & mask) {
QString drive_root = QString(disk) + ":";
if (getDriveNetworkPath(drive_root) == path) {
*drive = drive_root;
return true;
}
}
mask = mask << 1;
disk += 1;
}
return false;
}
static QString formatErrorMessage(DWORD error_code)
{
if (error_code == 0) {
return QObject::tr("unknown error");
}
wchar_t wbuf[4096] = {0};
::FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
wbuf,
(sizeof(wbuf) / sizeof(wchar_t)) - 1,
NULL);
return QString::fromWCharArray(wbuf);
}
void RepoTreeView::mapLibraryAsNetworkDrive()
{
LocalRepo local_repo =
qvariant_cast<LocalRepo>(set_sync_interval_action_->data());
QString worktree = QDir::toNativeSeparators(local_repo.worktree);
// Path format is \\localhost\c$\folder
QString net_path = QString("\\\\localhost\\%1$\\%2")
.arg(worktree.left(1).toLower())
.arg(worktree.mid(3));
QString mapped_drive;
if (alreadyMappedAsNetworkDrive(net_path, &mapped_drive)) {
QDesktopServices::openUrl(QUrl::fromLocalFile(mapped_drive));
return;
}
if (!findNextAvailableDrive(&mapped_drive)) {
seafApplet->warningBox(tr("No available windows drive letter"));
return;
}
qWarning("path is %s", net_path.toUtf8().data());
int code = QProcess::execute("net", QStringList() << "use" << mapped_drive
<< net_path);
if (code != 0) {
seafApplet->warningBox(tr("Operation failed: %1").arg(formatErrorMessage(code)));
} else {
QDesktopServices::openUrl(QUrl::fromLocalFile(mapped_drive));
}
}
#else
void RepoTreeView::mapLibraryAsNetworkDrive()
{
}
#endif // Q_OS_WIN32