File: kupdaemon.cpp

package info (click to toggle)
kup-backup 0.10.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,576 kB
  • sloc: cpp: 8,422; xml: 311; makefile: 6; sh: 3
file content (339 lines) | stat: -rw-r--r-- 12,074 bytes parent folder | download
1
2
3
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
// SPDX-FileCopyrightText: 2020 Simon Persson <simon.persson@mykolab.com>
//
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL

#include "kupdaemon.h"
#include "backupplan.h"
#include "edexecutor.h"
#include "fsexecutor.h"
#include "kupsettings.h"

#include <QApplication>
#include <QDBusConnection>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMessageBox>
#include <QPushButton>
#include <QSessionManager>
#include <QTimer>

#include <KIdleTime>
#include <KLocalizedString>
#include <KUiServerJobTracker>

KupDaemon::KupDaemon()
    : mConfig(KSharedConfig::openConfig(QStringLiteral("kuprc")))
    , mSettings(new KupSettings(mConfig, this))
    , mUsageAccTimer(new QTimer(this))
    , mStatusUpdateTimer(new QTimer(this))
    , mWaitingToReloadConfig(false)
    , mJobTracker(new KUiServerJobTracker(this))
    , mLocalServer(new QLocalServer(this))
{
}

KupDaemon::~KupDaemon()
{
    while (!mExecutors.isEmpty()) {
        delete mExecutors.takeFirst();
    }
    KIdleTime::instance()->removeAllIdleTimeouts();
}

bool KupDaemon::shouldStart()
{
    return mSettings->mBackupsEnabled;
}

void KupDaemon::setupGuiStuff()
{
    // timer to update logged time and also trigger warning if too long
    // time has now passed since last backup
    mUsageAccTimer->setInterval(KUP_USAGE_MONITOR_INTERVAL_S * 1000);
    mUsageAccTimer->start();
    KIdleTime *lIdleTime = KIdleTime::instance();
    lIdleTime->addIdleTimeout(KUP_IDLE_TIMEOUT_S * 1000);
    connect(lIdleTime, qOverload<int, int>(&KIdleTime::timeoutReached), mUsageAccTimer, &QTimer::stop);
    connect(lIdleTime, qOverload<int, int>(&KIdleTime::timeoutReached), lIdleTime, &KIdleTime::catchNextResumeEvent);
    connect(lIdleTime, &KIdleTime::resumingFromIdle, mUsageAccTimer, qOverload<>(&QTimer::start));

    // delay status update to avoid sending a status to plasma applet
    // that will be changed again just a microsecond later anyway
    mStatusUpdateTimer->setInterval(500);
    mStatusUpdateTimer->setSingleShot(true);
    connect(mStatusUpdateTimer, &QTimer::timeout, this, [this] {
        foreach (QLocalSocket *lSocket, mSockets) {
            sendStatus(lSocket);
        }

        if (mWaitingToReloadConfig) {
            // quite likely the config can be reloaded now, give it a try.
            QTimer::singleShot(0, this, SLOT(reloadConfig()));
        }
    });

    QDBusConnection lDBus = QDBusConnection::sessionBus();
    if (lDBus.isConnected()) {
        if (lDBus.registerService(KUP_DBUS_SERVICE_NAME)) {
            lDBus.registerObject(KUP_DBUS_OBJECT_PATH, this, QDBusConnection::ExportAllSlots);
        }
    }
    QString lSocketName = QStringLiteral("kup-daemon-");
    lSocketName += QString::fromLocal8Bit(qgetenv("USER"));

    connect(mLocalServer, &QLocalServer::newConnection, this, [this] {
        QLocalSocket *lSocket = mLocalServer->nextPendingConnection();
        if (lSocket == nullptr) {
            return;
        }
        sendStatus(lSocket);
        mSockets.append(lSocket);
        connect(lSocket, &QLocalSocket::readyRead, this, [this, lSocket] {
            handleRequests(lSocket);
        });
        connect(lSocket, &QLocalSocket::disconnected, this, [this, lSocket] {
            mSockets.removeAll(lSocket);
            lSocket->deleteLater();
        });
    });
    // remove old socket first in case it's still there, otherwise listen() fails.
    QLocalServer::removeServer(lSocketName);
    mLocalServer->listen(lSocketName);

    reloadConfig();
}

