File indexing completed on 2024-04-28 09:41:48
0001 // SPDX-FileCopyrightText: 2020 Simon Persson <simon.persson@mykolab.com> 0002 // 0003 // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 0005 #include "kupkcm.h" 0006 #include "backupplan.h" 0007 #include "backupplanwidget.h" 0008 #include "kupdaemon.h" 0009 #include "kupsettings.h" 0010 #include "planstatuswidget.h" 0011 0012 #include <QCheckBox> 0013 #include <QDBusInterface> 0014 #include <QLabel> 0015 #include <QPushButton> 0016 #include <QScrollArea> 0017 #include <QStackedLayout> 0018 0019 #include <KAboutData> 0020 #include <KConfigDialogManager> 0021 #include <KLineEdit> 0022 #include <KLocalizedString> 0023 #include <KMessageBox> 0024 #include <KPluginFactory> 0025 #include <KProcess> 0026 0027 #if QT_VERSION_MAJOR == 5 0028 #include <Kdelibs4ConfigMigrator> 0029 #endif 0030 0031 K_PLUGIN_CLASS_WITH_JSON(KupKcm, "kcm_kup.json") 0032 0033 #if QT_VERSION_MAJOR == 5 0034 KupKcm::KupKcm(QWidget *pParent, const QVariantList &pArgs) 0035 : KCModule(pParent, pArgs) 0036 #else 0037 KupKcm::KupKcm(QObject *pParent, const KPluginMetaData &md, const QVariantList &pArgs) 0038 : KCModule(pParent, md) 0039 #endif 0040 , mSourcePageToShow(0) 0041 { 0042 #if QT_VERSION_MAJOR == 5 0043 KAboutData lAbout(QStringLiteral("kcm_kup"), i18n("Kup Configuration Module"), 0044 QStringLiteral("0.9.1"), 0045 i18n("Configuration of backup plans for the Kup backup system"), 0046 KAboutLicense::GPL, i18n("Copyright (C) 2011-2020 Simon Persson")); 0047 lAbout.addAuthor(i18n("Simon Persson"), i18n("Maintainer"), "simon.persson@mykolab.com"); 0048 lAbout.setTranslator(xi18nc("NAME OF TRANSLATORS", "Your names"), 0049 xi18nc("EMAIL OF TRANSLATORS", "Your emails")); 0050 setAboutData(new KAboutData(lAbout)); 0051 #endif 0052 setObjectName(QStringLiteral("kcm_kup")); //needed for the kconfigdialogmanager magic 0053 setButtons((Apply | buttons()) & ~Default); 0054 0055 KProcess lBupProcess; 0056 lBupProcess << QStringLiteral("bup") << QStringLiteral("version"); 0057 lBupProcess.setOutputChannelMode(KProcess::MergedChannels); 0058 int lExitCode = lBupProcess.execute(); 0059 if(lExitCode >= 0) { 0060 mBupVersion = QString::fromUtf8(lBupProcess.readAllStandardOutput()); 0061 KProcess lPar2Process; 0062 lPar2Process << QStringLiteral("bup") << QStringLiteral("fsck") << QStringLiteral("--par2-ok"); 0063 mPar2Available = lPar2Process.execute() == 0; 0064 } else { 0065 mPar2Available = false; 0066 } 0067 0068 KProcess lRsyncProcess; 0069 lRsyncProcess << QStringLiteral("rsync") << QStringLiteral("--version"); 0070 lRsyncProcess.setOutputChannelMode(KProcess::MergedChannels); 0071 lExitCode = lRsyncProcess.execute(); 0072 if(lExitCode >= 0) { 0073 QString lOutput = QString::fromLocal8Bit(lRsyncProcess.readLine()); 0074 mRsyncVersion = lOutput.split(QLatin1Char(' '), Qt::SkipEmptyParts).at(2); 0075 } 0076 0077 if(mBupVersion.isEmpty() && mRsyncVersion.isEmpty()) { 0078 auto lSorryIcon = new QLabel; 0079 lSorryIcon->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-error")).pixmap(64, 64)); 0080 QString lInstallMessage = i18n("<h2>Backup programs are missing</h2>" 0081 "<p>Before you can activate any backup plan you need to " 0082 "install either of</p>" 0083 "<ul><li>bup, for versioned backups</li>" 0084 "<li>rsync, for synchronized backups</li></ul>"); 0085 auto lSorryText = new QLabel(lInstallMessage); 0086 lSorryText->setWordWrap(true); 0087 auto lHLayout = new QHBoxLayout; 0088 lHLayout->addWidget(lSorryIcon); 0089 lHLayout->addWidget(lSorryText, 1); 0090 #if QT_VERSION_MAJOR == 5 0091 setLayout(lHLayout); 0092 #else 0093 widget()->setLayout(lHLayout); 0094 #endif 0095 } else { 0096 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0097 Kdelibs4ConfigMigrator lMigrator(QStringLiteral("kup")); 0098 lMigrator.setConfigFiles(QStringList() << QStringLiteral("kuprc")); 0099 lMigrator.migrate(); 0100 #endif 0101 0102 mConfig = KSharedConfig::openConfig(QStringLiteral("kuprc")); 0103 mSettings = new KupSettings(mConfig, this); 0104 for(int i = 0; i < mSettings->mNumberOfPlans; ++i) { 0105 mPlans.append(new BackupPlan(i+1, mConfig, this)); 0106 mConfigManagers.append(nullptr); 0107 mPlanWidgets.append(nullptr); 0108 mStatusWidgets.append(nullptr); 0109 } 0110 createSettingsFrontPage(); 0111 addConfig(mSettings, mFrontPage); 0112 mStackedLayout = new QStackedLayout; 0113 mStackedLayout->addWidget(mFrontPage); 0114 #if QT_VERSION_MAJOR == 5 0115 setLayout(mStackedLayout); 0116 #else 0117 widget()->setLayout(mStackedLayout); 0118 #endif 0119 QListIterator<QVariant> lIter(pArgs); 0120 while(lIter.hasNext()) { 0121 QVariant lVariant = lIter.next(); 0122 if(lVariant.type() == QVariant::String) { 0123 QString lArgument = lVariant.toString(); 0124 if(lArgument == QStringLiteral("show_sources") && lIter.hasNext()) { 0125 mSourcePageToShow = lIter.next().toString().toInt(); 0126 } 0127 } 0128 } 0129 } 0130 } 0131 0132 #if QT_VERSION_MAJOR == 5 0133 QSize KupKcm::sizeHint() const { 0134 return {800, 600}; 0135 } 0136 #endif 0137 0138 void KupKcm::load() { 0139 if(mBupVersion.isEmpty() && mRsyncVersion.isEmpty()) { 0140 return; 0141 } 0142 // status will be set correctly after construction, set to checked here to 0143 // match the enabled status of other widgets 0144 mEnableCheckBox->setChecked(true); 0145 for(int i = 0; i < mSettings->mNumberOfPlans; ++i) { 0146 if(!mConfigManagers.at(i)) 0147 createPlanWidgets(i); 0148 mConfigManagers.at(i)->updateWidgets(); 0149 } 0150 for(int i = mSettings->mNumberOfPlans; i < mPlans.count();) { 0151 completelyRemovePlan(i); 0152 } 0153 KCModule::load(); 0154 // this call is needed because it could have been set true before, now load() is called 0155 // because user pressed reset button. need to manually reset the "changed" state to false 0156 // in this case. 0157 unmanagedWidgetChangeState(false); 0158 if(mSourcePageToShow > 0) { 0159 mStackedLayout->setCurrentIndex(mSourcePageToShow); 0160 mPlanWidgets[mSourcePageToShow - 1]->showSourcePage(); 0161 mSourcePageToShow = 0; //only trigger on first load after startup. 0162 } 0163 } 0164 0165 void KupKcm::save() { 0166 int lPlansRemoved = 0; 0167 for(int i=0; i < mPlans.count(); ++i) { 0168 auto *lPlan = mPlans.at(i); 0169 auto *lManager = mConfigManagers.at(i); 0170 if(lManager == nullptr) { 0171 lPlan->removePlanFromConfig(); 0172 delete mPlans.takeAt(i); 0173 mConfigManagers.removeAt(i); 0174 mStatusWidgets.removeAt(i); 0175 delete mPlanWidgets.takeAt(i); 0176 ++lPlansRemoved; 0177 --i; 0178 continue; 0179 } 0180 if(lPlansRemoved != 0) { 0181 lPlan->removePlanFromConfig(); 0182 lPlan->setPlanNumber(i + 1); 0183 // config manager does not detect a changed group name of the config items. 0184 // To work around, read default settings - config manager will then notice 0185 // changed values and save current widget status into the config using the 0186 // new group name. If all settings for the plan already was default then 0187 // nothing was saved anyway, either under old or new group name. 0188 lPlan->setDefaults(); 0189 lPlan->save(); 0190 } 0191 mPlanWidgets.at(i)->saveExtraData(); 0192 lManager->updateSettings(); 0193 mStatusWidgets.at(i)->updateIcon(); 0194 if(lPlan->mDestinationType == 1 && lPlan->mExternalUUID.isEmpty()) { 0195 #if QT_VERSION_MAJOR == 5 0196 QWidget *wid = this; 0197 #else 0198 QWidget *wid = widget(); 0199 #endif 0200 KMessageBox::information(wid, xi18nc("@info %1 is the name of the backup plan", 0201 "%1 does not have a destination!<nl/>" 0202 "No backups will be saved by this plan.", 0203 lPlan->mDescription), 0204 xi18nc("@title:window", "Warning"), 0205 QString(), KMessageBox::Dangerous); 0206 } 0207 } 0208 mSettings->mNumberOfPlans = mPlans.count(); 0209 mSettings->save(); 0210 0211 KCModule::save(); 0212 0213 QDBusInterface lInterface(KUP_DBUS_SERVICE_NAME, KUP_DBUS_OBJECT_PATH); 0214 if(lInterface.isValid()) { 0215 lInterface.call(QStringLiteral("reloadConfig")); 0216 } else { 0217 KProcess::startDetached(QStringLiteral("kup-daemon")); 0218 } 0219 } 0220 0221 void KupKcm::updateChangedStatus() { 0222 bool lHasUnmanagedChanged = false; 0223 foreach(KConfigDialogManager *lConfigManager, mConfigManagers) { 0224 if(!lConfigManager || lConfigManager->hasChanged()) { 0225 lHasUnmanagedChanged = true; 0226 break; 0227 } 0228 } 0229 if(mPlanWidgets.count() != mSettings->mNumberOfPlans) 0230 lHasUnmanagedChanged = true; 0231 unmanagedWidgetChangeState(lHasUnmanagedChanged); 0232 } 0233 0234 void KupKcm::showFrontPage() { 0235 mStackedLayout->setCurrentIndex(0); 0236 } 0237 0238 void KupKcm::createSettingsFrontPage() { 0239 mFrontPage = new QWidget; 0240 auto lHLayout = new QHBoxLayout; 0241 auto lVLayout = new QVBoxLayout; 0242 auto lScrollArea = new QScrollArea; 0243 auto lCentralWidget = new QWidget(lScrollArea); 0244 mVerticalLayout = new QVBoxLayout; 0245 lScrollArea->setWidget(lCentralWidget); 0246 lScrollArea->setWidgetResizable(true); 0247 lScrollArea->setFrameStyle(QFrame::NoFrame); 0248 0249 auto lAddPlanButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), 0250 xi18nc("@action:button", "Add New Plan")); 0251 connect(lAddPlanButton, &QPushButton::clicked, this, [this]{ 0252 mPlans.append(new BackupPlan(mPlans.count() + 1, mConfig, this)); 0253 if(mBupVersion.isEmpty()) mPlans.last()->mBackupType = 1; 0254 mConfigManagers.append(nullptr); 0255 mPlanWidgets.append(nullptr); 0256 mStatusWidgets.append(nullptr); 0257 createPlanWidgets(mPlans.count() - 1); 0258 updateChangedStatus(); 0259 emit mStatusWidgets.at(mPlans.count() - 1)->configureMe(); 0260 }); 0261 0262 mEnableCheckBox = new QCheckBox(xi18nc("@option:check", "Backups Enabled")); 0263 mEnableCheckBox->setObjectName(QStringLiteral("kcfg_Backups enabled")); 0264 connect(mEnableCheckBox, &QCheckBox::toggled, lAddPlanButton, &QPushButton::setEnabled); 0265 0266 lHLayout->addWidget(mEnableCheckBox); 0267 lHLayout->addStretch(); 0268 lHLayout->addWidget(lAddPlanButton); 0269 lVLayout->addLayout(lHLayout); 0270 lVLayout->addWidget(lScrollArea); 0271 mFrontPage->setLayout(lVLayout); 0272 0273 auto lFilediggerButton = new QPushButton(xi18nc("@action:button", "Open and restore from existing backups")); 0274 connect(lFilediggerButton, &QPushButton::clicked, []{KProcess::startDetached(QStringLiteral("kup-filedigger"));}); 0275 mVerticalLayout->addWidget(lFilediggerButton); 0276 mVerticalLayout->addStretch(1); 0277 lCentralWidget->setLayout(mVerticalLayout); 0278 } 0279 0280 void KupKcm::createPlanWidgets(int pIndex) { 0281 auto lPlanWidget = new BackupPlanWidget(mPlans.at(pIndex), mBupVersion, 0282 mRsyncVersion, mPar2Available); 0283 connect(lPlanWidget, SIGNAL(requestOverviewReturn()), this, SLOT(showFrontPage())); 0284 auto lConfigManager = new KConfigDialogManager(lPlanWidget, mPlans.at(pIndex)); 0285 lConfigManager->setObjectName(objectName()); 0286 connect(lConfigManager, SIGNAL(widgetModified()), this, SLOT(updateChangedStatus())); 0287 auto lStatusWidget = new PlanStatusWidget(mPlans.at(pIndex)); 0288 connect(lStatusWidget, &PlanStatusWidget::removeMe, this, [this]{ 0289 int lIndex = mStatusWidgets.indexOf(qobject_cast<PlanStatusWidget*>(sender())); 0290 if(lIndex < mSettings->mNumberOfPlans) 0291 partiallyRemovePlan(lIndex); 0292 else 0293 completelyRemovePlan(lIndex); 0294 updateChangedStatus(); 0295 }); 0296 connect(lStatusWidget, &PlanStatusWidget::configureMe, this, [this]{ 0297 int lIndex = mStatusWidgets.indexOf(qobject_cast<PlanStatusWidget*>(sender())); 0298 mStackedLayout->setCurrentIndex(lIndex + 1); 0299 }); 0300 connect(lStatusWidget, &PlanStatusWidget::duplicateMe, this, [this]{ 0301 int lIndex = mStatusWidgets.indexOf(qobject_cast<PlanStatusWidget*>(sender())); 0302 auto lNewPlan = new BackupPlan(mPlans.count() + 1, mConfig, this); 0303 lNewPlan->copyFrom(*mPlans.at(lIndex)); 0304 mPlans.append(lNewPlan); 0305 mConfigManagers.append(nullptr); 0306 mPlanWidgets.append(nullptr); 0307 mStatusWidgets.append(nullptr); 0308 createPlanWidgets(mPlans.count() - 1); 0309 // crazy trick to make the config system realize that stuff has changed 0310 // and will need to be saved. 0311 lNewPlan->setDefaults(); 0312 updateChangedStatus(); 0313 }); 0314 connect(mEnableCheckBox, &QCheckBox::toggled, 0315 lStatusWidget, &PlanStatusWidget::setEnabled); 0316 connect(lPlanWidget->mDescriptionEdit, &KLineEdit::textChanged, 0317 lStatusWidget->mDescriptionLabel, &QLabel::setText); 0318 0319 mConfigManagers[pIndex] = lConfigManager; 0320 mPlanWidgets[pIndex] = lPlanWidget; 0321 mStackedLayout->insertWidget(pIndex + 1, lPlanWidget); 0322 mStatusWidgets[pIndex] = lStatusWidget; 0323 // always insert at end, before the file digger button and strech space at the bottom. 0324 mVerticalLayout->insertWidget(mVerticalLayout->count() - 2, lStatusWidget); 0325 } 0326 0327 void KupKcm::completelyRemovePlan(int pIndex) { 0328 delete mConfigManagers.takeAt(pIndex); 0329 delete mStatusWidgets.takeAt(pIndex); 0330 delete mPlanWidgets.takeAt(pIndex); 0331 delete mPlans.takeAt(pIndex); 0332 } 0333 0334 void KupKcm::partiallyRemovePlan(int pIndex) { 0335 mConfigManagers.at(pIndex)->deleteLater(); 0336 mConfigManagers[pIndex] = nullptr; 0337 mStatusWidgets.at(pIndex)->deleteLater(); 0338 mStatusWidgets[pIndex] = nullptr; 0339 } 0340 0341 #include "kupkcm.moc"