Commit fe4ee6ea authored by Simon Persson's avatar Simon Persson

Add recording of logfile to kup-daemon

Log is kept in users cache folder, only stores which commands get executed and stderr output from the commands. File is cleared at start of backup saving so it only ever shows the last run of a backup plan. One file for each backup plan.
Modify notification to include a "show log file" button. Add menu item for showing log file.
Closes #1.
parent 8286f5e4
......@@ -29,10 +29,13 @@
#endif
BackupJob::BackupJob(const QStringList &pPathsIncluded, const QStringList &pPathsExcluded,
const QString &pDestinationPath)
const QString &pDestinationPath, const QString &pLogFilePath)
:KJob(), mPathsIncluded(pPathsIncluded), mPathsExcluded(pPathsExcluded),
mDestinationPath(pDestinationPath)
mDestinationPath(pDestinationPath), mLogFilePath(pLogFilePath)
{
mLogFile.setFileName(mLogFilePath);
mLogFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
mLogStream.setDevice(&mLogFile);
}
void BackupJob::makeNice(int pPid) {
......
......@@ -22,21 +22,32 @@
#define BACKUPJOB_H
#include <KJob>
#include <QFile>
#include <QStringList>
#include <QTextStream>
#include "backupplan.h"
class BackupJob : public KJob
{
Q_OBJECT
public:
enum ErrorCodes {
ErrorWithLog = UserDefinedError,
ErrorWithoutLog
};
protected:
BackupJob(const QStringList &pPathsIncluded, const QStringList &pPathsExcluded,
const QString &pDestinationPath);
const QString &pDestinationPath, const QString &pLogFilePath);
static void makeNice(int pPid);
QStringList mPathsIncluded;
QStringList mPathsExcluded;
QString mDestinationPath;
QString mLogFilePath;
QFile mLogFile;
QTextStream mLogStream;
};
#endif // BACKUPJOB_H
......@@ -20,13 +20,14 @@
#include "bupjob.h"
#include <KGlobal>
#include <KLocale>
#include <QDir>
#include <QTimer>
BupJob::BupJob(const QStringList &pPathsIncluded, const QStringList &pPathsExcluded,
const QString &pDestinationPath)
:BackupJob(pPathsIncluded, pPathsExcluded, pDestinationPath)
const QString &pDestinationPath, const QString &pLogFilePath)
:BackupJob(pPathsIncluded, pPathsExcluded, pDestinationPath, pLogFilePath)
{
mIndexProcess.setOutputChannelMode(KProcess::SeparateChannels);
mSaveProcess.setOutputChannelMode(KProcess::SeparateChannels);
......@@ -41,7 +42,7 @@ void BupJob::startIndexing() {
lVersionProcess.setOutputChannelMode(KProcess::SeparateChannels);
lVersionProcess << QLatin1String("bup") << QLatin1String("version");
if(lVersionProcess.execute() < 0) {
setError(1);
setError(ErrorWithoutLog);
setErrorText(i18nc("notification", "The \"bup\" program is needed but could not be found, "
"maybe it is not installed?"));
emitResult();
......@@ -49,15 +50,23 @@ void BupJob::startIndexing() {
}
mBupVersion = QString::fromUtf8(lVersionProcess.readAllStandardOutput());
mLogStream << QLatin1String("Kup is starting bup backup job at ")
<< KGlobal::locale()->formatDateTime(QDateTime::currentDateTime(),
KLocale::LongDate, true)
<< endl;
KProcess lInitProcess;
lInitProcess.setOutputChannelMode(KProcess::SeparateChannels);
lInitProcess << QLatin1String("bup");
lInitProcess << QLatin1String("-d") << mDestinationPath;
lInitProcess << QLatin1String("init");
mLogStream << lInitProcess.program().join(QLatin1String(" ")) << endl;
if(lInitProcess.execute() != 0) {
setError(1);
setErrorText(i18nc("notification", "Backup destination could not be initialised by bup:\n%1",
QString::fromUtf8(lInitProcess.readAllStandardError())));
mLogStream << QString::fromUtf8(lInitProcess.readAllStandardError()) << endl;
mLogStream << QLatin1String("Kup is aborting the bup backup job.") << endl;
setError(ErrorWithLog);
setErrorText(i18nc("notification", "Backup destination could not be initialised. "
"See log file for more details."));
emitResult();
return;
}
......@@ -74,6 +83,7 @@ void BupJob::startIndexing() {
connect(&mIndexProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(slotIndexingDone(int,QProcess::ExitStatus)));
connect(&mIndexProcess, SIGNAL(started()), SLOT(slotIndexingStarted()));
mLogStream << mIndexProcess.program().join(QLatin1String(" ")) << endl;
mIndexProcess.start();
}
......@@ -82,10 +92,12 @@ void BupJob::slotIndexingStarted() {
}
void BupJob::slotIndexingDone(int pExitCode, QProcess::ExitStatus pExitStatus) {
mLogStream << QString::fromUtf8(mIndexProcess.readAllStandardError()) << endl;
if(pExitStatus != QProcess::NormalExit || pExitCode != 0) {
setError(1);
setErrorText(i18nc("notification", "Indexing of file system did not complete successfully:\n%1",
QString::fromUtf8(mIndexProcess.readAllStandardError())));
mLogStream << QLatin1String("Kup is aborting the bup backup job.") << endl;
setError(ErrorWithLog);
setErrorText(i18nc("notification", "Indexing of file system did not complete successfully. "
"See log file for more details."));
emitResult();
return;
}
......@@ -98,6 +110,7 @@ void BupJob::slotIndexingDone(int pExitCode, QProcess::ExitStatus pExitStatus) {
connect(&mSaveProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(slotSavingDone(int,QProcess::ExitStatus)));
connect(&mSaveProcess, SIGNAL(started()), SLOT(slotSavingStarted()));
mLogStream << mSaveProcess.program().join(QLatin1String(" ")) << endl;
mSaveProcess.start();
}
......@@ -106,11 +119,14 @@ void BupJob::slotSavingStarted() {
}
void BupJob::slotSavingDone(int pExitCode, QProcess::ExitStatus pExitStatus) {
mLogStream << QString::fromUtf8(mSaveProcess.readAllStandardError()) << endl;
if(pExitStatus != QProcess::NormalExit || pExitCode != 0) {
setError(1);
setErrorText(i18nc("notification", "Backup did not complete successfully:\n%1",
QString::fromUtf8(mSaveProcess.readAllStandardError())));
mLogStream << QLatin1String("Kup did not successfully complete the bup backup job.") << endl;
setError(ErrorWithLog);
setErrorText(i18nc("notification", "Saving backup did not complete successfully. "
"See log file for more details."));
} else {
mLogStream << QLatin1String("Kup successfully completed the bup backup job.") << endl;
}
emitResult();
}
......@@ -31,7 +31,7 @@ class BupJob : public BackupJob
public:
BupJob(const QStringList &pPathsIncluded, const QStringList &pPathsExcluded,
const QString &pDestinationPath);
const QString &pDestinationPath, const QString &pLogFilePath);
virtual void start();
protected slots:
......
......@@ -131,7 +131,7 @@ void EDExecutor::startBackup() {
void EDExecutor::slotBackupDone(KJob *pJob) {
if(pJob->error()) {
KNotification::event(KNotification::Error, i18nc("@title", "Problem"), pJob->errorText());
notifyBackupFailed(pJob);
exitBackupRunningState(false);
} else {
mPlan->mLastCompleteBackup = QDateTime::currentDateTime().toUTC();
......
......@@ -117,7 +117,7 @@ void FSExecutor::startBackup() {
void FSExecutor::slotBackupDone(KJob *pJob) {
if(pJob->error()) {
KNotification::event(KNotification::Error, i18nc("@title", "Problem"), pJob->errorText());
notifyBackupFailed(pJob);
exitBackupRunningState(false);
} else {
mPlan->mLastCompleteBackup = QDateTime::currentDateTime().toUTC();
......
......@@ -8,3 +8,7 @@ Name=Start taking backup
Comment=A question if user wants to start taking a backup
Action=Popup
[Event/BackupFailed]
Name=Backup failed
Comment=Saving of backup failed, offer user to see log file
Action=Popup
......@@ -33,20 +33,41 @@
#include <QMenu>
PlanExecutor::PlanExecutor(BackupPlan *pPlan, QObject *pParent)
:QObject(pParent), mState(NOT_AVAILABLE), mPlan(pPlan), mQuestion(NULL)
:QObject(pParent), mState(NOT_AVAILABLE), mPlan(pPlan), mQuestion(NULL), mFailNotification(NULL)
{
mShowFilesAction = new QAction(i18nc("@action:inmenu", "Show Files"), this);
mShowFilesAction->setEnabled(false);
connect(mShowFilesAction, SIGNAL(triggered()), SLOT(showFilesClicked()));
QString lCachePath = QString::fromLocal8Bit(qgetenv("XDG_CACHE_HOME").constData());
if(lCachePath.isEmpty()) {
lCachePath = QDir::homePath();
lCachePath.append(QLatin1String("/.cache"));
}
lCachePath.append(QLatin1String("/kup"));
QDir lCacheDir(lCachePath);
if(!lCacheDir.exists()) {
if(!lCacheDir.mkpath(lCachePath)) {
lCachePath = QLatin1String("/tmp");
}
}
mLogFilePath = lCachePath;
mLogFilePath.append(QLatin1String("/kup_plan"));
mLogFilePath.append(QString::number(mPlan->planNumber()));
mLogFilePath.append(QLatin1String(".log"));
mRunBackupAction = new QAction(i18nc("@action:inmenu", "Take Backup Now"), this);
mRunBackupAction->setEnabled(false);
connect(mRunBackupAction, SIGNAL(triggered()), SLOT(enterBackupRunningState()));
mShowFilesAction = new QAction(i18nc("@action:inmenu", "Show Files"), this);
mShowFilesAction->setEnabled(false);
connect(mShowFilesAction, SIGNAL(triggered()), SLOT(showFilesClicked()));
mShowLogFileAction = new QAction(i18nc("@action:inmenu", "Show Log File"), this);
mShowLogFileAction->setEnabled(QFileInfo(mLogFilePath).exists());
connect(mShowLogFileAction, SIGNAL(triggered()), SLOT(showLog()));
mActionMenu = new QMenu(mPlan->mDescription);
mActionMenu->setEnabled(false);
mActionMenu->addAction(mRunBackupAction);
mActionMenu->addAction(mShowFilesAction);
mActionMenu->addAction(mShowLogFileAction);
mSchedulingTimer = new QTimer(this);
mSchedulingTimer->setSingleShot(true);
......@@ -61,7 +82,6 @@ void PlanExecutor::enterAvailableState() {
if(mState == NOT_AVAILABLE) {
mShowFilesAction->setEnabled(true);
mRunBackupAction->setEnabled(true);
mActionMenu->setEnabled(true);
mState = WAITING_FOR_FIRST_BACKUP; //initial child state of "Available" state
emit stateChanged();
}
......@@ -122,12 +142,12 @@ void PlanExecutor::enterNotAvailableState() {
mSchedulingTimer->stop();
mShowFilesAction->setEnabled(false);
mRunBackupAction->setEnabled(false);
mActionMenu->setEnabled(false);
mState = NOT_AVAILABLE;
emit stateChanged();
}
void PlanExecutor::askUser(const QString &pQuestion) {
discardUserQuestion();
mQuestion = new KNotification(QLatin1String("StartBackup"), KNotification::Persistent);
mQuestion->setTitle(i18nc("@title:window", "Backup Device Available - %1", mPlan->mDescription));
mQuestion->setText(pQuestion);
......@@ -151,6 +171,34 @@ void PlanExecutor::discardUserQuestion() {
}
}
void PlanExecutor::notifyBackupFailed(KJob *pFailedJob) {
discardFailNotification();
mFailNotification = new KNotification(QLatin1String("BackupFailed"), KNotification::Persistent);
mFailNotification->setTitle(i18nc("@title:window", "Saving of Backup Failed"));
mFailNotification->setText(pFailedJob->errorText());
if(pFailedJob->error() == BackupJob::ErrorWithLog) {
QStringList lAnswers;
lAnswers << i18nc("@action:button", "Show log file");
mFailNotification->setActions(lAnswers);
}
connect(mFailNotification, SIGNAL(action1Activated()), SLOT(showLog()));
connect(mFailNotification, SIGNAL(closed()), SLOT(discardFailNotification()));
connect(mFailNotification, SIGNAL(ignored()), SLOT(discardFailNotification()));
mFailNotification->sendEvent();
}
void PlanExecutor::discardFailNotification() {
if(mFailNotification) {
mFailNotification->deleteLater();
mFailNotification = NULL;
}
}
void PlanExecutor::showLog() {
discardFailNotification();
KRun::runUrl(mLogFilePath, QLatin1String("text/x-log"), NULL);
}
void PlanExecutor::enterBackupRunningState() {
discardUserQuestion();
mState = RUNNING;
......@@ -161,6 +209,7 @@ void PlanExecutor::enterBackupRunningState() {
void PlanExecutor::exitBackupRunningState(bool pWasSuccessful) {
mRunBackupAction->setEnabled(true);
mShowLogFileAction->setEnabled(QFileInfo(mLogFilePath).exists());
if(pWasSuccessful) {
if(mPlan->mScheduleType == BackupPlan::USAGE) {
//reset usage time after successful backup
......@@ -220,9 +269,9 @@ void PlanExecutor::showFilesClicked() {
BackupJob *PlanExecutor::createBackupJob() {
if(mPlan->mBackupType == BackupPlan::BupType) {
return new BupJob(mPlan->mPathsIncluded, mPlan->mPathsExcluded, mDestinationPath);
return new BupJob(mPlan->mPathsIncluded, mPlan->mPathsExcluded, mDestinationPath, mLogFilePath);
} else if(mPlan->mBackupType == BackupPlan::RsyncType) {
return new RsyncJob(mPlan->mPathsIncluded, mPlan->mPathsExcluded, mDestinationPath);
return new RsyncJob(mPlan->mPathsIncluded, mPlan->mPathsExcluded, mDestinationPath, mLogFilePath);
}
qWarning("Invalid backup type in configuration!");
return NULL;
......
......@@ -90,6 +90,11 @@ protected slots:
void askUser(const QString &pQuestion);
void discardUserQuestion();
void notifyBackupFailed(KJob *pFailedJob);
void discardFailNotification();
void showLog();
protected:
enum ExecutorState {NOT_AVAILABLE, WAITING_FOR_FIRST_BACKUP,
WAITING_FOR_BACKUP_AGAIN, RUNNING, WAITING_FOR_MANUAL_BACKUP};
......@@ -97,12 +102,15 @@ protected:
BackupJob *createBackupJob();
QString mDestinationPath;
QString mLogFilePath;
BackupPlan *mPlan;
QMenu *mActionMenu;
QAction *mShowFilesAction;
QAction *mRunBackupAction;
QAction *mShowLogFileAction;
KNotification *mQuestion;
QTimer *mSchedulingTimer;
KNotification *mFailNotification;
};
#endif // PLANEXECUTOR_H
......@@ -20,12 +20,13 @@
#include "rsyncjob.h"
#include <KGlobal>
#include <KLocale>
#include <QTimer>
RsyncJob::RsyncJob(const QStringList &pPathsIncluded, const QStringList &pPathsExcluded,
const QString &pDestinationPath)
:BackupJob(pPathsIncluded, pPathsExcluded, pDestinationPath)
const QString &pDestinationPath, const QString &pLogFilePath)
:BackupJob(pPathsIncluded, pPathsExcluded, pDestinationPath, pLogFilePath)
{
mRsyncProcess.setOutputChannelMode(KProcess::SeparateChannels);
}
......@@ -39,13 +40,18 @@ void RsyncJob::startRsync() {
lVersionProcess.setOutputChannelMode(KProcess::SeparateChannels);
lVersionProcess << QLatin1String("rsync") << QLatin1String("--version");
if(lVersionProcess.execute() < 0) {
setError(1);
setError(ErrorWithoutLog);
setErrorText(i18nc("notification", "The \"rsync\" program is needed but could not be found, "
"maybe it is not installed?"));
emitResult();
return;
}
mLogStream << QLatin1String("Kup is starting rsync backup job at ")
<< KGlobal::locale()->formatDateTime(QDateTime::currentDateTime(),
KLocale::LongDate, true)
<< endl;
mRsyncProcess << QLatin1String("rsync") << QLatin1String("-aR");
mRsyncProcess << QLatin1String("--delete") << QLatin1String("--delete-excluded");
foreach(QString lExclude, mPathsExcluded) {
......@@ -56,6 +62,7 @@ void RsyncJob::startRsync() {
connect(&mRsyncProcess, SIGNAL(started()), SLOT(slotRsyncStarted()));
connect(&mRsyncProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(slotRsyncFinished(int,QProcess::ExitStatus)));
mLogStream << mRsyncProcess.program().join(QLatin1String(" ")) << endl;
mRsyncProcess.start();
}
......@@ -64,10 +71,14 @@ void RsyncJob::slotRsyncStarted() {
}
void RsyncJob::slotRsyncFinished(int pExitCode, QProcess::ExitStatus pExitStatus) {
mLogStream << QString::fromUtf8(mRsyncProcess.readAllStandardError()) << endl;
if(pExitStatus != QProcess::NormalExit || pExitCode != 0) {
setError(1);
setErrorText(i18nc("notification", "Backup did not complete successfully:\n%1",
QString::fromLocal8Bit(mRsyncProcess.readAllStandardError())));
mLogStream << QLatin1String("Kup did not successfully complete the rsync backup job.") << endl;
setError(ErrorWithLog);
setErrorText(i18nc("notification", "Saving backup did not complete successfully. "
"See log file for more details."));
} else {
mLogStream << QLatin1String("Kup successfully completed the rsync backup job.") << endl;
}
emitResult();
}
......
......@@ -31,7 +31,7 @@ class RsyncJob : public BackupJob
public:
RsyncJob(const QStringList &pPathsIncluded, const QStringList &pPathsExcluded,
const QString &pDestinationPath);
const QString &pDestinationPath, const QString &pLogFilePath);
virtual void start();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment