diff --git a/Apper/ApperKCM.cpp b/Apper/ApperKCM.cpp index 2d6e841..f0983c5 100644 --- a/Apper/ApperKCM.cpp +++ b/Apper/ApperKCM.cpp @@ -1,922 +1,922 @@ /*************************************************************************** * Copyright (C) 2008-2018 by Daniel Nicoletti * * dantti12@gmail.com * * * * 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. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ApperKCM.h" #include "ui_ApperKCM.h" #include //#include #include #include #include #include #include #include #include //#include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_APPSTREAM #include #endif #include #include #include "FiltersMenu.h" #include "BrowseView.h" #include "CategoryModel.h" #include "TransactionHistory.h" #include "Settings/Settings.h" #include "Updater/Updater.h" Q_LOGGING_CATEGORY(APPER, "apper") #define BAR_SEARCH 0 #define BAR_UPDATE 1 #define BAR_SETTINGS 2 #define BAR_TITLE 3 ApperKCM::ApperKCM(QWidget *parent) : QWidget(parent), ui(new Ui::ApperKCM), m_findIcon(QIcon::fromTheme("edit-find")), m_cancelIcon(QIcon::fromTheme("dialog-cancel")) { ui->setupUi(this); // store the actions supported by the backend connect(Daemon::global(), &Daemon::changed, this, &ApperKCM::daemonChanged); // Set the current locale Daemon::global()->setHints(QLatin1String("locale=") + QLocale::system().name() + QLatin1String(".UTF-8")); // Browse TAB ui->backTB->setIcon(QIcon::fromTheme("go-previous")); // create our toolbar auto toolBar = new QToolBar(this); ui->gridLayout_2->addWidget(toolBar); toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); connect(ui->browseView, &BrowseView::categoryActivated, this, &ApperKCM::on_homeView_activated); auto findMenu = new QMenu(this); // find is just a generic name in case we don't have any search method m_genericActionK = new KToolBarPopupAction(m_findIcon, i18n("Find"), this); toolBar->addAction(m_genericActionK); // Add actions that the backend supports findMenu->addAction(ui->actionFindName); setCurrentAction(ui->actionFindName); findMenu->addAction(ui->actionFindDescription); if (!m_currentAction) { setCurrentAction(ui->actionFindDescription); } findMenu->addAction(ui->actionFindFile); if (!m_currentAction) { setCurrentAction(ui->actionFindFile); } // If no action was set we can't use this search if (m_currentAction == 0) { m_genericActionK->setEnabled(false); ui->searchKLE->setEnabled(false); } else { // Check to see if we need the KToolBarPopupAction setCurrentActionCancel(false); if (findMenu->actions().size() > 1) { m_currentAction->setVisible(false); m_genericActionK->setMenu(findMenu); } else { m_currentAction->setVisible(true); toolBar->removeAction(m_genericActionK); toolBar->addAction(m_currentAction); } connect(m_genericActionK, &KToolBarPopupAction::triggered, this, &ApperKCM::genericActionKTriggered); } // Create the groups model m_groupsModel = new CategoryModel(this); ui->browseView->setCategoryModel(m_groupsModel); connect(m_groupsModel, &CategoryModel::finished, this, &ApperKCM::setupHomeModel); ui->homeView->setSpacing(10); ui->homeView->viewport()->setAttribute(Qt::WA_Hover); KFileItemDelegate *delegate = new KFileItemDelegate(this); delegate->setWrapMode(QTextOption::WordWrap); ui->homeView->setItemDelegate(delegate); // install the backend filters ui->filtersTB->setMenu(m_filtersMenu = new FiltersMenu(this)); connect(m_filtersMenu, &FiltersMenu::filtersChanged, this, &ApperKCM::search); ui->filtersTB->setIcon(QIcon::fromTheme("view-filter")); ApplicationSortFilterModel *proxy = ui->browseView->proxy(); proxy->setApplicationFilter(m_filtersMenu->filterApplications()); connect(m_filtersMenu, QOverload::of(&FiltersMenu::filterApplications), proxy, &ApplicationSortFilterModel::setApplicationFilter); //initialize the model, delegate, client and connect it's signals m_browseModel = ui->browseView->model(); // CHANGES TAB ui->changesView->viewport()->setAttribute(Qt::WA_Hover); m_changesModel = new PackageModel(this); auto changedProxy = new KCategorizedSortFilterProxyModel(this); changedProxy->setSourceModel(m_changesModel); changedProxy->setDynamicSortFilter(true); changedProxy->setCategorizedModel(true); changedProxy->setSortCaseSensitivity(Qt::CaseInsensitive); changedProxy->setSortRole(PackageModel::SortRole); changedProxy->sort(0); ui->changesView->setModel(changedProxy); auto changesDelegate = new ChangesDelegate(ui->changesView); changesDelegate->setExtendPixmapWidth(0); ui->changesView->setItemDelegate(changesDelegate); // Connect this signal to keep track of changes connect(m_browseModel, &PackageModel::changed, this, &ApperKCM::checkChanged); // packageUnchecked from changes model connect(m_changesModel, &PackageModel::packageUnchecked, m_changesModel, &PackageModel::removePackage); connect(m_changesModel, &PackageModel::packageUnchecked, m_browseModel, &PackageModel::uncheckPackageDefault); ui->reviewMessage->setIcon(QIcon::fromTheme("edit-redo")); ui->reviewMessage->setText(i18n("Some software changes were made")); auto reviewAction = new QAction(i18n("Review"), this); connect(reviewAction, &QAction::triggered, this, &ApperKCM::showReviewPages); ui->reviewMessage->addAction(reviewAction); auto discardAction = new QAction(i18n("Discard"), this); connect(discardAction, &QAction::triggered, m_browseModel, &PackageModel::uncheckAll); ui->reviewMessage->addAction(discardAction); auto applyAction = new QAction(i18n("Apply"), this); connect(applyAction, &QAction::triggered, this, &ApperKCM::save); ui->reviewMessage->addAction(applyAction); ui->reviewMessage->setCloseButtonVisible(false); ui->reviewMessage->hide(); connect(ui->reviewMessage, &KMessageWidget::showAnimationFinished, this, [this] () { if (!ui->reviewMessage->property("HasChanges").toBool()) { ui->reviewMessage->animatedHide(); } }); connect(ui->reviewMessage, &KMessageWidget::hideAnimationFinished, this, [this] () { if (ui->reviewMessage->property("HasChanges").toBool()) { ui->reviewMessage->animatedShow(); } }); auto menu = new QMenu(this); ui->settingsTB->setMenu(menu); ui->settingsTB->setIcon(QIcon::fromTheme("preferences-other")); auto signalMapper = new QSignalMapper(this); connect(signalMapper, QOverload::of(&QSignalMapper::mapped), this, &ApperKCM::setPage); QAction *action; action = menu->addAction(QIcon::fromTheme("view-history"), i18n("History")); signalMapper->setMapping(action, "history"); connect(action, &QAction::triggered, signalMapper, QOverload<>::of(&QSignalMapper::map)); action = menu->addAction(QIcon::fromTheme("preferences-other"), i18n("Settings")); signalMapper->setMapping(action, "settings"); connect(action, &QAction::triggered, signalMapper, QOverload<>::of(&QSignalMapper::map)); auto helpMenu = new KHelpMenu(this, KAboutData::applicationData()); menu->addMenu(helpMenu->menu()); // Make sure the search bar is visible ui->stackedWidgetBar->setCurrentIndex(BAR_SEARCH); } void ApperKCM::setupHomeModel() { KCategorizedSortFilterProxyModel *oldProxy = m_groupsProxyModel; m_groupsProxyModel = new KCategorizedSortFilterProxyModel(this); m_groupsProxyModel->setSourceModel(m_groupsModel); m_groupsProxyModel->setCategorizedModel(true); m_groupsProxyModel->sort(0); ui->homeView->setModel(m_groupsProxyModel); if (oldProxy) { oldProxy->deleteLater(); } } void ApperKCM::genericActionKTriggered() { m_currentAction->trigger(); } void ApperKCM::setCurrentAction(QAction *action) { // just load the new action if it changes this // also ensures that our menu has more than one action if (m_currentAction != action) { // hides the item from the list action->setVisible(false); // ensures the current action was created if (m_currentAction) { // show the item back in the list m_currentAction->setVisible(true); } m_currentAction = action; // copy data from the curront action m_genericActionK->setText(m_currentAction->text()); m_genericActionK->setIcon(m_currentAction->icon()); } } void ApperKCM::setCurrentActionEnabled(bool state) { if (m_currentAction) { m_currentAction->setEnabled(state); } m_genericActionK->setEnabled(state); } void ApperKCM::setCurrentActionCancel(bool cancel) { if (cancel) { // every action should like cancel ui->actionFindName->setText(i18n("&Cancel")); ui->actionFindFile->setText(i18n("&Cancel")); ui->actionFindDescription->setText(i18n("&Cancel")); m_genericActionK->setText(i18n("&Cancel")); // set cancel icons ui->actionFindFile->setIcon(m_cancelIcon); ui->actionFindDescription->setIcon(m_cancelIcon); ui->actionFindName->setIcon(m_cancelIcon); m_genericActionK->setIcon(m_cancelIcon); } else { ui->actionFindName->setText(i18n("Find by &name")); ui->actionFindFile->setText(i18n("Find by f&ile name")); ui->actionFindDescription->setText(i18n("Find by &description")); // Define actions icon ui->actionFindFile->setIcon(QIcon::fromTheme("document-open")); ui->actionFindDescription->setIcon(QIcon::fromTheme("document-edit")); ui->actionFindName->setIcon(m_findIcon); m_genericActionK->setIcon(m_findIcon); if (m_currentAction) { m_genericActionK->setText(m_currentAction->text()); } else { // This might happen when the backend can // only search groups m_genericActionK->setText(i18n("Find")); } } } void ApperKCM::checkChanged() { bool hasChanges = false; if (ui->stackedWidget->currentWidget() == ui->pageHome || ui->stackedWidget->currentWidget() == ui->pageChanges || ui->stackedWidget->currentWidget() == ui->pageBrowse) { hasChanges = m_browseModel->hasChanges(); if (!hasChanges && ui->stackedWidget->currentWidget() == ui->pageChanges) { search(); } if (hasChanges) { if (!ui->reviewMessage->isHideAnimationRunning() && !ui->reviewMessage->isShowAnimationRunning()) { ui->reviewMessage->animatedShow(); } } else { if (!ui->reviewMessage->isHideAnimationRunning() && !ui->reviewMessage->isShowAnimationRunning()) { ui->reviewMessage->animatedHide(); } } ui->reviewMessage->setProperty("HasChanges", hasChanges); } else if (ui->stackedWidget->currentWidget() == m_updaterPage) { hasChanges = m_updaterPage->hasChanges(); } else if (ui->stackedWidget->currentWidget() == m_settingsPage) { hasChanges = m_settingsPage->hasChanges(); } emit changed(hasChanges); } void ApperKCM::errorCode(PackageKit::Transaction::Error error, const QString &details) { if (error != Transaction::ErrorTransactionCancelled) { KMessageBox::detailedSorry(this, PkStrings::errorMessage(error), details, PkStrings::error(error), KMessageBox::Notify); } } ApperKCM::~ApperKCM() { delete ui; } void ApperKCM::daemonChanged() { Transaction::Roles roles = Daemon::roles(); if (m_roles == roles) { return; } m_roles = roles; // Add actions that the backend supports ui->actionFindName->setEnabled(roles & Transaction::RoleSearchName); ui->actionFindDescription->setEnabled(roles & Transaction::RoleSearchDetails); ui->actionFindFile->setEnabled(roles & Transaction::RoleSearchFile); ui->browseView->init(roles); m_groupsModel->setRoles(roles); m_filtersMenu->setFilters(Daemon::filters()); } void ApperKCM::on_actionFindName_triggered() { setCurrentAction(ui->actionFindName); if (!ui->searchKLE->text().isEmpty()) { // cache the search m_searchRole = Transaction::RoleSearchName; m_searchString = ui->searchKLE->text(); // create the main transaction search(); } } void ApperKCM::on_actionFindDescription_triggered() { setCurrentAction(ui->actionFindDescription); if (!ui->searchKLE->text().isEmpty()) { // cache the search m_searchRole = Transaction::RoleSearchDetails; m_searchString = ui->searchKLE->text(); // create the main transaction search(); } } void ApperKCM::on_actionFindFile_triggered() { setCurrentAction(ui->actionFindFile); if (!ui->searchKLE->text().isEmpty()) { // cache the search m_searchRole = Transaction::RoleSearchFile; m_searchString = ui->searchKLE->text(); // create the main transaction search(); } } void ApperKCM::on_homeView_activated(const QModelIndex &index) { if (index.isValid()) { const auto proxy = qobject_cast(index.model()); // If the cast failed it's the index came from browseView if (proxy) { m_searchParentCategory = proxy->mapToSource(index); } else { m_searchParentCategory = index; } // cache the search m_searchRole = static_cast(index.data(CategoryModel::SearchRole).toUInt()); qCDebug(APPER) << m_searchRole << index.data(CategoryModel::CategoryRole).toString(); if (m_searchRole == Transaction::RoleResolve) { #ifdef HAVE_APPSTREAM CategoryMatcher parser = index.data(CategoryModel::CategoryRole).value(); // m_searchCategory = AppStream::instance()->findPkgNames(parser); #endif // HAVE_APPSTREAM } else if (m_searchRole == Transaction::RoleSearchGroup) { if (index.data(CategoryModel::GroupRole).type() == QVariant::String) { QString category = index.data(CategoryModel::GroupRole).toString(); if (category.startsWith('@') || (category.startsWith(QLatin1String("repo:")) && category.size() > 5)) { m_searchGroupCategory = category; } else { m_groupsModel->setRootIndex(m_searchParentCategory); ui->backTB->setEnabled(true); return; } } else { m_searchGroupCategory.clear(); int groupRole = index.data(CategoryModel::GroupRole).toInt(); m_searchGroup = static_cast(groupRole); m_searchString = index.data().toString(); // Store the nice name to change the title } } else if (m_searchRole == Transaction::RoleGetUpdates) { setPage("updates"); return; } // create the main transaction search(); } } bool ApperKCM::canChangePage() { bool changed; // Check if we can change the current page if (ui->stackedWidget->currentWidget() == m_updaterPage) { changed = m_updaterPage->hasChanges(); } else if (ui->stackedWidget->currentWidget() == m_settingsPage) { changed = m_settingsPage->hasChanges(); } else { changed = m_browseModel->hasChanges(); } // if there are no changes don't ask the user if (!changed) { return true; } const int queryUser = KMessageBox::warningYesNoCancel( this, i18n("The settings of the current module have changed.\n" "Do you want to apply the changes or discard them?"), i18n("Apply Settings"), KStandardGuiItem::apply(), KStandardGuiItem::discard(), KStandardGuiItem::cancel()); switch (queryUser) { case KMessageBox::Yes: save(); return true; case KMessageBox::No: load(); return true; case KMessageBox::Cancel: return false; default: return false; } } QString ApperKCM::page() const { return QString(); } void ApperKCM::setPage(const QString &page) { auto transaction = qobject_cast(ui->stackedWidget->currentWidget()); if (transaction) { return; } if (page == QLatin1String("settings")) { if (ui->stackedWidget->currentWidget() != m_settingsPage) { if (!canChangePage()) { return; } if (m_settingsPage == 0) { m_settingsPage = new Settings(m_roles, this); connect(m_settingsPage, &Settings::changed, this, &ApperKCM::checkChanged); connect(m_settingsPage, &Settings::refreshCache, this, &ApperKCM::refreshCache); ui->stackedWidget->addWidget(m_settingsPage); connect(ui->generalSettingsPB, &QPushButton::toggled, m_settingsPage, &Settings::showGeneralSettings); connect(ui->repoSettingsPB, &QPushButton::toggled, m_settingsPage, &Settings::showRepoSettings); } checkChanged(); // ui->buttonBox->clear(); // ui->buttonBox->setStandardButtons(QDialogButtonBox::Apply | QDialogButtonBox::Reset); // setButtons(KCModule::Default | KCModule::Apply); emit changed(true); // THIS IS DUMB setButtons only take effect after changed goes true emit changed(false); ui->generalSettingsPB->setChecked(true); ui->stackedWidgetBar->setCurrentIndex(BAR_SETTINGS); ui->stackedWidget->setCurrentWidget(m_settingsPage); m_settingsPage->load(); ui->titleL->clear(); ui->backTB->setEnabled(true); emit caption(i18n("Settings")); } } else if (page == QLatin1String("updates")) { if (ui->stackedWidget->currentWidget() != m_updaterPage) { if (!canChangePage()) { return; } if (m_updaterPage == nullptr) { m_updaterPage = new Updater(m_roles, this); connect(m_updaterPage, &Updater::refreshCache, this, &ApperKCM::refreshCache); connect(m_updaterPage, &Updater::downloadSize, ui->downloadL, &QLabel::setText); - connect(m_updaterPage, &Updater::changed, this, &ApperKCM::checkChanged); +// connect(m_updaterPage, &Updater::changed, this, &ApperKCM::checkChanged); ui->stackedWidget->addWidget(m_updaterPage); ui->checkUpdatesPB->setIcon(QIcon::fromTheme("view-refresh")); connect(ui->checkUpdatesPB, &QPushButton::clicked, this, &ApperKCM::refreshCache); ui->updatePB->setIcon(QIcon::fromTheme(QLatin1String("system-software-update"))); connect(ui->updatePB, &QPushButton::clicked, this, &ApperKCM::save); connect(m_updaterPage, &Updater::changed, ui->updatePB, &QPushButton::setEnabled); } checkChanged(); ui->stackedWidget->setCurrentWidget(m_updaterPage); m_updaterPage->load(); ui->stackedWidgetBar->setCurrentIndex(BAR_UPDATE); ui->backTB->setEnabled(true); emit caption(i18n("Updates")); } } else if (page == QLatin1String("home")) { if (ui->stackedWidget->currentWidget() == m_updaterPage || ui->stackedWidget->currentWidget() == m_settingsPage) { on_backTB_clicked(); } } else if (page == QLatin1String("history")) { m_history = new TransactionHistory(this); ui->searchKLE->clear(); connect(ui->searchKLE, &QLineEdit::textChanged, m_history, &TransactionHistory::setFilterRegExp); ui->stackedWidget->addWidget(m_history); ui->stackedWidget->setCurrentWidget(m_history); ui->backTB->setEnabled(true); ui->filtersTB->setEnabled(false); ui->widget->setEnabled(false); emit caption(i18n("History")); } } void ApperKCM::on_backTB_clicked() { bool canGoBack = false; if (ui->stackedWidget->currentWidget() == ui->pageBrowse) { if (!ui->browseView->goBack()) { return; } else if (m_groupsModel->hasParent()) { canGoBack = true; } } else if (ui->stackedWidget->currentWidget() == m_history) { ui->filtersTB->setEnabled(true); ui->widget->setEnabled(true); m_history->deleteLater(); m_history = 0; } else if (ui->stackedWidget->currentWidget() == ui->pageHome) { if (m_groupsModel->setParentIndex()) { // if we are able to set a new parent item // do not disable back button return; } } else if (ui->stackedWidget->currentWidget() == m_updaterPage) { if (!canChangePage()) { return; } ui->stackedWidgetBar->setCurrentIndex(BAR_SEARCH); checkChanged(); } else if (ui->stackedWidget->currentWidget() == m_settingsPage) { if (!canChangePage()) { return; } emit changed(true); // THIS IS DUMB setButtons only take effect after changed goes true ui->stackedWidgetBar->setCurrentIndex(BAR_SEARCH); checkChanged(); } ui->homeView->selectionModel()->clear(); ui->stackedWidget->setCurrentWidget(ui->pageHome); ui->backTB->setEnabled(canGoBack); // reset the search role m_searchRole = Transaction::RoleUnknown; emit caption(); } void ApperKCM::showReviewPages() { m_changesModel->clear(); m_changesModel->addSelectedPackagesFromModel(m_browseModel); ui->stackedWidget->setCurrentWidget(ui->pageChanges); ui->backTB->setEnabled(true); emit caption(i18n("Pending Changes")); } void ApperKCM::disconnectTransaction() { if (m_searchTransaction) { // Disconnect everything so that the model don't store // wrong data m_searchTransaction->cancel(); disconnect(m_searchTransaction, &Transaction::finished, ui->browseView->busyCursor(), &KPixmapSequenceOverlayPainter::stop); disconnect(m_searchTransaction, &Transaction::finished, this, &ApperKCM::finished); disconnect(m_searchTransaction, &Transaction::finished, m_browseModel, &PackageModel::finished); disconnect(m_searchTransaction, &Transaction::finished, m_browseModel, &PackageModel::fetchSizes); disconnect(m_searchTransaction, &Transaction::package, m_browseModel, &PackageModel::addNotSelectedPackage); disconnect(m_searchTransaction, &Transaction::errorCode, this, &ApperKCM::errorCode); } } void ApperKCM::search() { ui->browseView->cleanUi(); if (ui->stackedWidgetBar->currentIndex() != BAR_SEARCH) { ui->stackedWidgetBar->setCurrentIndex(BAR_SEARCH); } disconnectTransaction(); // search switch (m_searchRole) { case Transaction::RoleSearchName: m_searchTransaction = Daemon::searchNames(m_searchString, m_filtersMenu->filters()); emit caption(m_searchString); break; case Transaction::RoleSearchDetails: m_searchTransaction = Daemon::searchDetails(m_searchString, m_filtersMenu->filters()); emit caption(m_searchString); break; case Transaction::RoleSearchFile: m_searchTransaction = Daemon::searchFiles(m_searchString, m_filtersMenu->filters()); emit caption(m_searchString); break; case Transaction::RoleSearchGroup: if (m_searchGroupCategory.isEmpty()) { m_searchTransaction = Daemon::searchGroup(m_searchGroup, m_filtersMenu->filters()); // m_searchString has the group nice name emit caption(m_searchString); } else { ui->browseView->setParentCategory(m_searchParentCategory); emit caption(m_searchParentCategory.data().toString()); #ifndef HAVE_APPSTREAM if (m_searchGroupCategory.startsWith(QLatin1Char('@')) || m_searchGroupCategory.startsWith(QLatin1String("repo:"))) { m_searchTransaction = Daemon::searchGroup(m_searchGroupCategory, m_filtersMenu->filters()); } #endif // else the transaction is useless } break; case Transaction::RoleGetPackages: // we want all the installed ones ui->browseView->disableExportInstalledPB(); m_searchTransaction = Daemon::getPackages(Transaction::FilterInstalled | m_filtersMenu->filters()); connect(m_searchTransaction, &Transaction::finished, ui->browseView, &BrowseView::enableExportInstalledPB); emit caption(i18n("Installed Software")); break; case Transaction::RoleResolve: #ifdef HAVE_APPSTREAM if (!m_searchCategory.isEmpty()) { ui->browseView->setParentCategory(m_searchParentCategory); // WARNING the resolve might fail if the backend // has a low limit MaximumItemsToResolve m_searchTransaction = Daemon::resolve(m_searchCategory, m_filtersMenu->filters()); emit caption(m_searchParentCategory.data().toString()); } else { ui->browseView->setParentCategory(m_searchParentCategory); KMessageBox::sorry(this, i18n("Could not find an application that matched this category")); emit caption(); disconnectTransaction(); m_searchTransaction = 0; return; } break; #endif default: qCWarning(APPER) << "Search type not defined yet"; emit caption(); disconnectTransaction(); m_searchTransaction = 0; return; } connect(m_searchTransaction, &Transaction::finished, ui->browseView->busyCursor(), &KPixmapSequenceOverlayPainter::stop); connect(m_searchTransaction, &Transaction::finished, this, &ApperKCM::finished); connect(m_searchTransaction, &Transaction::finished, m_browseModel, &PackageModel::finished); if (ui->browseView->isShowingSizes()) { connect(m_searchTransaction, &Transaction::finished, m_browseModel, &PackageModel::fetchSizes); } connect(m_searchTransaction, &Transaction::package, m_browseModel, &PackageModel::addNotSelectedPackage); connect(m_searchTransaction, &Transaction::errorCode, this, &ApperKCM::errorCode); // cleans the models m_browseModel->clear(); ui->browseView->showInstalledPanel(m_searchRole == Transaction::RoleGetPackages); ui->browseView->busyCursor()->start(); ui->backTB->setEnabled(true); setCurrentActionCancel(true); setCurrentActionEnabled(m_searchTransaction->allowCancel()); ui->stackedWidget->setCurrentWidget(ui->pageBrowse); } void ApperKCM::changed() { Transaction *trans = qobject_cast(sender()); setCurrentActionEnabled(trans->allowCancel()); } void ApperKCM::refreshCache() { emit changed(false); QWidget *currentWidget = ui->stackedWidget->currentWidget(); auto transactionW = new PkTransactionWidget(this); connect(transactionW, &PkTransactionWidget::titleChangedProgress, this, &ApperKCM::caption); QPointer transaction = new PkTransaction(transactionW); Daemon::setHints (QLatin1String("cache-age=")+QString::number(m_cacheAge)); transaction->refreshCache(m_forceRefreshCache); transactionW->setTransaction(transaction, Transaction::RoleRefreshCache); ui->stackedWidget->addWidget(transactionW); ui->stackedWidget->setCurrentWidget(transactionW); ui->stackedWidgetBar->setCurrentIndex(BAR_TITLE); ui->backTB->setEnabled(false); connect(transactionW, &PkTransactionWidget::titleChanged, ui->titleL, &QLabel::setText); QEventLoop loop; connect(transaction, &PkTransaction::finished, &loop, &QEventLoop::quit); // wait for the end of transaction if (!transaction->isFinished()) { loop.exec(); if (!transaction) { // Avoid crashing return; } // If the refresh failed force next refresh Cache call m_forceRefreshCache = transaction->exitStatus() == PkTransaction::Failed; } if (m_updaterPage) { m_updaterPage->getUpdates(); } if (currentWidget == m_settingsPage) { setPage("settings"); } else { setPage("updates"); } QTimer::singleShot(0, this, &ApperKCM::checkChanged); } void ApperKCM::save() { QWidget *currentWidget = ui->stackedWidget->currentWidget(); if (currentWidget == m_settingsPage) { m_settingsPage->save(); } else { ui->reviewMessage->hide(); auto transactionW = new PkTransactionWidget(this); connect(transactionW, &PkTransactionWidget::titleChangedProgress, this, &ApperKCM::caption); QPointer transaction = new PkTransaction(transactionW); ui->stackedWidget->addWidget(transactionW); ui->stackedWidget->setCurrentWidget(transactionW); ui->stackedWidgetBar->setCurrentIndex(BAR_TITLE); ui->backTB->setEnabled(false); connect(transactionW, &PkTransactionWidget::titleChanged, ui->titleL, &QLabel::setText); emit changed(false); QEventLoop loop; connect(transaction, &PkTransaction::finished, &loop, &QEventLoop::quit); if (currentWidget == m_updaterPage) { transaction->updatePackages(m_updaterPage->packagesToUpdate()); transactionW->setTransaction(transaction, Transaction::RoleUpdatePackages); // wait for the end of transaction if (!transaction->isFinished()) { loop.exec(); if (!transaction) { // Avoid crashing return; } } } else { // install then remove packages QStringList installPackages = m_browseModel->selectedPackagesToInstall(); if (!installPackages.isEmpty()) { transaction->installPackages(installPackages); transactionW->setTransaction(transaction, Transaction::RoleInstallPackages); // wait for the end of transaction if (!transaction->isFinished()) { loop.exec(); if (!transaction) { // Avoid crashing return; } } if (transaction->exitStatus() == PkTransaction::Success) { m_browseModel->uncheckAvailablePackages(); } } QStringList removePackages = m_browseModel->selectedPackagesToRemove(); if (!removePackages.isEmpty()) { transaction->removePackages(removePackages); transactionW->setTransaction(transaction, Transaction::RoleRemovePackages); // wait for the end of transaction if (!transaction->isFinished()) { loop.exec(); if (!transaction) { // Avoid crashing return; } } if (transaction->exitStatus() == PkTransaction::Success) { m_browseModel->uncheckInstalledPackages(); } } } transaction->deleteLater(); if (currentWidget == m_updaterPage) { m_updaterPage->getUpdates(); setPage("updates"); } else { // install then remove packages search(); } QTimer::singleShot(0, this, &ApperKCM::checkChanged); } } void ApperKCM::load() { if (ui->stackedWidget->currentWidget() == m_updaterPage) { m_updaterPage->load(); } else if (ui->stackedWidget->currentWidget() == m_settingsPage) { m_settingsPage->load(); } else { // set focus on the search lineEdit ui->searchKLE->setFocus(Qt::OtherFocusReason); m_browseModel->setAllChecked(false); } } void ApperKCM::defaults() { if (ui->stackedWidget->currentWidget() == m_settingsPage) { m_settingsPage->defaults(); } } void ApperKCM::finished() { // if m_currentAction is false means that our // find button should be disable as there aren't any // search methods setCurrentActionEnabled(m_currentAction); setCurrentActionCancel(false); m_searchTransaction = 0; } void ApperKCM::keyPressEvent(QKeyEvent *event) { if (ui->searchKLE->hasFocus() && ui->stackedWidget->currentWidget() != m_history && (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)) { // special tab handling here m_currentAction->trigger(); return; } // KCModule::keyPressEvent(event); } void ApperKCM::closeEvent(QCloseEvent *event) { // PkTransaction *transaction = qobject_cast(stackedWidget->currentWidget()); // if (transaction) { event->ignore(); // } else { // event->accept(); // } } #include "ApperKCM.moc" diff --git a/apperd/ApperdThread.cpp b/apperd/ApperdThread.cpp index 1269edc..e88e824 100644 --- a/apperd/ApperdThread.cpp +++ b/apperd/ApperdThread.cpp @@ -1,331 +1,331 @@ /*************************************************************************** * Copyright (C) 2012 by Daniel Nicoletti * * * * 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. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ApperdThread.h" #include "RefreshCacheTask.h" #include "Updater.h" #include "DistroUpgrade.h" #include "TransactionWatcher.h" #include "DBusInterface.h" #include "RebootListener.h" #include #include -//#include +#include #include #include #include #include #include //#include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(APPER_DAEMON) #define FIVE_MIN 360000 #define ONE_MIN 72000 /* * What we need: * - Refresh the package cache periodicaly (implies listenning to PK's updates changed) * - Update or Display Package Updates * - Display Distro Upgrades * - Start Sentinel to keep an eye on running transactions */ using namespace PackageKit; //using namespace Solid; ApperdThread::ApperdThread(QObject *parent) : QObject(parent), m_proxyChanged(true), m_AptRebootListener(new AptRebootListener(this)) { } ApperdThread::~ApperdThread() { } void ApperdThread::init() { // connect(PowerManagement::notifier(), SIGNAL(appShouldConserveResourcesChanged(bool)), // this, SLOT(appShouldConserveResourcesChanged())); // This timer keeps polling to see if it has // to refresh the cache m_qtimer = new QTimer(this); m_qtimer->setInterval(FIVE_MIN); connect(m_qtimer, &QTimer::timeout, this, &ApperdThread::poll); m_qtimer->start(); //check if any changes to the file occour //this also prevents from reading when a checkUpdate happens - KDirWatch *confWatch = new KDirWatch(this); -// confWatch->addFile(KStandardDirs::locateLocal("config", "apper")); + auto confWatch = new KDirWatch(this); + confWatch->addFile(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1String("/apper")); connect(confWatch, SIGNAL(dirty(QString)), this, SLOT(configFileChanged())); connect(confWatch, SIGNAL(created(QString)), this, SLOT(configFileChanged())); connect(confWatch, SIGNAL(deleted(QString)), this, SLOT(configFileChanged())); confWatch->startScan(); // Watch for changes in the KDE proxy settings - KDirWatch *proxyWatch = new KDirWatch(this); -// proxyWatch->addFile(KStandardDirs::locateLocal("config", "kioslaverc")); + auto proxyWatch = new KDirWatch(this); + confWatch->addFile(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1String("/kioslaverc")); connect(proxyWatch, SIGNAL(dirty(QString)), this, SLOT(proxyChanged())); connect(proxyWatch, SIGNAL(created(QString)), this, SLOT(proxyChanged())); connect(proxyWatch, SIGNAL(deleted(QString)), this, SLOT(proxyChanged())); proxyWatch->startScan(); Daemon::global()->setHints(QLatin1String("locale=") + QLocale::system().name() + QLatin1String(".UTF-8")); connect(Daemon::global(), &Daemon::updatesChanged, this, &ApperdThread::updatesChanged); m_interface = new DBusInterface(this); m_refreshCache = new RefreshCacheTask(this); connect(m_interface, &DBusInterface::refreshCache, m_refreshCache, &RefreshCacheTask::refreshCache); m_updater = new Updater(this); m_distroUpgrade = new DistroUpgrade(this); // read the current settings configFileChanged(); // In case PackageKit is not running watch for it's registration to configure proxy auto watcher = new QDBusServiceWatcher(QLatin1String("org.freedesktop.PackageKit"), QDBusConnection::systemBus(), QDBusServiceWatcher::WatchForRegistration, this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &ApperdThread::setProxy); // if PackageKit is running check to see if there are running transactons already bool packagekitIsRunning = nameHasOwner(QLatin1String("org.freedesktop.PackageKit"), QDBusConnection::systemBus()); m_transactionWatcher = new TransactionWatcher(packagekitIsRunning, this); // connect the watch transaction coming from the updater icon to our watcher connect(m_interface, &DBusInterface::watchTransaction, m_transactionWatcher, &TransactionWatcher::watchTransactionInteractive); // listen to Debian/Apt reboot signals from other sources (apt) connect(m_AptRebootListener, &AptRebootListener::requestReboot, m_transactionWatcher, &TransactionWatcher::showRebootNotificationApt); QTimer::singleShot(2 /*minutes*/ * 60 /*seconds*/ * 1000 /*msec*/, m_AptRebootListener, SLOT(checkForReboot())); if (packagekitIsRunning) { // PackageKit is running set the session Proxy setProxy(); // If packagekit is already running go check // for updates updatesChanged(); } else { // Initial check for updates QTimer::singleShot(ONE_MIN, this, SLOT(updatesChanged())); } } // This is called every 5 minutes void ApperdThread::poll() { if (m_lastRefreshCache.isNull()) { // This value wasn't set // convert this to QDateTime m_lastRefreshCache = getTimeSinceRefreshCache(); } // If check for updates is active if (m_configs[CFG_INTERVAL].value() != Enum::Never) { // Find out how many seconds passed since last refresh cache uint secsSinceLastRefresh; secsSinceLastRefresh = QDateTime::currentDateTime().toTime_t() - m_lastRefreshCache.toTime_t(); // If lastRefreshCache is null it means that the cache was never refreshed if (m_lastRefreshCache.isNull() || secsSinceLastRefresh > m_configs[CFG_INTERVAL].value()) { bool ignoreBattery = m_configs[CFG_CHECK_UP_BATTERY].value(); bool ignoreMobile = m_configs[CFG_CHECK_UP_MOBILE].value(); if (isSystemReady(ignoreBattery, ignoreMobile)) { m_refreshCache->refreshCache(); } // Invalidate the last time the cache was refreshed m_lastRefreshCache = QDateTime(); } } } void ApperdThread::configFileChanged() { KConfig config("apper"); KConfigGroup checkUpdateGroup(&config, "CheckUpdate"); m_configs[CFG_CHECK_UP_BATTERY] = checkUpdateGroup.readEntry(CFG_CHECK_UP_BATTERY, DEFAULT_CHECK_UP_BATTERY); m_configs[CFG_CHECK_UP_MOBILE] = checkUpdateGroup.readEntry(CFG_CHECK_UP_MOBILE, DEFAULT_CHECK_UP_MOBILE); m_configs[CFG_INSTALL_UP_BATTERY] = checkUpdateGroup.readEntry(CFG_INSTALL_UP_BATTERY, DEFAULT_INSTALL_UP_BATTERY); m_configs[CFG_INSTALL_UP_MOBILE] = checkUpdateGroup.readEntry(CFG_INSTALL_UP_MOBILE, DEFAULT_INSTALL_UP_MOBILE); m_configs[CFG_AUTO_UP] = checkUpdateGroup.readEntry(CFG_AUTO_UP, Enum::AutoUpdateDefault); m_configs[CFG_INTERVAL] = checkUpdateGroup.readEntry(CFG_INTERVAL, Enum::TimeIntervalDefault); m_configs[CFG_DISTRO_UPGRADE] = checkUpdateGroup.readEntry(CFG_DISTRO_UPGRADE, Enum::DistroUpgradeDefault); m_updater->setConfig(m_configs); m_distroUpgrade->setConfig(m_configs); KDirWatch *confWatch = qobject_cast(sender()); if (confWatch) { // Check for updates again since the config changed updatesChanged(); } } void ApperdThread::proxyChanged() { // We must reparse the configuration since the values are all cached KProtocolManager::reparseConfiguration(); QHash proxyConfig; if (KProtocolManager::proxyType() == KProtocolManager::ManualProxy) { proxyConfig["http"] = KProtocolManager::proxyFor("http"); proxyConfig["https"] = KProtocolManager::proxyFor("https"); proxyConfig["ftp"] = KProtocolManager::proxyFor("ftp"); proxyConfig["socks"] = KProtocolManager::proxyFor("socks"); } // Check if the proxy settings really changed to avoid setting them twice if (proxyConfig != m_proxyConfig) { m_proxyConfig = proxyConfig; m_proxyChanged = true; setProxy(); } } void ApperdThread::setProxy() { if (!m_proxyChanged) { return; } // If we were called by the watcher it is because PackageKit is running bool packagekitIsRunning = true; auto watcher = qobject_cast(sender()); if (!watcher) { packagekitIsRunning = nameHasOwner(QLatin1String("org.freedesktop.PackageKit"), QDBusConnection::systemBus()); } if (packagekitIsRunning) { // Apply the proxy changes only if packagekit is running // use value() to not insert items on the hash Daemon::global()->setProxy(m_proxyConfig.value("http"), m_proxyConfig.value("https"), m_proxyConfig.value("ftp"), m_proxyConfig.value("socks"), QString(), QString()); m_proxyChanged = false; } } void ApperdThread::updatesChanged() { // update the last time the cache was refreshed QDateTime lastCacheRefresh; lastCacheRefresh = getTimeSinceRefreshCache(); if (lastCacheRefresh != m_lastRefreshCache) { m_lastRefreshCache = lastCacheRefresh; } bool ignoreBattery = m_configs[CFG_INSTALL_UP_BATTERY].value(); bool ignoreMobile = m_configs[CFG_INSTALL_UP_MOBILE].value(); // Make sure the user sees the updates m_updater->checkForUpdates(isSystemReady(ignoreBattery, ignoreMobile)); m_distroUpgrade->checkDistroUpgrades(); } void ApperdThread::appShouldConserveResourcesChanged() { bool ignoreBattery = m_configs[CFG_INSTALL_UP_BATTERY].value(); bool ignoreMobile = m_configs[CFG_INSTALL_UP_MOBILE].value(); if (isSystemReady(ignoreBattery, ignoreMobile)) { m_updater->setSystemReady(); } } QDateTime ApperdThread::getTimeSinceRefreshCache() const { uint value = Daemon::global()->getTimeSinceAction(Transaction::RoleRefreshCache); // When the refresh cache value was not yet defined UINT_MAX is returned if (value == UINT_MAX) { return QDateTime(); } else { // Calculate the last time the cache was refreshed by // subtracting the seconds from the current time return QDateTime::currentDateTime().addSecs(value * -1); } } bool ApperdThread::nameHasOwner(const QString &name, const QDBusConnection &connection) { QDBusMessage message; message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.DBus"), QLatin1String("/"), QLatin1String("org.freedesktop.DBus"), QLatin1String("NameHasOwner")); message << qVariantFromValue(name); QDBusReply reply = connection.call(message); return reply.value(); } bool ApperdThread::isSystemReady(bool ignoreBattery, bool ignoreMobile) const { // First check if we should conserve resources // check how applications should behave (e.g. on battery power) // if (!ignoreBattery && Solid::PowerManagement::appShouldConserveResources()) { qCDebug(APPER_DAEMON) << "System is not ready, application should conserve resources"; // This was fixed for KDElibs 4.8.5 return false; // } // TODO it would be nice is Solid provided this // so we wouldn't be waking up PackageKit for this Solid task. Daemon::Network network = Daemon::global()->networkState(); // test whether network is connected if (network == Daemon::NetworkOffline || network == Daemon::NetworkUnknown) { qCDebug(APPER_DAEMON) << "System is not ready, network state" << network; return false; } // check how applications should behave (e.g. on battery power) if (!ignoreMobile && network == Daemon::NetworkMobile) { qCDebug(APPER_DAEMON) << "System is not ready, network state" << network; return false; } return true; } diff --git a/apperd/TransactionJob.cpp b/apperd/TransactionJob.cpp index bfd429a..2b18e50 100644 --- a/apperd/TransactionJob.cpp +++ b/apperd/TransactionJob.cpp @@ -1,188 +1,190 @@ /*************************************************************************** * Copyright (C) 2008-2011 by Daniel Nicoletti * * dantti12@gmail.com * * * * 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. * * * * You should have received a copy of the GNU General Public License * * along with this program; see the file COPYING. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "TransactionJob.h" #include #include #include //#include #include #include Q_DECLARE_LOGGING_CATEGORY(APPER_DAEMON) TransactionJob::TransactionJob(Transaction *transaction, QObject *parent) : KJob(parent), m_transaction(transaction), m_status(transaction->status()), m_role(transaction->role()), m_flags(transaction->transactionFlags()), m_percentage(0), m_speed(0), m_downloadSizeRemainingTotal(0), m_finished(false) { setCapabilities(Killable); connect(transaction, &Transaction::roleChanged, this, &TransactionJob::updateJob); connect(transaction, &Transaction::statusChanged, this, &TransactionJob::updateJob); connect(transaction, &Transaction::downloadSizeRemainingChanged, this, &TransactionJob::updateJob); connect(transaction, &Transaction::transactionFlagsChanged, this, &TransactionJob::updateJob); connect(transaction, &Transaction::percentageChanged, this, &TransactionJob::updateJob); connect(transaction, &Transaction::speedChanged, this, &TransactionJob::updateJob); connect(transaction, &Transaction::finished, this, &TransactionJob::finished); connect(transaction, &Transaction::package, this, &TransactionJob::package); connect(transaction, &Transaction::repoDetail, this, &TransactionJob::repoDetail); } TransactionJob::~TransactionJob() { } void TransactionJob::finished(PackageKit::Transaction::Exit exit) { if (m_finished) { return; } // emit the description so the Speed: xxx KiB/s // don't get confused to a destination URL emit description(this, PkStrings::action(m_role, m_flags)); if (exit == Transaction::ExitCancelled || exit == Transaction::ExitFailed) { setError(KilledJobError); } m_finished = true; emitResult(); } void TransactionJob::package(Transaction::Info info, const QString &packageID, const QString &summary) { Q_UNUSED(summary) if (!packageID.isEmpty()) { bool changed = false; if (info == Transaction::InfoFinished) { changed = m_packages.removeOne(Transaction::packageName(packageID)); } else if (!m_packages.contains(Transaction::packageName(packageID))) { m_packages << Transaction::packageName(packageID); changed = true; } if (changed) { m_details = m_packages.join(QLatin1String(", ")); emitDescription(); } } } void TransactionJob::repoDetail(const QString &repoId, const QString &repoDescription) { Q_UNUSED(repoId) QString first = PkStrings::status(m_status); emit description(this, PkStrings::action(m_role, m_flags), qMakePair(first, repoDescription)); } void TransactionJob::emitDescription() { QString details = m_details; if (details.isEmpty()) { details = QLatin1String("..."); } QString first = PkStrings::status(m_status); emit description(this, PkStrings::action(m_role, m_flags), qMakePair(first, details)); } void TransactionJob::updateJob() { Transaction::Role role = m_transaction->role(); Transaction::TransactionFlags flags = m_transaction->transactionFlags(); if (m_role != role || m_flags != flags) { m_role = role; m_flags = flags; emitDescription(); } // Status & Speed Transaction::Status status = m_transaction->status(); if (m_status != status) { m_status = status; emitDescription(); } uint percentage = m_transaction->percentage(); if (percentage <= 100) { emitPercent(percentage, 100); } else if (m_percentage != 0) { percentage = 0; emitPercent(0, 0); } m_percentage = percentage; uint speed = m_transaction->speed(); if (m_speed != speed) { m_speed = speed; emitSpeed(m_speed); } if (m_downloadSizeRemainingTotal == 0) { m_downloadSizeRemainingTotal = m_transaction->downloadSizeRemaining(); } if (m_downloadSizeRemainingTotal) { qulonglong processed; processed = m_downloadSizeRemainingTotal - m_transaction->downloadSizeRemaining(); emitPercent(processed, m_downloadSizeRemainingTotal); } } void TransactionJob::start() { m_role = Transaction::RoleUnknown; m_speed = 0; m_downloadSizeRemainingTotal = 0; m_details = Transaction::packageName(m_transaction->lastPackage()); updateJob(); } bool TransactionJob::isFinished() const { return m_finished; } Transaction *TransactionJob::transaction() const { return m_transaction; } bool TransactionJob::doKill() { // emit the description so the Speed: xxx KiB/s // don't get confused to a destination URL emit description(this, PkStrings::action(m_role, m_flags)); - m_transaction->cancel(); + QDBusPendingReply<> reply = m_transaction->cancel(); + reply.waitForFinished(); + qCDebug(APPER_DAEMON) << "Transaction cancel operation result" << m_transaction->tid().path() << reply.error(); emit canceled(); - return m_transaction->role() == Transaction::RoleCancel; + return !reply.isError() && m_transaction->role() == Transaction::RoleCancel; } #include "TransactionJob.moc"