void KupDaemon::reloadConfig()
{
    foreach (PlanExecutor *lExecutor, mExecutors) {
        if (lExecutor->busy()) {
            mWaitingToReloadConfig = true;
            return;
        }
    }
    mWaitingToReloadConfig = false;

    mSettings->load();
    while (!mExecutors.isEmpty()) {
        delete mExecutors.takeFirst();
    }
    if (!mSettings->mBackupsEnabled)
        qApp->quit();

    setupExecutors();
    // Juuuust in case all those executors for some reason never
    // triggered an updated status... Doesn't hurt anyway.
    mStatusUpdateTimer->start();
}

// This method is exposed over DBus so that filedigger can call it
void KupDaemon::runIntegrityCheck(const QString &pPath)
{
    foreach (PlanExecutor *lExecutor, mExecutors) {
        // if caller passes in an empty path, startsWith will return true and we will try to check
        // all backup plans.
        if (lExecutor->mDestinationPath.startsWith(pPath)) {
            lExecutor->startIntegrityCheck();
        }
    }
}

// This method is exposed over DBus so that user scripts can call it
void KupDaemon::saveNewBackup(int pPlanNumber)
{
    if (pPlanNumber > 0 && pPlanNumber <= mExecutors.count()) {
        mExecutors[pPlanNumber - 1]->startBackupSaveJob();
    }
}

void KupDaemon::registerJob(KJob *pJob)
{
    mJobTracker->registerJob(pJob);
}

void KupDaemon::unregisterJob(KJob *pJob)
{
    mJobTracker->unregisterJob(pJob);
}

void KupDaemon::slotShutdownRequest(QSessionManager &pManager)
{
    // this will make session management not try (and fail because of KDBusService starting only
    // one instance) to start this daemon. We have autostart for the purpose of launching this
    // daemon instead.
    pManager.setRestartHint(QSessionManager::RestartNever);

    foreach (PlanExecutor *lExecutor, mExecutors) {
        if (lExecutor->busy() && pManager.allowsErrorInteraction()) {
            QMessageBox lMessageBox;
            QPushButton *lContinueButton = lMessageBox.addButton(i18n("Continue"), QMessageBox::RejectRole);
            lMessageBox.addButton(i18n("Stop"), QMessageBox::AcceptRole);
            lMessageBox.setText(i18nc("%1 is a text explaining the current activity", "Currently busy: %1", lExecutor->currentActivityTitle()));
            lMessageBox.setInformativeText(i18n("Do you really want to stop?"));
            lMessageBox.setIcon(QMessageBox::Warning);
            lMessageBox.setWindowIcon(QIcon::fromTheme(QStringLiteral("kup")));
            lMessageBox.setWindowTitle(i18n("User Backups"));
            lMessageBox.exec();
            if (lMessageBox.clickedButton() == lContinueButton) {
                pManager.cancel();
            }
            return; // only ask for one active executor.
        }
    }
}

void KupDaemon::setupExecutors()
{
    for (int i = 0; i < mSettings->mNumberOfPlans; ++i) {
        PlanExecutor *lExecutor;
        auto *lPlan = new BackupPlan(i + 1, mConfig, this);
        if (lPlan->mPathsIncluded.isEmpty()) {
            delete lPlan;
            continue;
        }
        if (lPlan->mDestinationType == 0) {
            lExecutor = new FSExecutor(lPlan, this);
        } else if (lPlan->mDestinationType == 1) {
            lExecutor = new EDExecutor(lPlan, this);
        } else {
            delete lPlan;
            continue;
        }
        connect(lExecutor, &PlanExecutor::stateChanged, this, [this] {
            mStatusUpdateTimer->start();
        });
        connect(lExecutor, &PlanExecutor::backupStatusChanged, this, [this] {
            mStatusUpdateTimer->start();
        });
        connect(mUsageAccTimer, &QTimer::timeout, lExecutor, &PlanExecutor::updateAccumulatedUsageTime);
        lExecutor->checkStatus();
        mExecutors.append(lExecutor);
    }
}

void KupDaemon::handleRequests(QLocalSocket *pSocket)
{
    if (pSocket->bytesAvailable() <= 0) {
        return;
    }
    QJsonDocument lDoc = QJsonDocument::fromJson(pSocket->readAll());
    if (!lDoc.isObject()) {
        return;
    }
    QJsonObject lCommand = lDoc.object();
    QString lOperation = lCommand["operation name"].toString();
    if (lOperation == QStringLiteral("get status")) {
        sendStatus(pSocket);
        return;
    }
    if (lOperation == QStringLiteral("reload")) {
        reloadConfig();
        return;
    }

    int lPlanNumber = lCommand["plan number"].toInt(-1);
    if (lPlanNumber < 0 || lPlanNumber >= mExecutors.count()) {
        return;
    }
    if (lOperation == QStringLiteral("save backup")) {
        mExecutors.at(lPlanNumber)->startBackupSaveJob();
    }
    if (lOperation == QStringLiteral("remove backups")) {
        mExecutors.at(lPlanNumber)->showBackupPurger();
    }
    if (lOperation == QStringLiteral("show log file")) {
        mExecutors.at(lPlanNumber)->showLog();
    }
    if (lOperation == QStringLiteral("show backup files")) {
        mExecutors.at(lPlanNumber)->showBackupFiles();
    }
}

