/* * Copyright (C) by Daniel Molkentin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "accountsettings.h" #include "ui_accountsettings.h" #include "theme.h" #include "folderman.h" #include "folderwizard.h" #include "folderstatusmodel.h" #include "folderstatusdelegate.h" #include "utility.h" #include "application.h" #include "configfile.h" #include "account.h" #include "accountstate.h" #include "quotainfo.h" #include "accountmanager.h" #include "owncloudsetupwizard.h" #include "creds/abstractcredentials.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "account.h" namespace OCC { static const char progressBarStyleC[] = "QProgressBar {" "border: 1px solid grey;" "border-radius: 5px;" "text-align: center;" "}" "QProgressBar::chunk {" "background-color: %1; width: 1px;" "}"; AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) : QWidget(parent), ui(new Ui::AccountSettings), _wasDisabledBefore(false), _accountState(accountState), _quotaInfo(accountState) { ui->setupUi(this); _model = new FolderStatusModel; _model->setAccountState(_accountState); _model->setParent(this); FolderStatusDelegate *delegate = new FolderStatusDelegate; delegate->setParent(this); ui->_folderList->header()->hide(); ui->_folderList->setItemDelegate( delegate ); ui->_folderList->setModel( _model ); #if defined(Q_OS_MAC) ui->_folderList->setMinimumWidth( 400 ); #else ui->_folderList->setMinimumWidth( 300 ); #endif connect(ui->_folderList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotCustomContextMenuRequested(QPoint))); connect(ui->_folderList, SIGNAL(expanded(QModelIndex)) , this, SLOT(refreshSelectiveSyncStatus())); connect(ui->_folderList, SIGNAL(collapsed(QModelIndex)) , this, SLOT(refreshSelectiveSyncStatus())); connect(ui->selectiveSyncNotification, SIGNAL(linkActivated(QString)), this, SLOT(slotLinkActivated(QString))); connect(_model, SIGNAL(suggestExpand(QModelIndex)), ui->_folderList, SLOT(expand(QModelIndex))); connect(_model, SIGNAL(dirtyChanged()), this, SLOT(refreshSelectiveSyncStatus())); refreshSelectiveSyncStatus(); connect(_model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(refreshSelectiveSyncStatus())); QAction *resetFolderAction = new QAction(this); resetFolderAction->setShortcut(QKeySequence(Qt::Key_F5)); connect(resetFolderAction, SIGNAL(triggered()), SLOT(slotResetCurrentFolder())); addAction(resetFolderAction); QAction *syncNowAction = new QAction(this); syncNowAction->setShortcut(QKeySequence(Qt::Key_F6)); connect(syncNowAction, SIGNAL(triggered()), SLOT(slotSyncCurrentFolderNow())); addAction(syncNowAction); connect(ui->_folderList, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotFolderListClicked(const QModelIndex&))); connect(ui->selectiveSyncApply, SIGNAL(clicked()), _model, SLOT(slotApplySelectiveSync())); connect(ui->selectiveSyncCancel, SIGNAL(clicked()), _model, SLOT(resetFolders())); connect(FolderMan::instance(), SIGNAL(folderListLoaded(Folder::Map)), _model, SLOT(resetFolders())); connect(this, SIGNAL(folderChanged()), _model, SLOT(resetFolders())); QColor color = palette().highlight().color(); ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name())); ui->connectLabel->setText(tr("No account configured.")); connect(_accountState, SIGNAL(stateChanged(int)), SLOT(slotAccountStateChanged(int))); slotAccountStateChanged(_accountState->state()); connect( &_quotaInfo, SIGNAL(quotaUpdated(qint64,qint64)), this, SLOT(slotUpdateQuota(qint64,qint64))); connect(ui->signInButton, SIGNAL(clicked()) , this, SLOT(slotSignInAccount())); connect(ui->deleteButton, SIGNAL(clicked()) , this, SLOT(slotDeleteAccount())); } void AccountSettings::doExpand() { ui->_folderList->expandToDepth(0); } void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) { QTreeView *tv = ui->_folderList; QModelIndex index = tv->indexAt(pos); if (!index.isValid()) { return; } QString alias = _model->data( index, FolderStatusDelegate::FolderAliasRole ).toString(); if (alias.isEmpty()) { return; } tv->setCurrentIndex(index); bool folderPaused = _model->data( index, FolderStatusDelegate::FolderSyncPaused).toBool(); bool folderConnected = _model->data( index, FolderStatusDelegate::FolderAccountConnected ).toBool(); QMenu *menu = new QMenu(tv); menu->setAttribute(Qt::WA_DeleteOnClose); QAction *ac = menu->addAction(tr("Open folder")); connect(ac, SIGNAL(triggered(bool)), this, SLOT(slotOpenCurrentFolder())); ac = menu->addAction(tr("Choose what to sync")); ac->setEnabled(folderConnected); connect(ac, SIGNAL(triggered(bool)), this, SLOT(doExpand())); ac = menu->addAction(folderPaused ? tr("Resume sync") : tr("Pause sync")); connect(ac, SIGNAL(triggered(bool)), this, SLOT(slotEnableCurrentFolder())); ac = menu->addAction(tr("Remove folder sync connection")); connect(ac, SIGNAL(triggered(bool)), this, SLOT(slotRemoveCurrentFolder())); menu->exec(tv->mapToGlobal(pos)); } void AccountSettings::slotFolderListClicked(const QModelIndex& indx) { if (indx.data(FolderStatusDelegate::AddButton).toBool()) { if (indx.flags() & Qt::ItemIsEnabled) { slotAddFolder(); } else { QToolTip::showText( QCursor::pos(), _model->data(indx, Qt::ToolTipRole).toString(), this); } return; } if (_model->classify(indx) == FolderStatusModel::RootFolder) { // tries to find if we clicked on the '...' button. QTreeView *tv = ui->_folderList; auto pos = tv->mapFromGlobal(QCursor::pos()); if (FolderStatusDelegate::optionsButtonRect(tv->visualRect(indx)).contains(pos)) { slotCustomContextMenuRequested(pos); return; } // Expand root items on single click if(_accountState && _accountState->state() == AccountState::Connected ) { bool expanded = ! (ui->_folderList->isExpanded(indx)); ui->_folderList->setExpanded(indx, expanded); } } } void AccountSettings::slotAddFolder() { FolderMan *folderMan = FolderMan::instance(); folderMan->setSyncEnabled(false); // do not start more syncs. FolderWizard *folderWizard = new FolderWizard(_accountState->account(), this); connect(folderWizard, SIGNAL(accepted()), SLOT(slotFolderWizardAccepted())); connect(folderWizard, SIGNAL(rejected()), SLOT(slotFolderWizardRejected())); folderWizard->open(); } void AccountSettings::slotFolderWizardAccepted() { FolderWizard *folderWizard = qobject_cast(sender()); FolderMan *folderMan = FolderMan::instance(); qDebug() << "* Folder wizard completed"; FolderDefinition definition; definition.alias = folderWizard->field(QLatin1String("alias")).toString(); definition.localPath = FolderDefinition::prepareLocalPath( folderWizard->field(QLatin1String("sourceFolder")).toString()); definition.targetPath = folderWizard->property("targetPath").toString(); { QDir dir(definition.localPath); if (!dir.exists()) { qDebug() << "Creating folder" << definition.localPath; if (!dir.mkpath(".")) { QMessageBox::warning(this, tr("Folder creation failed"), tr("

Could not create local folder %1.") .arg(QDir::toNativeSeparators(definition.localPath))); return; } } } /* take the value from the definition of already existing folders. All folders have * the same setting so far. * The default is to not sync hidden files */ definition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles(); auto selectiveSyncBlackList = folderWizard->property("selectiveSyncBlackList").toStringList(); folderMan->setSyncEnabled(true); Folder *f = folderMan->addFolder(_accountState, definition); if( f ) { f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, selectiveSyncBlackList); // The user already accepted the selective sync dialog. everything is in the white list f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, QStringList() << QLatin1String("/")); folderMan->slotScheduleAllFolders(); emit folderChanged(); } } void AccountSettings::slotFolderWizardRejected() { qDebug() << "* Folder wizard cancelled"; FolderMan *folderMan = FolderMan::instance(); folderMan->setSyncEnabled(true); } void AccountSettings::slotRemoveCurrentFolder() { QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); if( selected.isValid() ) { int row = selected.row(); QString alias = _model->data( selected, FolderStatusDelegate::FolderAliasRole ).toString(); qDebug() << "Remove Folder alias " << alias; if( !alias.isEmpty() ) { QMessageBox messageBox(QMessageBox::Question, tr("Confirm Folder Sync Connection Removal"), tr("

