kupkcm.cpp 12.4 KB
Newer Older
1 2
/***************************************************************************
 *   Copyright Simon Persson                                               *
3
 *   simonpersson1@gmail.com                                               *
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 *                                                                         *
 *   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; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "backupplan.h"
#include "backupplanwidget.h"
#include "kupdaemon.h"
#include "kupkcm.h"
#include "kupsettings.h"
#include "planstatuswidget.h"

#include <QCheckBox>
29
#include <QDBusInterface>
30
#include <QLabel>
31
#include <QMessageBox>
32
#include <QPushButton>
33 34 35 36 37
#include <QScrollArea>
#include <QStackedLayout>

#include <KAboutData>
#include <KConfigDialogManager>
38
#include <Kdelibs4ConfigMigrator>
39
#include <KLineEdit>
Simon Persson's avatar
Simon Persson committed
40
#include <KLocalizedString>
41 42 43 44 45
#include <KPluginFactory>
#include <KProcess>

K_PLUGIN_FACTORY(KupKcmFactory, registerPlugin<KupKcm>();)

46 47
KupKcm::KupKcm(QWidget *pParent, const QVariantList &pArgs)
   : KCModule(pParent, pArgs)
48
{
Simon Persson's avatar
Simon Persson committed
49
	KAboutData lAbout(QStringLiteral("kcm_kup"), i18n("Kup Configuration Module"),
Simon Persson's avatar
Simon Persson committed
50
	                  QStringLiteral("0.7.2"),
51 52 53 54
	                  i18n("Configuration of backup plans for the Kup backup system"),
	                  KAboutLicense::GPL, i18n("Copyright (C) 2011-2015 Simon Persson"),
	                  QString(), QString(), "simonpersson1@gmail.com");
	lAbout.addAuthor(i18n("Simon Persson"), i18n("Maintainer"), "simonpersson1@gmail.com");
Simon Persson's avatar
Simon Persson committed
55 56
	lAbout.setTranslator(xi18nc("NAME OF TRANSLATORS", "Your names"),
	                     xi18nc("EMAIL OF TRANSLATORS", "Your emails"));
57
	setAboutData(new KAboutData(lAbout));
58

59
	setObjectName(QStringLiteral("kcm_kup")); //needed for the kconfigdialogmanager magic
60 61
	setButtons((Apply | buttons()) & ~Default);

62
	KProcess lBupProcess;
63
	lBupProcess << QStringLiteral("bup") << QStringLiteral("version");
Simon Persson's avatar
Simon Persson committed
64
	lBupProcess.setOutputChannelMode(KProcess::MergedChannels);
65 66
	int lExitCode = lBupProcess.execute();
	if(lExitCode >= 0) {
67
		mBupVersion = QString::fromUtf8(lBupProcess.readAllStandardOutput());
68
		KProcess lPar2Process;
69
		lPar2Process << QStringLiteral("bup") << QStringLiteral("fsck") << QStringLiteral("--par2-ok");
70 71 72
		mPar2Available = lPar2Process.execute() == 0;
	} else {
		mPar2Available = false;
73 74
	}

Simon Persson's avatar
Simon Persson committed
75
	KProcess lRsyncProcess;
76
	lRsyncProcess << QStringLiteral("rsync") << QStringLiteral("--version");
Simon Persson's avatar
Simon Persson committed
77 78 79
	lRsyncProcess.setOutputChannelMode(KProcess::MergedChannels);
	lExitCode = lRsyncProcess.execute();
	if(lExitCode >= 0) {
Simon Persson's avatar
Simon Persson committed
80 81
		QString lOutput = QString::fromLocal8Bit(lRsyncProcess.readLine());
		mRsyncVersion = lOutput.split(QLatin1Char(' '), QString::SkipEmptyParts).at(2);
Simon Persson's avatar
Simon Persson committed
82 83 84
	}

	if(mBupVersion.isEmpty() && mRsyncVersion.isEmpty()) {
85
		QLabel *lSorryIcon = new QLabel;
86
		lSorryIcon->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-error")).pixmap(64, 64));
87 88 89 90 91
		QString lInstallMessage = i18n("<h2>Backup programs are missing</h2>"
		                               "<p>Before you can activate any backup plan you need to "
		                               "install either of</p>"
		                               "<ul><li>bup, for versioned backups</li>"
		                               "<li>rsync, for synchronized backups</li></ul>");
Simon Persson's avatar
Simon Persson committed
92 93
		QLabel *lSorryText = new QLabel(lInstallMessage);
		lSorryText->setWordWrap(true);
94 95
		QHBoxLayout *lHLayout = new QHBoxLayout;
		lHLayout->addWidget(lSorryIcon);
Simon Persson's avatar
Simon Persson committed
96
		lHLayout->addWidget(lSorryText, 1);
97 98
		setLayout(lHLayout);
	} else {
99 100 101 102
		Kdelibs4ConfigMigrator lMigrator(QStringLiteral("kup"));
		lMigrator.setConfigFiles(QStringList() << QStringLiteral("kuprc"));
		lMigrator.migrate();

103
		mConfig = KSharedConfig::openConfig(QStringLiteral("kuprc"));
104 105 106
		mSettings = new KupSettings(mConfig, this);
		for(int i = 0; i < mSettings->mNumberOfPlans; ++i) {
			mPlans.append(new BackupPlan(i+1, mConfig, this));
Simon Persson's avatar
Simon Persson committed
107 108 109
			mConfigManagers.append(nullptr);
			mPlanWidgets.append(nullptr);
			mStatusWidgets.append(nullptr);
110 111 112 113 114 115 116
		}
		createSettingsFrontPage();
		addConfig(mSettings, mFrontPage);
		mStackedLayout = new QStackedLayout;
		mStackedLayout->addWidget(mFrontPage);
		setLayout(mStackedLayout);
	}
117 118
}

Simon Persson's avatar
Simon Persson committed
119 120 121 122 123
QSize KupKcm::sizeHint() const {
	int lBaseWidth = fontMetrics().width('M');
	return QSize(lBaseWidth * 65, lBaseWidth * 35);
}

124
void KupKcm::load() {
Simon Persson's avatar
Simon Persson committed
125
	if(mBupVersion.isEmpty() && mRsyncVersion.isEmpty()) {
126 127
		return;
	}
128 129 130 131 132 133 134 135 136 137 138 139
	// status will be set correctly after construction, set to checked here to
	// match the enabled status of other widgets
	mEnableCheckBox->setChecked(true);
	for(int i = 0; i < mSettings->mNumberOfPlans; ++i) {
		if(!mConfigManagers.at(i))
			createPlanWidgets(i);
		mConfigManagers.at(i)->updateWidgets();
	}
	for(int i = mSettings->mNumberOfPlans; i < mPlans.count();) {
		completelyRemovePlan(i);
	}
	KCModule::load();
Simon Persson's avatar
Simon Persson committed
140 141 142
	// this call is needed because it could have been set true before, now load() is called
	// because user pressed reset button. need to manually reset the "changed" state to false
	// in this case.
143
	unmanagedWidgetChangeState(false);
144 145 146 147 148 149 150 151 152
}

void KupKcm::save() {
	KConfigDialogManager *lManager;
	BackupPlan *lPlan;
	int lPlansRemoved = 0;
	for(int i=0; i < mPlans.count(); ++i) {
		lPlan = mPlans.at(i);
		lManager = mConfigManagers.at(i);
Simon Persson's avatar
Simon Persson committed
153
		if(lManager != nullptr) {
154 155 156 157 158 159 160 161 162 163
			if(lPlansRemoved != 0) {
				lPlan->removePlanFromConfig();
				lPlan->setPlanNumber(i + 1);
				// config manager does not detect a changed group name of the config items.
				// To work around, read default settings - config manager will then notice
				// changed values and save current widget status into the config using the
				// new group name. If all settings for the plan already was default then
				// nothing was saved anyway, either under old or new group name.
				lPlan->setDefaults();
			}
164
			mPlanWidgets.at(i)->saveExtraData();
165 166
			lManager->updateSettings();
			mStatusWidgets.at(i)->updateIcon();
167
			if(lPlan->mDestinationType == 1 && lPlan->mExternalUUID.isEmpty()) {
168
				QMessageBox::warning(this, xi18nc("@title:window", "Warning"),
169
				                     xi18nc("@info %1 is the name of the backup plan",
170
				                            "%1 does not have a destination!<nl/>"
Simon Persson's avatar
Simon Persson committed
171 172
				                            "No backups will be saved by this plan.",
				                            lPlan->mDescription));
173
			}
174 175 176 177 178 179 180 181 182 183 184 185
		}
		else {
			lPlan->removePlanFromConfig();
			delete mPlans.takeAt(i);
			mConfigManagers.removeAt(i);
			mStatusWidgets.removeAt(i);
			mPlanWidgets.removeAt(i);
			++lPlansRemoved;
			--i;
		}
	}
	mSettings->mNumberOfPlans = mPlans.count();
Simon Persson's avatar
Simon Persson committed
186
	mSettings->save();
187 188 189

	KCModule::save();

190 191
	QDBusInterface lInterface(KUP_DBUS_SERVICE_NAME, KUP_DBUS_OBJECT_PATH);
	if(lInterface.isValid()) {
192
		lInterface.call(QStringLiteral("reloadConfig"));
193
	} else {
194
		KProcess::startDetached(QStringLiteral("kup-daemon"));
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
	}
}

void KupKcm::updateChangedStatus() {
	bool lHasUnmanagedChanged = false;
	foreach(KConfigDialogManager *lConfigManager, mConfigManagers) {
		if(!lConfigManager || lConfigManager->hasChanged()) {
			lHasUnmanagedChanged = true;
			break;
		}
	}
	if(mPlanWidgets.count() != mSettings->mNumberOfPlans)
		lHasUnmanagedChanged = true;
	unmanagedWidgetChangeState(lHasUnmanagedChanged);
}

void KupKcm::showFrontPage() {
	mStackedLayout->setCurrentIndex(0);
}

void KupKcm::createSettingsFrontPage() {
	mFrontPage = new QWidget;
	QHBoxLayout *lHLayout = new QHBoxLayout;
	QVBoxLayout *lVLayout = new QVBoxLayout;
	QScrollArea *lScrollArea = new QScrollArea;
	QWidget *lCentralWidget = new QWidget(lScrollArea);
	mVerticalLayout = new QVBoxLayout;
	lScrollArea->setWidget(lCentralWidget);
	lScrollArea->setWidgetResizable(true);
	lScrollArea->setFrameStyle(QFrame::NoFrame);

226 227 228 229
	QPushButton *lAddPlanButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")),
	                                             xi18nc("@action:button", "Add New Plan"));
	connect(lAddPlanButton, &QPushButton::clicked, [this]{
		mPlans.append(new BackupPlan(mPlans.count() + 1, mConfig, this));
Simon Persson's avatar
Simon Persson committed
230 231 232
		mConfigManagers.append(nullptr);
		mPlanWidgets.append(nullptr);
		mStatusWidgets.append(nullptr);
233 234 235 236
		createPlanWidgets(mPlans.count() - 1);
		updateChangedStatus();
		emit mStatusWidgets.at(mPlans.count() - 1)->configureMe();
	});
237

238
	mEnableCheckBox = new QCheckBox(xi18nc("@option:check", "Backups Enabled"));
239
	mEnableCheckBox->setObjectName(QStringLiteral("kcfg_Backups enabled"));
240
	connect(mEnableCheckBox, &QCheckBox::toggled, lAddPlanButton, &QPushButton::setEnabled);
241 242 243

	lHLayout->addWidget(mEnableCheckBox);
	lHLayout->addStretch();
244
	lHLayout->addWidget(lAddPlanButton);
245 246 247 248
	lVLayout->addLayout(lHLayout);
	lVLayout->addWidget(lScrollArea);
	mFrontPage->setLayout(lVLayout);

249 250 251
	QPushButton *lFilediggerButton = new QPushButton(xi18nc("@action:button", "Open and restore from existing backups"));
	connect(lFilediggerButton, &QPushButton::clicked, []{KProcess::startDetached(QStringLiteral("kup-filedigger"));});
	mVerticalLayout->addWidget(lFilediggerButton);
252 253 254 255 256
	mVerticalLayout->addStretch(1);
	lCentralWidget->setLayout(mVerticalLayout);
}

void KupKcm::createPlanWidgets(int pIndex) {
Simon Persson's avatar
Simon Persson committed
257 258
	BackupPlanWidget *lPlanWidget = new BackupPlanWidget(mPlans.at(pIndex), mBupVersion,
	                                                     mRsyncVersion, mPar2Available);
259 260 261 262 263
	connect(lPlanWidget, SIGNAL(requestOverviewReturn()), this, SLOT(showFrontPage()));
	KConfigDialogManager *lConfigManager = new KConfigDialogManager(lPlanWidget, mPlans.at(pIndex));
	lConfigManager->setObjectName(objectName());
	connect(lConfigManager, SIGNAL(widgetModified()), this, SLOT(updateChangedStatus()));
	PlanStatusWidget *lStatusWidget = new PlanStatusWidget(mPlans.at(pIndex));
264 265 266 267 268 269 270 271 272 273
	connect(lStatusWidget, &PlanStatusWidget::removeMe, [=]{
		if(pIndex < mSettings->mNumberOfPlans)
			partiallyRemovePlan(pIndex);
		else
			completelyRemovePlan(pIndex);
		updateChangedStatus();
	});
	connect(lStatusWidget, &PlanStatusWidget::configureMe, [=]{
		mStackedLayout->setCurrentIndex(pIndex + 1);
	});
274 275 276 277
	connect(lStatusWidget, &PlanStatusWidget::duplicateMe, [this,pIndex]{
		BackupPlan *lNewPlan = new BackupPlan(mPlans.count() + 1, mConfig, this);
		lNewPlan->copyFrom(*mPlans.at(pIndex));
		mPlans.append(lNewPlan);
Simon Persson's avatar
Simon Persson committed
278 279 280
		mConfigManagers.append(nullptr);
		mPlanWidgets.append(nullptr);
		mStatusWidgets.append(nullptr);
281 282 283 284 285 286
		createPlanWidgets(mPlans.count() - 1);
		// crazy trick to make the config system realize that stuff has changed
		// and will need to be saved.
		lNewPlan->setDefaults();
		updateChangedStatus();
	});
287 288 289 290
	connect(mEnableCheckBox, &QCheckBox::toggled,
	        lStatusWidget, &PlanStatusWidget::setEnabled);
	connect(lPlanWidget->mDescriptionEdit, &KLineEdit::textChanged,
	        lStatusWidget->mDescriptionLabel, &QLabel::setText);
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311

	mConfigManagers[pIndex] = lConfigManager;
	mPlanWidgets[pIndex] = lPlanWidget;
	mStackedLayout->insertWidget(pIndex + 1, lPlanWidget);
	mStatusWidgets[pIndex] = lStatusWidget;
	mVerticalLayout->insertWidget(pIndex, lStatusWidget);
}

void KupKcm::completelyRemovePlan(int pIndex) {
	mVerticalLayout->removeWidget(mStatusWidgets.at(pIndex));
	mStackedLayout->removeWidget(mPlanWidgets.at(pIndex));
	delete mConfigManagers.takeAt(pIndex);
	delete mStatusWidgets.takeAt(pIndex);
	delete mPlanWidgets.takeAt(pIndex);
	delete mPlans.takeAt(pIndex);
}

void KupKcm::partiallyRemovePlan(int pIndex) {
	mVerticalLayout->removeWidget(mStatusWidgets.at(pIndex));
	mStackedLayout->removeWidget(mPlanWidgets.at(pIndex));
	mConfigManagers.at(pIndex)->deleteLater();
Simon Persson's avatar
Simon Persson committed
312
	mConfigManagers[pIndex] = nullptr;
313
	mStatusWidgets.at(pIndex)->deleteLater();
Simon Persson's avatar
Simon Persson committed
314
	mStatusWidgets[pIndex] = nullptr;
315
	mPlanWidgets.at(pIndex)->deleteLater();
Simon Persson's avatar
Simon Persson committed
316
	mPlanWidgets[pIndex] = nullptr;
317 318
}

319
#include "kupkcm.moc"