1 | /* |
2 | * Copyright (C) by Daniel Molkentin <danimo@owncloud.com> |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify |
5 | * it under the terms of the GNU General Public License as published by |
6 | * the Free Software Foundation; either version 2 of the License, or |
7 | * (at your option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, but |
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
11 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
12 | * for more details. |
13 | */ |
14 | |
15 | |
16 | #include "accountsettings.h" |
17 | #include "ui_accountsettings.h" |
18 | |
19 | #include "theme.h" |
20 | #include "folderman.h" |
21 | #include "folderwizard.h" |
22 | #include "folderstatusmodel.h" |
23 | #include "folderstatusdelegate.h" |
24 | #include "common/utility.h" |
25 | #include "application.h" |
26 | #include "configfile.h" |
27 | #include "account.h" |
28 | #include "accountstate.h" |
29 | #include "quotainfo.h" |
30 | #include "accountmanager.h" |
31 | #include "owncloudsetupwizard.h" |
32 | #include "creds/abstractcredentials.h" |
33 | #include "creds/httpcredentialsgui.h" |
34 | #include "tooltipupdater.h" |
35 | #include "filesystem.h" |
36 | |
37 | #include <math.h> |
38 | |
39 | #include <QDesktopServices> |
40 | #include <QDir> |
41 | #include <QListWidgetItem> |
42 | #include <QMessageBox> |
43 | #include <QAction> |
44 | #include <QVBoxLayout> |
45 | #include <QTreeView> |
46 | #include <QKeySequence> |
47 | #include <QIcon> |
48 | #include <QVariant> |
49 | #include <QToolTip> |
50 | #include <qstringlistmodel.h> |
51 | #include <qpropertyanimation.h> |
52 | |
53 | #include "account.h" |
54 | |
55 | #ifdef Q_OS_MAC |
56 | #include "settingsdialogmac.h" |
57 | #endif |
58 | |
59 | namespace OCC { |
60 | |
61 | Q_LOGGING_CATEGORY(lcAccountSettings, "gui.account.settings" , QtInfoMsg) |
62 | |
63 | static const char progressBarStyleC[] = |
64 | "QProgressBar {" |
65 | "border: 1px solid grey;" |
66 | "border-radius: 5px;" |
67 | "text-align: center;" |
68 | "}" |
69 | "QProgressBar::chunk {" |
70 | "background-color: %1; width: 1px;" |
71 | "}" ; |
72 | |
73 | /** |
74 | * Adjusts the mouse cursor based on the region it is on over the folder tree view. |
75 | * |
76 | * Used to show that one can click the red error list box by changing the cursor |
77 | * to the pointing hand. |
78 | */ |
79 | class MouseCursorChanger : public QObject |
80 | { |
81 | Q_OBJECT |
82 | public: |
83 | MouseCursorChanger(QObject *parent) |
84 | : QObject(parent) |
85 | { |
86 | } |
87 | |
88 | QTreeView *folderList; |
89 | FolderStatusModel *model; |
90 | |
91 | protected: |
92 | bool eventFilter(QObject *watched, QEvent *event) override |
93 | { |
94 | if (event->type() == QEvent::HoverMove) { |
95 | Qt::CursorShape shape = Qt::ArrowCursor; |
96 | auto pos = folderList->mapFromGlobal(QCursor::pos()); |
97 | auto index = folderList->indexAt(pos); |
98 | if (model->classify(index) == FolderStatusModel::RootFolder |
99 | && (FolderStatusDelegate::errorsListRect(folderList->visualRect(index), index).contains(pos) |
100 | || FolderStatusDelegate::optionsButtonRect(folderList->visualRect(index),folderList->layoutDirection()).contains(pos))) { |
101 | shape = Qt::PointingHandCursor; |
102 | } |
103 | folderList->setCursor(shape); |
104 | } |
105 | return QObject::eventFilter(watched, event); |
106 | } |
107 | }; |
108 | |
109 | AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) |
110 | : QWidget(parent) |
111 | , ui(new Ui::AccountSettings) |
112 | , _wasDisabledBefore(false) |
113 | , _accountState(accountState) |
114 | , _quotaInfo(accountState) |
115 | { |
116 | ui->setupUi(this); |
117 | |
118 | _model = new FolderStatusModel; |
119 | _model->setAccountState(_accountState); |
120 | _model->setParent(this); |
121 | FolderStatusDelegate *delegate = new FolderStatusDelegate; |
122 | delegate->setParent(this); |
123 | |
124 | ui->_folderList->header()->hide(); |
125 | ui->_folderList->setItemDelegate(delegate); |
126 | ui->_folderList->setModel(_model); |
127 | #if defined(Q_OS_MAC) |
128 | ui->_folderList->setMinimumWidth(400); |
129 | #else |
130 | ui->_folderList->setMinimumWidth(300); |
131 | #endif |
132 | new ToolTipUpdater(ui->_folderList); |
133 | |
134 | auto mouseCursorChanger = new MouseCursorChanger(this); |
135 | mouseCursorChanger->folderList = ui->_folderList; |
136 | mouseCursorChanger->model = _model; |
137 | ui->_folderList->setMouseTracking(true); |
138 | ui->_folderList->setAttribute(Qt::WA_Hover, true); |
139 | ui->_folderList->installEventFilter(mouseCursorChanger); |
140 | |
141 | createAccountToolbox(); |
142 | connect(AccountManager::instance(), &AccountManager::accountAdded, |
143 | this, &AccountSettings::slotAccountAdded); |
144 | connect(ui->_folderList, &QWidget::customContextMenuRequested, |
145 | this, &AccountSettings::slotCustomContextMenuRequested); |
146 | connect(ui->_folderList, &QAbstractItemView::clicked, |
147 | this, &AccountSettings::slotFolderListClicked); |
148 | connect(ui->_folderList, &QTreeView::expanded, this, &AccountSettings::refreshSelectiveSyncStatus); |
149 | connect(ui->_folderList, &QTreeView::collapsed, this, &AccountSettings::refreshSelectiveSyncStatus); |
150 | connect(ui->selectiveSyncNotification, &QLabel::linkActivated, |
151 | this, &AccountSettings::slotLinkActivated); |
152 | connect(_model, &FolderStatusModel::suggestExpand, ui->_folderList, &QTreeView::expand); |
153 | connect(_model, &FolderStatusModel::dirtyChanged, this, &AccountSettings::refreshSelectiveSyncStatus); |
154 | refreshSelectiveSyncStatus(); |
155 | connect(_model, &QAbstractItemModel::rowsInserted, |
156 | this, &AccountSettings::refreshSelectiveSyncStatus); |
157 | |
158 | QAction *syncNowAction = new QAction(this); |
159 | syncNowAction->setShortcut(QKeySequence(Qt::Key_F6)); |
160 | connect(syncNowAction, &QAction::triggered, this, &AccountSettings::slotScheduleCurrentFolder); |
161 | addAction(syncNowAction); |
162 | |
163 | QAction *syncNowWithRemoteDiscovery = new QAction(this); |
164 | syncNowWithRemoteDiscovery->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_F6)); |
165 | connect(syncNowWithRemoteDiscovery, &QAction::triggered, this, &AccountSettings::slotScheduleCurrentFolderForceRemoteDiscovery); |
166 | addAction(syncNowWithRemoteDiscovery); |
167 | |
168 | |
169 | connect(ui->selectiveSyncApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync); |
170 | connect(ui->selectiveSyncCancel, &QAbstractButton::clicked, _model, &FolderStatusModel::resetFolders); |
171 | connect(ui->bigFolderApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync); |
172 | connect(ui->bigFolderSyncAll, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncAllPendingBigFolders); |
173 | connect(ui->bigFolderSyncNone, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncNoPendingBigFolders); |
174 | |
175 | connect(FolderMan::instance(), &FolderMan::folderListChanged, _model, &FolderStatusModel::resetFolders); |
176 | connect(this, &AccountSettings::folderChanged, _model, &FolderStatusModel::resetFolders); |
177 | |
178 | |
179 | QColor color = palette().highlight().color(); |
180 | ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name())); |
181 | |
182 | ui->connectLabel->setText(tr("No account configured." )); |
183 | |
184 | connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged); |
185 | slotAccountStateChanged(); |
186 | |
187 | connect(&_quotaInfo, &QuotaInfo::quotaUpdated, |
188 | this, &AccountSettings::slotUpdateQuota); |
189 | } |
190 | |
191 | |
192 | void AccountSettings::createAccountToolbox() |
193 | { |
194 | QMenu * = new QMenu(); |
195 | _addAccountAction = new QAction(tr("Add new" ), this); |
196 | menu->addAction(_addAccountAction); |
197 | connect(_addAccountAction, &QAction::triggered, this, &AccountSettings::slotOpenAccountWizard); |
198 | |
199 | _toggleSignInOutAction = new QAction(tr("Log out" ), this); |
200 | connect(_toggleSignInOutAction, &QAction::triggered, this, &AccountSettings::slotToggleSignInState); |
201 | menu->addAction(_toggleSignInOutAction); |
202 | |
203 | QAction *action = new QAction(tr("Remove" ), this); |
204 | menu->addAction(action); |
205 | connect(action, &QAction::triggered, this, &AccountSettings::slotDeleteAccount); |
206 | |
207 | ui->_accountToolbox->setText(tr("Account" ) + QLatin1Char(' ')); |
208 | ui->_accountToolbox->setMenu(menu); |
209 | ui->_accountToolbox->setPopupMode(QToolButton::InstantPopup); |
210 | |
211 | slotAccountAdded(_accountState); |
212 | } |
213 | |
214 | QString AccountSettings::selectedFolderAlias() const |
215 | { |
216 | QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); |
217 | if (!selected.isValid()) |
218 | return "" ; |
219 | return _model->data(selected, FolderStatusDelegate::FolderAliasRole).toString(); |
220 | } |
221 | |
222 | void AccountSettings::slotOpenAccountWizard() |
223 | { |
224 | // We can't call isSystemTrayAvailable with appmenu-qt5 because it breaks the systemtray |
225 | // (issue #4693, #4944) |
226 | if (qgetenv("QT_QPA_PLATFORMTHEME" ) == "appmenu-qt5" || QSystemTrayIcon::isSystemTrayAvailable()) { |
227 | topLevelWidget()->close(); |
228 | } |
229 | #ifdef Q_OS_MAC |
230 | qCDebug(lcAccountSettings) << parent() << topLevelWidget(); |
231 | SettingsDialogMac *sd = qobject_cast<SettingsDialogMac *>(topLevelWidget()); |
232 | |
233 | if (sd) { |
234 | sd->showActivityPage(); |
235 | } else { |
236 | qFatal("nope" ); |
237 | } |
238 | #endif |
239 | OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)), 0); |
240 | } |
241 | |
242 | void AccountSettings::slotToggleSignInState() |
243 | { |
244 | if (_accountState->isSignedOut()) { |
245 | _accountState->account()->resetRejectedCertificates(); |
246 | _accountState->signIn(); |
247 | } else { |
248 | _accountState->signOutByUi(); |
249 | } |
250 | } |
251 | |
252 | void AccountSettings::doExpand() |
253 | { |
254 | ui->_folderList->expandToDepth(0); |
255 | } |
256 | |
257 | void AccountSettings::(const QPoint &pos) |
258 | { |
259 | QTreeView *tv = ui->_folderList; |
260 | QModelIndex index = tv->indexAt(pos); |
261 | if (!index.isValid()) { |
262 | return; |
263 | } |
264 | |
265 | if (_model->classify(index) == FolderStatusModel::SubFolder) { |
266 | QMenu * = new QMenu(tv); |
267 | menu->setAttribute(Qt::WA_DeleteOnClose); |
268 | |
269 | QAction *ac = menu->addAction(tr("Open folder" )); |
270 | connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentLocalSubFolder); |
271 | |
272 | QString fileName = _model->data(index, FolderStatusDelegate::FolderPathRole).toString(); |
273 | if (!QFile::exists(fileName)) { |
274 | ac->setEnabled(false); |
275 | } |
276 | |
277 | menu->popup(tv->mapToGlobal(pos)); |
278 | return; |
279 | } |
280 | |
281 | if (_model->classify(index) != FolderStatusModel::RootFolder) { |
282 | return; |
283 | } |
284 | |
285 | tv->setCurrentIndex(index); |
286 | QString alias = _model->data(index, FolderStatusDelegate::FolderAliasRole).toString(); |
287 | bool folderPaused = _model->data(index, FolderStatusDelegate::FolderSyncPaused).toBool(); |
288 | bool folderConnected = _model->data(index, FolderStatusDelegate::FolderAccountConnected).toBool(); |
289 | auto folderMan = FolderMan::instance(); |
290 | |
291 | QMenu * = new QMenu(tv); |
292 | |
293 | menu->setAttribute(Qt::WA_DeleteOnClose); |
294 | |
295 | QAction *ac = menu->addAction(tr("Open folder" )); |
296 | connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentFolder); |
297 | |
298 | if (!ui->_folderList->isExpanded(index)) { |
299 | ac = menu->addAction(tr("Choose what to sync" )); |
300 | ac->setEnabled(folderConnected); |
301 | connect(ac, &QAction::triggered, this, &AccountSettings::doExpand); |
302 | } |
303 | |
304 | if (!folderPaused) { |
305 | ac = menu->addAction(tr("Force sync now" )); |
306 | if (folderMan->currentSyncFolder() == folderMan->folder(alias)) { |
307 | ac->setText(tr("Restart sync" )); |
308 | } |
309 | ac->setEnabled(folderConnected); |
310 | connect(ac, &QAction::triggered, this, &AccountSettings::slotForceSyncCurrentFolder); |
311 | } |
312 | |
313 | ac = menu->addAction(folderPaused ? tr("Resume sync" ) : tr("Pause sync" )); |
314 | connect(ac, &QAction::triggered, this, &AccountSettings::slotEnableCurrentFolder); |
315 | |
316 | ac = menu->addAction(tr("Remove folder sync connection" )); |
317 | connect(ac, &QAction::triggered, this, &AccountSettings::slotRemoveCurrentFolder); |
318 | menu->popup(tv->mapToGlobal(pos)); |
319 | } |
320 | |
321 | void AccountSettings::slotFolderListClicked(const QModelIndex &indx) |
322 | { |
323 | if (indx.data(FolderStatusDelegate::AddButton).toBool()) { |
324 | // "Add Folder Sync Connection" |
325 | if (indx.flags() & Qt::ItemIsEnabled) { |
326 | slotAddFolder(); |
327 | } else { |
328 | QToolTip::showText( |
329 | QCursor::pos(), |
330 | _model->data(indx, Qt::ToolTipRole).toString(), |
331 | this); |
332 | } |
333 | return; |
334 | } |
335 | if (_model->classify(indx) == FolderStatusModel::RootFolder) { |
336 | // tries to find if we clicked on the '...' button. |
337 | QTreeView *tv = ui->_folderList; |
338 | auto pos = tv->mapFromGlobal(QCursor::pos()); |
339 | if (FolderStatusDelegate::optionsButtonRect(tv->visualRect(indx), layoutDirection()).contains(pos)) { |
340 | slotCustomContextMenuRequested(pos); |
341 | return; |
342 | } |
343 | if (FolderStatusDelegate::errorsListRect(tv->visualRect(indx), indx).contains(pos)) { |
344 | emit showIssuesList(_model->data(indx, FolderStatusDelegate::FolderAliasRole).toString()); |
345 | return; |
346 | } |
347 | |
348 | // Expand root items on single click |
349 | if (_accountState && _accountState->state() == AccountState::Connected) { |
350 | bool expanded = !(ui->_folderList->isExpanded(indx)); |
351 | ui->_folderList->setExpanded(indx, expanded); |
352 | } |
353 | } |
354 | } |
355 | |
356 | void AccountSettings::slotAddFolder() |
357 | { |
358 | FolderMan *folderMan = FolderMan::instance(); |
359 | folderMan->setSyncEnabled(false); // do not start more syncs. |
360 | |
361 | FolderWizard *folderWizard = new FolderWizard(_accountState->account(), this); |
362 | |
363 | connect(folderWizard, &QDialog::accepted, this, &AccountSettings::slotFolderWizardAccepted); |
364 | connect(folderWizard, &QDialog::rejected, this, &AccountSettings::slotFolderWizardRejected); |
365 | folderWizard->open(); |
366 | } |
367 | |
368 | |
369 | void AccountSettings::slotFolderWizardAccepted() |
370 | { |
371 | FolderWizard *folderWizard = qobject_cast<FolderWizard *>(sender()); |
372 | FolderMan *folderMan = FolderMan::instance(); |
373 | |
374 | qCInfo(lcAccountSettings) << "Folder wizard completed" ; |
375 | |
376 | FolderDefinition definition; |
377 | definition.localPath = FolderDefinition::prepareLocalPath( |
378 | folderWizard->field(QLatin1String("sourceFolder" )).toString()); |
379 | definition.targetPath = FolderDefinition::prepareTargetPath( |
380 | folderWizard->property("targetPath" ).toString()); |
381 | definition.useVirtualFiles = folderWizard->property("useVirtualFiles" ).toBool(); |
382 | |
383 | { |
384 | QDir dir(definition.localPath); |
385 | if (!dir.exists()) { |
386 | qCInfo(lcAccountSettings) << "Creating folder" << definition.localPath; |
387 | if (!dir.mkpath("." )) { |
388 | QMessageBox::warning(this, tr("Folder creation failed" ), |
389 | tr("<p>Could not create local folder <i>%1</i>." ) |
390 | .arg(QDir::toNativeSeparators(definition.localPath))); |
391 | return; |
392 | } |
393 | } |
394 | FileSystem::setFolderMinimumPermissions(definition.localPath); |
395 | Utility::setupFavLink(definition.localPath); |
396 | } |
397 | |
398 | /* take the value from the definition of already existing folders. All folders have |
399 | * the same setting so far. |
400 | * The default is to not sync hidden files |
401 | */ |
402 | definition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles(); |
403 | |
404 | if (folderMan->navigationPaneHelper().showInExplorerNavigationPane()) |
405 | definition.navigationPaneClsid = QUuid::createUuid(); |
406 | |
407 | auto selectiveSyncBlackList = folderWizard->property("selectiveSyncBlackList" ).toStringList(); |
408 | |
409 | folderMan->setSyncEnabled(true); |
410 | |
411 | Folder *f = folderMan->addFolder(_accountState, definition); |
412 | if (f) { |
413 | f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, selectiveSyncBlackList); |
414 | |
415 | // The user already accepted the selective sync dialog. everything is in the white list |
416 | f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, |
417 | QStringList() << QLatin1String("/" )); |
418 | folderMan->scheduleAllFolders(); |
419 | emit folderChanged(); |
420 | } |
421 | } |
422 | |
423 | void AccountSettings::slotFolderWizardRejected() |
424 | { |
425 | qCInfo(lcAccountSettings) << "Folder wizard cancelled" ; |
426 | FolderMan *folderMan = FolderMan::instance(); |
427 | folderMan->setSyncEnabled(true); |
428 | } |
429 | |
430 | void AccountSettings::slotRemoveCurrentFolder() |
431 | { |
432 | FolderMan *folderMan = FolderMan::instance(); |
433 | auto folder = folderMan->folder(selectedFolderAlias()); |
434 | QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); |
435 | if (selected.isValid() && folder) { |
436 | int row = selected.row(); |
437 | |
438 | qCInfo(lcAccountSettings) << "Remove Folder alias " << folder->alias(); |
439 | QString shortGuiLocalPath = folder->shortGuiLocalPath(); |
440 | |
441 | QMessageBox messageBox(QMessageBox::Question, |
442 | tr("Confirm Folder Sync Connection Removal" ), |
443 | tr("<p>Do you really want to stop syncing the folder <i>%1</i>?</p>" |
444 | "<p><b>Note:</b> This will <b>not</b> delete any files.</p>" ) |
445 | .arg(shortGuiLocalPath), |
446 | QMessageBox::NoButton, |
447 | this); |
448 | QPushButton *yesButton = |
449 | messageBox.addButton(tr("Remove Folder Sync Connection" ), QMessageBox::YesRole); |
450 | messageBox.addButton(tr("Cancel" ), QMessageBox::NoRole); |
451 | |
452 | messageBox.exec(); |
453 | if (messageBox.clickedButton() != yesButton) { |
454 | return; |
455 | } |
456 | |
457 | folderMan->removeFolder(folder); |
458 | _model->removeRow(row); |
459 | |
460 | // single folder fix to show add-button and hide remove-button |
461 | |
462 | emit folderChanged(); |
463 | } |
464 | } |
465 | |
466 | void AccountSettings::slotOpenCurrentFolder() |
467 | { |
468 | auto alias = selectedFolderAlias(); |
469 | if (!alias.isEmpty()) { |
470 | emit openFolderAlias(alias); |
471 | } |
472 | } |
473 | |
474 | void AccountSettings::slotOpenCurrentLocalSubFolder() |
475 | { |
476 | QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); |
477 | if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder) |
478 | return; |
479 | QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString(); |
480 | QUrl url = QUrl::fromLocalFile(fileName); |
481 | QDesktopServices::openUrl(url); |
482 | } |
483 | |
484 | void AccountSettings::showConnectionLabel(const QString &message, QStringList errors) |
485 | { |
486 | const QString errStyle = QLatin1String("color:#ffffff; background-color:#bb4d4d;padding:5px;" |
487 | "border-width: 1px; border-style: solid; border-color: #aaaaaa;" |
488 | "border-radius:5px;" ); |
489 | if (errors.isEmpty()) { |
490 | ui->connectLabel->setText(message); |
491 | ui->connectLabel->setToolTip(QString()); |
492 | ui->connectLabel->setStyleSheet(QString()); |
493 | } else { |
494 | errors.prepend(message); |
495 | const QString msg = errors.join(QLatin1String("\n" )); |
496 | qCDebug(lcAccountSettings) << msg; |
497 | ui->connectLabel->setText(msg); |
498 | ui->connectLabel->setToolTip(QString()); |
499 | ui->connectLabel->setStyleSheet(errStyle); |
500 | } |
501 | ui->accountStatus->setVisible(!message.isEmpty()); |
502 | } |
503 | |
504 | void AccountSettings::slotEnableCurrentFolder() |
505 | { |
506 | auto alias = selectedFolderAlias(); |
507 | |
508 | if (!alias.isEmpty()) { |
509 | FolderMan *folderMan = FolderMan::instance(); |
510 | |
511 | qCInfo(lcAccountSettings) << "Application: enable folder with alias " << alias; |
512 | bool terminate = false; |
513 | bool currentlyPaused = false; |
514 | |
515 | // this sets the folder status to disabled but does not interrupt it. |
516 | Folder *f = folderMan->folder(alias); |
517 | if (!f) { |
518 | return; |
519 | } |
520 | currentlyPaused = f->syncPaused(); |
521 | if (!currentlyPaused) { |
522 | // check if a sync is still running and if so, ask if we should terminate. |
523 | if (f->isBusy()) { // its still running |
524 | #if defined(Q_OS_MAC) |
525 | QWidget *parent = this; |
526 | Qt::WindowFlags flags = Qt::Sheet; |
527 | #else |
528 | QWidget *parent = 0; |
529 | Qt::WindowFlags flags = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint; // default flags |
530 | #endif |
531 | QMessageBox msgbox(QMessageBox::Question, tr("Sync Running" ), |
532 | tr("The syncing operation is running.<br/>Do you want to terminate it?" ), |
533 | QMessageBox::Yes | QMessageBox::No, parent, flags); |
534 | msgbox.setDefaultButton(QMessageBox::Yes); |
535 | int reply = msgbox.exec(); |
536 | if (reply == QMessageBox::Yes) |
537 | terminate = true; |
538 | else |
539 | return; // do nothing |
540 | } |
541 | } |
542 | |
543 | // message box can return at any time while the thread keeps running, |
544 | // so better check again after the user has responded. |
545 | if (f->isBusy() && terminate) { |
546 | f->slotTerminateSync(); |
547 | } |
548 | f->setSyncPaused(!currentlyPaused); |
549 | |
550 | // keep state for the icon setting. |
551 | if (currentlyPaused) |
552 | _wasDisabledBefore = true; |
553 | |
554 | _model->slotUpdateFolderState(f); |
555 | } |
556 | } |
557 | |
558 | void AccountSettings::slotScheduleCurrentFolder() |
559 | { |
560 | FolderMan *folderMan = FolderMan::instance(); |
561 | if (auto folder = folderMan->folder(selectedFolderAlias())) { |
562 | folderMan->scheduleFolder(folder); |
563 | } |
564 | } |
565 | |
566 | void AccountSettings::slotScheduleCurrentFolderForceRemoteDiscovery() |
567 | { |
568 | FolderMan *folderMan = FolderMan::instance(); |
569 | if (auto folder = folderMan->folder(selectedFolderAlias())) { |
570 | folder->journalDb()->forceRemoteDiscoveryNextSync(); |
571 | folderMan->scheduleFolder(folder); |
572 | } |
573 | } |
574 | |
575 | void AccountSettings::slotForceSyncCurrentFolder() |
576 | { |
577 | FolderMan *folderMan = FolderMan::instance(); |
578 | if (auto selectedFolder = folderMan->folder(selectedFolderAlias())) { |
579 | // Terminate and reschedule any running sync |
580 | if (Folder *current = folderMan->currentSyncFolder()) { |
581 | folderMan->terminateSyncProcess(); |
582 | folderMan->scheduleFolder(current); |
583 | } |
584 | |
585 | // Insert the selected folder at the front of the queue |
586 | folderMan->scheduleFolderNext(selectedFolder); |
587 | } |
588 | } |
589 | |
590 | void AccountSettings::slotOpenOC() |
591 | { |
592 | if (_OCUrl.isValid()) |
593 | QDesktopServices::openUrl(_OCUrl); |
594 | } |
595 | |
596 | void AccountSettings::slotUpdateQuota(qint64 total, qint64 used) |
597 | { |
598 | if (total > 0) { |
599 | ui->quotaProgressBar->setVisible(true); |
600 | ui->quotaProgressBar->setEnabled(true); |
601 | // workaround the label only accepting ints (which may be only 32 bit wide) |
602 | const double percent = used / (double)total * 100; |
603 | const int percentInt = qMin(qRound(percent), 100); |
604 | ui->quotaProgressBar->setValue(percentInt); |
605 | QString usedStr = Utility::octetsToString(used); |
606 | QString totalStr = Utility::octetsToString(total); |
607 | QString percentStr = Utility::compactFormatDouble(percent, 1); |
608 | QString toolTip = tr("%1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits." ).arg(usedStr, totalStr, percentStr); |
609 | ui->quotaInfoLabel->setText(tr("%1 of %2 in use" ).arg(usedStr, totalStr)); |
610 | ui->quotaInfoLabel->setToolTip(toolTip); |
611 | ui->quotaProgressBar->setToolTip(toolTip); |
612 | } else { |
613 | ui->quotaProgressBar->setVisible(false); |
614 | ui->quotaInfoLabel->setToolTip(QString()); |
615 | |
616 | /* -1 means not computed; -2 means unknown; -3 means unlimited (#3940)*/ |
617 | if (total == 0 || total == -1) { |
618 | ui->quotaInfoLabel->setText(tr("Currently there is no storage usage information available." )); |
619 | } else { |
620 | QString usedStr = Utility::octetsToString(used); |
621 | ui->quotaInfoLabel->setText(tr("%1 in use" ).arg(usedStr)); |
622 | } |
623 | } |
624 | } |
625 | |
626 | void AccountSettings::slotAccountStateChanged() |
627 | { |
628 | int state = _accountState ? _accountState->state() : AccountState::Disconnected; |
629 | if (_accountState) { |
630 | ui->sslButton->updateAccountState(_accountState); |
631 | AccountPtr account = _accountState->account(); |
632 | QUrl safeUrl(account->url()); |
633 | safeUrl.setPassword(QString()); // Remove the password from the URL to avoid showing it in the UI |
634 | FolderMan *folderMan = FolderMan::instance(); |
635 | foreach (Folder *folder, folderMan->map().values()) { |
636 | _model->slotUpdateFolderState(folder); |
637 | } |
638 | |
639 | QString server = QString::fromLatin1("<a href=\"%1\">%2</a>" ) |
640 | .arg(Utility::escape(account->url().toString()), |
641 | Utility::escape(safeUrl.toString())); |
642 | QString serverWithUser = server; |
643 | if (AbstractCredentials *cred = account->credentials()) { |
644 | QString user = account->davDisplayName(); |
645 | if (user.isEmpty()) { |
646 | user = cred->user(); |
647 | } |
648 | serverWithUser = tr("%1 as <i>%2</i>" ).arg(server, Utility::escape(user)); |
649 | } |
650 | |
651 | if (state == AccountState::Connected) { |
652 | QStringList errors; |
653 | if (account->serverVersionUnsupported()) { |
654 | errors << tr("The server version %1 is old and unsupported! Proceed at your own risk." ).arg(account->serverVersion()); |
655 | } |
656 | showConnectionLabel(tr("Connected to %1." ).arg(serverWithUser), errors); |
657 | } else if (state == AccountState::ServiceUnavailable) { |
658 | showConnectionLabel(tr("Server %1 is temporarily unavailable." ).arg(server)); |
659 | } else if (state == AccountState::MaintenanceMode) { |
660 | showConnectionLabel(tr("Server %1 is currently in maintenance mode." ).arg(server)); |
661 | } else if (state == AccountState::SignedOut) { |
662 | showConnectionLabel(tr("Signed out from %1." ).arg(serverWithUser)); |
663 | } else if (state == AccountState::AskingCredentials) { |
664 | QUrl url; |
665 | if (auto cred = qobject_cast<HttpCredentialsGui *>(account->credentials())) { |
666 | connect(cred, &HttpCredentialsGui::authorisationLinkChanged, |
667 | this, &AccountSettings::slotAccountStateChanged, Qt::UniqueConnection); |
668 | url = cred->authorisationLink(); |
669 | } |
670 | if (url.isValid()) { |
671 | showConnectionLabel(tr("Obtaining authorization from the browser. " |
672 | "<a href='%1'>Click here</a> to re-open the browser." ) |
673 | .arg(url.toString(QUrl::FullyEncoded))); |
674 | } else { |
675 | showConnectionLabel(tr("Connecting to %1..." ).arg(serverWithUser)); |
676 | } |
677 | } else { |
678 | showConnectionLabel(tr("No connection to %1 at %2." ) |
679 | .arg(Utility::escape(Theme::instance()->appNameGUI()), server), |
680 | _accountState->connectionErrors()); |
681 | } |
682 | } else { |
683 | // ownCloud is not yet configured. |
684 | showConnectionLabel(tr("No %1 connection configured." ) |
685 | .arg(Utility::escape(Theme::instance()->appNameGUI()))); |
686 | } |
687 | |
688 | /* Allow to expand the item if the account is connected. */ |
689 | ui->_folderList->setItemsExpandable(state == AccountState::Connected); |
690 | |
691 | if (state != AccountState::Connected) { |
692 | /* check if there are expanded root items, if so, close them */ |
693 | int i; |
694 | for (i = 0; i < _model->rowCount(); ++i) { |
695 | if (ui->_folderList->isExpanded(_model->index(i))) |
696 | ui->_folderList->setExpanded(_model->index(i), false); |
697 | } |
698 | } |
699 | |
700 | // Disabling expansion of folders might require hiding the selective |
701 | // sync user interface buttons. |
702 | refreshSelectiveSyncStatus(); |
703 | |
704 | /* set the correct label for the Account toolbox button */ |
705 | if (_accountState) { |
706 | if (_accountState->isSignedOut()) { |
707 | _toggleSignInOutAction->setText(tr("Log in" )); |
708 | } else { |
709 | _toggleSignInOutAction->setText(tr("Log out" )); |
710 | } |
711 | } |
712 | } |
713 | |
714 | void AccountSettings::slotLinkActivated(const QString &link) |
715 | { |
716 | // Parse folder alias and filename from the link, calculate the index |
717 | // and select it if it exists. |
718 | const QStringList li = link.split(QLatin1String("?folder=" )); |
719 | if (li.count() > 1) { |
720 | QString myFolder = li[0]; |
721 | const QString alias = li[1]; |
722 | if (myFolder.endsWith(QLatin1Char('/'))) |
723 | myFolder.chop(1); |
724 | |
725 | // Make sure the folder itself is expanded |
726 | Folder *f = FolderMan::instance()->folder(alias); |
727 | QModelIndex folderIndx = _model->indexForPath(f, QString()); |
728 | if (!ui->_folderList->isExpanded(folderIndx)) { |
729 | ui->_folderList->setExpanded(folderIndx, true); |
730 | } |
731 | |
732 | QModelIndex indx = _model->indexForPath(f, myFolder); |
733 | if (indx.isValid()) { |
734 | // make sure all the parents are expanded |
735 | for (auto i = indx.parent(); i.isValid(); i = i.parent()) { |
736 | if (!ui->_folderList->isExpanded(i)) { |
737 | ui->_folderList->setExpanded(i, true); |
738 | } |
739 | } |
740 | ui->_folderList->setSelectionMode(QAbstractItemView::SingleSelection); |
741 | ui->_folderList->setCurrentIndex(indx); |
742 | ui->_folderList->scrollTo(indx); |
743 | } else { |
744 | qCWarning(lcAccountSettings) << "Unable to find a valid index for " << myFolder; |
745 | } |
746 | } |
747 | } |
748 | |
749 | AccountSettings::~AccountSettings() |
750 | { |
751 | delete ui; |
752 | } |
753 | |
754 | void AccountSettings::refreshSelectiveSyncStatus() |
755 | { |
756 | QString msg; |
757 | int cnt = 0; |
758 | foreach (Folder *folder, FolderMan::instance()->map().values()) { |
759 | if (folder->accountState() != _accountState) { |
760 | continue; |
761 | } |
762 | |
763 | bool ok; |
764 | auto undecidedList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, &ok); |
765 | QString p; |
766 | foreach (const auto &it, undecidedList) { |
767 | // FIXME: add the folder alias in a hoover hint. |
768 | // folder->alias() + QLatin1String("/") |
769 | if (cnt++) { |
770 | msg += QLatin1String(", " ); |
771 | } |
772 | QString myFolder = (it); |
773 | if (myFolder.endsWith('/')) { |
774 | myFolder.chop(1); |
775 | } |
776 | QModelIndex theIndx = _model->indexForPath(folder, myFolder); |
777 | if (theIndx.isValid()) { |
778 | msg += QString::fromLatin1("<a href=\"%1?folder=%2\">%1</a>" ) |
779 | .arg(Utility::escape(myFolder), Utility::escape(folder->alias())); |
780 | } else { |
781 | msg += myFolder; // no link because we do not know the index yet. |
782 | } |
783 | } |
784 | } |
785 | |
786 | // Some selective sync ui (either normal editing or big folder) will show |
787 | // if this variable ends up true. |
788 | bool shouldBeVisible = false; |
789 | |
790 | if (msg.isEmpty()) { |
791 | // Show the ui if the model is dirty only |
792 | shouldBeVisible = _model->isDirty() && _accountState->isConnected(); |
793 | |
794 | ui->selectiveSyncButtons->setVisible(true); |
795 | ui->bigFolderUi->setVisible(false); |
796 | } else { |
797 | // There's a reason the big folder ui should be shown |
798 | shouldBeVisible = _accountState->isConnected(); |
799 | |
800 | ConfigFile cfg; |
801 | QString info = !cfg.confirmExternalStorage() |
802 | ? tr("There are folders that were not synchronized because they are too big: " ) |
803 | : !cfg.newBigFolderSizeLimit().first |
804 | ? tr("There are folders that were not synchronized because they are external storages: " ) |
805 | : tr("There are folders that were not synchronized because they are too big or external storages: " ); |
806 | |
807 | ui->selectiveSyncNotification->setText(info + msg); |
808 | ui->selectiveSyncButtons->setVisible(false); |
809 | ui->bigFolderUi->setVisible(true); |
810 | } |
811 | |
812 | ui->selectiveSyncApply->setEnabled(_model->isDirty() || !msg.isEmpty()); |
813 | bool wasVisible = !ui->selectiveSyncStatus->isHidden(); |
814 | if (wasVisible != shouldBeVisible) { |
815 | QSize hint = ui->selectiveSyncStatus->sizeHint(); |
816 | if (shouldBeVisible) { |
817 | ui->selectiveSyncStatus->setMaximumHeight(0); |
818 | ui->selectiveSyncStatus->setVisible(true); |
819 | doExpand(); |
820 | } |
821 | auto anim = new QPropertyAnimation(ui->selectiveSyncStatus, "maximumHeight" , ui->selectiveSyncStatus); |
822 | anim->setEndValue(shouldBeVisible ? hint.height() : 0); |
823 | anim->start(QAbstractAnimation::DeleteWhenStopped); |
824 | connect(anim, &QPropertyAnimation::finished, [this, shouldBeVisible]() { |
825 | ui->selectiveSyncStatus->setMaximumHeight(QWIDGETSIZE_MAX); |
826 | if (!shouldBeVisible) { |
827 | ui->selectiveSyncStatus->hide(); |
828 | } |
829 | }); |
830 | } |
831 | } |
832 | |
833 | void AccountSettings::slotAccountAdded(AccountState *) |
834 | { |
835 | // if the theme is limited to single account, the button must hide if |
836 | // there is already one account. |
837 | int s = AccountManager::instance()->accounts().size(); |
838 | if (s > 0 && !Theme::instance()->multiAccount()) { |
839 | _addAccountAction->setVisible(false); |
840 | } else { |
841 | _addAccountAction->setVisible(true); |
842 | } |
843 | } |
844 | |
845 | void AccountSettings::slotDeleteAccount() |
846 | { |
847 | // Deleting the account potentially deletes 'this', so |
848 | // the QMessageBox should be destroyed before that happens. |
849 | { |
850 | QMessageBox messageBox(QMessageBox::Question, |
851 | tr("Confirm Account Removal" ), |
852 | tr("<p>Do you really want to remove the connection to the account <i>%1</i>?</p>" |
853 | "<p><b>Note:</b> This will <b>not</b> delete any files.</p>" ) |
854 | .arg(_accountState->account()->displayName()), |
855 | QMessageBox::NoButton, |
856 | this); |
857 | QPushButton *yesButton = |
858 | messageBox.addButton(tr("Remove connection" ), QMessageBox::YesRole); |
859 | messageBox.addButton(tr("Cancel" ), QMessageBox::NoRole); |
860 | |
861 | messageBox.exec(); |
862 | if (messageBox.clickedButton() != yesButton) { |
863 | return; |
864 | } |
865 | } |
866 | |
867 | // Else it might access during destruction. This should be better handled by it having a QSharedPointer |
868 | _model->setAccountState(0); |
869 | |
870 | auto manager = AccountManager::instance(); |
871 | manager->deleteAccount(_accountState); |
872 | manager->save(); |
873 | |
874 | // IMPORTANT: "this" is deleted from this point on. We should probably remove this synchronous |
875 | // .exec() QMessageBox magic above as it recurses into the event loop. |
876 | } |
877 | |
878 | bool AccountSettings::event(QEvent *e) |
879 | { |
880 | if (e->type() == QEvent::Hide || e->type() == QEvent::Show) { |
881 | _quotaInfo.setActive(isVisible()); |
882 | } |
883 | if (e->type() == QEvent::Show) { |
884 | // Expand the folder automatically only if there's only one, see #4283 |
885 | // The 2 is 1 folder + 1 'add folder' button |
886 | if (_model->rowCount() <= 2) { |
887 | ui->_folderList->setExpanded(_model->index(0, 0), true); |
888 | } |
889 | } |
890 | return QWidget::event(e); |
891 | } |
892 | |
893 | } // namespace OCC |
894 | |
895 | #include "accountsettings.moc" |
896 | |