void KupDaemon::sendStatus(QLocalSocket *pSocket)
{
    bool lTrayIconActive = false;
    bool lAnyPlanBusy = false;
    // If all backup plans have status == NO_STATUS then tooltip title will be empty
    QString lToolTipTitle;
    QString lToolTipSubTitle = i18nc("status in tooltip", "Backup destination not available");
    QString lToolTipIconName = QStringLiteral("kup");

    if (mExecutors.isEmpty()) {
        lToolTipTitle = i18n("No backup plans configured");
        lToolTipSubTitle.clear();
    }

    foreach (PlanExecutor *lExec, mExecutors) {
        if (lExec->destinationAvailable()) {
            lToolTipSubTitle = i18nc("status in tooltip", "Backup destination available");
            if (lExec->scheduleType() == BackupPlan::MANUAL) {
                lTrayIconActive = true;
            }
        }
    }

    foreach (PlanExecutor *lExec, mExecutors) {
        if (lExec->mPlan->backupStatus() == BackupPlan::GOOD) {
            lToolTipIconName = BackupPlan::iconName(BackupPlan::GOOD);
            lToolTipTitle = i18nc("status in tooltip", "Backup status OK");
        }
    }

    foreach (PlanExecutor *lExec, mExecutors) {
        if (lExec->mPlan->backupStatus() == BackupPlan::MEDIUM) {
            lToolTipIconName = BackupPlan::iconName(BackupPlan::MEDIUM);
            lToolTipTitle = i18nc("status in tooltip", "New backup suggested");
        }
    }

    foreach (PlanExecutor *lExec, mExecutors) {
        if (lExec->mPlan->backupStatus() == BackupPlan::BAD) {
            lToolTipIconName = BackupPlan::iconName(BackupPlan::BAD);
            lToolTipTitle = i18nc("status in tooltip", "New backup needed");
            lTrayIconActive = true;
        }
    }

    foreach (PlanExecutor *lExecutor, mExecutors) {
        if (lExecutor->busy()) {
            lToolTipIconName = QStringLiteral("kup");
            lToolTipTitle = lExecutor->currentActivityTitle();
            lToolTipSubTitle = lExecutor->mPlan->mDescription;
            lAnyPlanBusy = true;
        }
    }

    if (lToolTipTitle.isEmpty() && !lToolTipSubTitle.isEmpty()) {
        lToolTipTitle = lToolTipSubTitle;
        lToolTipSubTitle.clear();
    }

    QJsonObject lStatus;
    lStatus["event"] = QStringLiteral("status update");
    lStatus["tray icon active"] = lTrayIconActive;
    lStatus["tooltip icon name"] = lToolTipIconName;
    lStatus["tooltip title"] = lToolTipTitle;
    lStatus["tooltip subtitle"] = lToolTipSubTitle;
    lStatus["any plan busy"] = lAnyPlanBusy;
    lStatus["no plan reason"] = mExecutors.isEmpty() ? i18n("No backup plans configured") : QString();
    QJsonArray lPlans;
    foreach (PlanExecutor *lExecutor, mExecutors) {
        QJsonObject lPlan;
        lPlan[QStringLiteral("description")] = lExecutor->mPlan->mDescription;
        lPlan[QStringLiteral("destination available")] = lExecutor->destinationAvailable();
        lPlan[QStringLiteral("status heading")] = lExecutor->currentActivityTitle();
        lPlan[QStringLiteral("status details")] = lExecutor->mPlan->statusText();
        lPlan[QStringLiteral("icon name")] = BackupPlan::iconName(lExecutor->mPlan->backupStatus());
        lPlan[QStringLiteral("log file exists")] = QFileInfo::exists(lExecutor->mLogFilePath);
        lPlan[QStringLiteral("busy")] = lExecutor->busy();
        lPlan[QStringLiteral("bup type")] = lExecutor->mPlan->mBackupType == BackupPlan::BupType;
        lPlans.append(lPlan);
    }
    lStatus["plans"] = lPlans;
    QJsonDocument lDoc(lStatus);
    pSocket->write(lDoc.toJson());
}