Do you really want to stop syncing the folder %1?

" "

Note: This will not delete any files.

").arg(alias), QMessageBox::NoButton, this); QPushButton* yesButton = messageBox.addButton(tr("Remove Folder Sync Connection"), QMessageBox::YesRole); messageBox.addButton(tr("Cancel"), QMessageBox::NoRole); messageBox.exec(); if (messageBox.clickedButton() != yesButton) { return; } FolderMan *folderMan = FolderMan::instance(); folderMan->slotRemoveFolder( folderMan->folder(alias) ); _model->removeRow(row); // single folder fix to show add-button and hide remove-button emit folderChanged(); } } } void AccountSettings::slotResetCurrentFolder() { QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); if( selected.isValid() ) { QString alias = _model->data( selected, FolderStatusDelegate::FolderAliasRole ).toString(); if (alias.isEmpty()) return; int ret = QMessageBox::question( 0, tr("Confirm Folder Reset"), tr("

Do you really want to reset folder %1 and rebuild your client database?

" "

Note: This function is designed for maintenance purposes only. " "No files will be removed, but this can cause significant data traffic and " "take several minutes or hours to complete, depending on the size of the folder. " "Only use this option if advised by your administrator.

").arg(alias), QMessageBox::Yes|QMessageBox::No ); if( ret == QMessageBox::Yes ) { FolderMan *folderMan = FolderMan::instance(); if(Folder *f = folderMan->folder(alias)) { f->slotTerminateSync(); f->wipe(); } folderMan->slotScheduleAllFolders(); } } } void AccountSettings::slotOpenCurrentFolder() { QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); if( selected.isValid() ) { QString alias = _model->data( selected, FolderStatusDelegate::FolderAliasRole ).toString(); emit openFolderAlias(alias); } } void AccountSettings::showConnectionLabel( const QString& message, QStringList errors ) { const QString errStyle = QLatin1String("color:#ffffff; background-color:#bb4d4d;padding:5px;" "border-width: 1px; border-style: solid; border-color: #aaaaaa;" "border-radius:5px;"); if( errors.isEmpty() ) { ui->connectLabel->setText( message ); ui->connectLabel->setToolTip(QString()); ui->connectLabel->setStyleSheet(QString()); } else { errors.prepend(message); const QString msg = errors.join(QLatin1String("\n")); qDebug() << msg; ui->connectLabel->setText( msg ); ui->connectLabel->setToolTip(QString()); ui->connectLabel->setStyleSheet(errStyle); } ui->accountStatus->setVisible(!message.isEmpty()); } void AccountSettings::slotEnableCurrentFolder() { QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); if( selected.isValid() ) { QString alias = _model->data( selected, FolderStatusDelegate::FolderAliasRole ).toString(); if( alias.isEmpty() ) { qDebug() << "Empty alias to enable."; return; } FolderMan *folderMan = FolderMan::instance(); qDebug() << "Application: enable folder with alias " << alias; bool terminate = false; bool currentlyPaused = false; // this sets the folder status to disabled but does not interrupt it. Folder *f = folderMan->folder( alias ); if (!f) { return; } currentlyPaused = f->syncPaused(); if( ! currentlyPaused ) { // check if a sync is still running and if so, ask if we should terminate. if( f->isBusy() ) { // its still running #if defined(Q_OS_MAC) QWidget *parent = this; Qt::WindowFlags flags = Qt::Sheet; #else QWidget *parent = 0; Qt::WindowFlags flags = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint; // default flags #endif QMessageBox msgbox(QMessageBox::Question, tr("Sync Running"), tr("The syncing operation is running.
Do you want to terminate it?"), QMessageBox::Yes | QMessageBox::No, parent, flags); msgbox.setDefaultButton(QMessageBox::Yes); int reply = msgbox.exec(); if ( reply == QMessageBox::Yes ) terminate = true; else return; // do nothing } } // message box can return at any time while the thread keeps running, // so better check again after the user has responded. if ( f->isBusy() && terminate ) { f->slotTerminateSync(); } f->setSyncPaused(!currentlyPaused); // toggle the pause setting folderMan->slotSetFolderPaused( f, !currentlyPaused ); // keep state for the icon setting. if( currentlyPaused ) _wasDisabledBefore = true; _model->slotUpdateFolderState (f); } } void AccountSettings::slotSyncCurrentFolderNow() { QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); if( !selected.isValid() ) return; QString alias = _model->data( selected, FolderStatusDelegate::FolderAliasRole ).toString(); FolderMan *folderMan = FolderMan::instance(); folderMan->slotScheduleSync(folderMan->folder(alias)); } void AccountSettings::slotOpenOC() { if( _OCUrl.isValid() ) QDesktopServices::openUrl( _OCUrl ); } void AccountSettings::slotUpdateQuota(qint64 total, qint64 used) { if( total > 0 ) { ui->quotaProgressBar->setVisible(true); ui->quotaProgressBar->setEnabled(true); // workaround the label only accepting ints (which may be only 32 bit wide) const double percent = used/(double)total*100; const int percentInt = qMin(qRound(percent), 100); ui->quotaProgressBar->setValue(percentInt); QString usedStr = Utility::octetsToString(used); QString totalStr = Utility::octetsToString(total); QString percentStr = Utility::compactFormatDouble(percent, 1); 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); ui->quotaInfoLabel->setText(tr("%1 of %2 in use").arg(usedStr, totalStr)); ui->quotaInfoLabel->setToolTip(toolTip); ui->quotaProgressBar->setToolTip(toolTip); } else { ui->quotaProgressBar->setVisible(false); ui->quotaInfoLabel->setText(tr("Currently there is no storage usage information available.")); } } void AccountSettings::slotAccountStateChanged(int state) { if (_accountState) { ui->sslButton->updateAccountState(_accountState); AccountPtr account = _accountState->account(); QUrl safeUrl(account->url()); safeUrl.setPassword(QString()); // Remove the password from the URL to avoid showing it in the UI FolderMan *folderMan = FolderMan::instance(); foreach (Folder *folder, folderMan->map().values()) { _model->slotUpdateFolderState(folder); } QString server = QString::fromLatin1("%2").arg(account->url().toString(), safeUrl.toString()); QString serverWithUser = server; if (AbstractCredentials *cred = account->credentials()) { serverWithUser = tr("%1 as %2").arg(server, cred->user()); } if (state != AccountState::SignedOut && ui->signInButton->hasFocus()) { // The button is about to be hidden, clear the focus so the focus don't go to the // "remove account" button ui->signInButton->clearFocus(); } ui->signInButton->setVisible(state == AccountState::SignedOut); if (state == AccountState::Connected) { showConnectionLabel( tr("Connected to %1.").arg(serverWithUser) ); } else if (state == AccountState::ServiceUnavailable) { showConnectionLabel( tr("Server %1 is temporarily unavailable.").arg(server) ); } else if (state == AccountState::SignedOut) { showConnectionLabel( tr("Signed out from %1.").arg(serverWithUser) ); } else { showConnectionLabel( tr("No connection to %1 at %2.") .arg(Theme::instance()->appNameGUI(), server), _accountState->connectionErrors() ); } } else { // ownCloud is not yet configured. showConnectionLabel( tr("No %1 connection configured.").arg(Theme::instance()->appNameGUI()) ); } /* Allow to expand the item if the account is connected. */ ui->_folderList->setItemsExpandable( state == AccountState::Connected ); /* check if there are expanded root items, if so, close them, if the state is different from being Connected. */ if( state != AccountState::Connected ) { int i; for (i = 0; i < _model->rowCount(); ++i) { if (ui->_folderList->isExpanded(_model->index(i))) ui->_folderList->setExpanded(_model->index(i), false); } } } void AccountSettings::slotLinkActivated(const QString& link) { // Parse folder alias and filename from the link, calculate the index // and select it if it exists. const QStringList li = link.split(QLatin1String("?folder=")); if( li.count() > 1) { QString myFolder = li[0]; const QString alias = li[1]; if(myFolder.endsWith(QLatin1Char('/'))) myFolder.chop(1); Folder *f = FolderMan::instance()->folder(alias); QModelIndex folderIndx = _model->indexForPath(f, QString()); if( !ui->_folderList->isExpanded(folderIndx)) { ui->_folderList->setExpanded(folderIndx, true); return; } QModelIndex indx = _model->indexForPath(f, myFolder); if( indx.isValid() ) { ui->_folderList->setSelectionMode(QAbstractItemView::SingleSelection); ui->_folderList->setCurrentIndex(indx); ui->_folderList->scrollTo(indx); } else { qDebug() << "Unable to find a valid index for " << myFolder; } } } AccountSettings::~AccountSettings() { delete ui; } void AccountSettings::refreshSelectiveSyncStatus() { bool shouldBeVisible = _model->isDirty(); for (int i = 0; !shouldBeVisible && i < _model->rowCount(); ++i) { auto index = _model->index(i); if (ui->_folderList->isExpanded(index) && _model->rowCount(index) > 0) { shouldBeVisible = true; } } QString msg; int cnt = 0; foreach (Folder *folder, FolderMan::instance()->map().values()) { if (folder->accountState() != _accountState) { continue; } auto undecidedList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList); QString p; foreach(const auto &it, undecidedList) { // FIXME: add the folder alias in a hoover hint. // folder->alias() + QLatin1String("/") if( cnt++ ) { msg += QLatin1String(", "); } QString myFolder = (it); if( myFolder.endsWith('/')) { myFolder.chop(1); } QModelIndex theIndx = _model->indexForPath(folder, myFolder); if(theIndx.isValid()) { msg += QString::fromLatin1("%1").arg(myFolder).arg(folder->alias()); } else { msg += myFolder; // no link because we do not know the index yet. } } } if (msg.isEmpty()) { ui->selectiveSyncNotification->setVisible(false); ui->selectiveSyncNotification->setText(QString()); } else { ui->selectiveSyncNotification->setVisible(true); QString wholeMsg = tr("There are new folders that were not synchronized because they are too big: ") + msg; ui->selectiveSyncNotification->setText(wholeMsg); shouldBeVisible = true; } ui->selectiveSyncApply->setEnabled(_model->isDirty() || !msg.isEmpty()); bool wasVisible = !ui->selectiveSyncStatus->isHidden(); if (wasVisible != shouldBeVisible) { QSize hint = ui->selectiveSyncStatus->sizeHint(); if (shouldBeVisible) { ui->selectiveSyncStatus->setMaximumHeight(0); ui->selectiveSyncStatus->setVisible(true); } auto anim = new QPropertyAnimation(ui->selectiveSyncStatus, "maximumHeight", ui->selectiveSyncStatus); anim->setEndValue(shouldBeVisible ? hint.height() : 0); anim->start(QAbstractAnimation::DeleteWhenStopped); if (!shouldBeVisible) { connect(anim, SIGNAL(finished()), ui->selectiveSyncStatus, SLOT(hide())); } } } void AccountSettings::slotSignInAccount() { _accountState->setSignedOut(false); } void AccountSettings::slotDeleteAccount() { // Deleting the account potentially deletes 'this', so // the QMessageBox should be destroyed before that happens. { QMessageBox messageBox(QMessageBox::Question, tr("Confirm Account Removal"), tr("

Do you really want to remove the connection to the account %1?

" "

Note: This will not delete any files.

") .arg(_accountState->account()->displayName()), QMessageBox::NoButton, this); QPushButton* yesButton = messageBox.addButton(tr("Remove connection"), QMessageBox::YesRole); messageBox.addButton(tr("Cancel"), QMessageBox::NoRole); messageBox.exec(); if (messageBox.clickedButton() != yesButton) { return; } } auto manager = AccountManager::instance(); manager->deleteAccount(_accountState); manager->save(); // if there is no more account, show the wizard. if( manager->accounts().isEmpty() ) { OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int))); } } bool AccountSettings::event(QEvent* e) { if (e->type() == QEvent::Hide || e->type() == QEvent::Show) { _quotaInfo.setActive(isVisible()); } if (e->type() == QEvent::Show) { ui->_folderList->setExpanded(_model->index(0, 0), true); } return QWidget::event(e); } } // namespace OCC