From c86cae70f06e07a0fd99b7d2b54a3893290bb8ed Mon Sep 17 00:00:00 2001 From: Joern Date: Mon, 12 Oct 2015 19:01:31 +0200 Subject: [PATCH] AutoBackup/Data Snapshot Feature on close of Athlete Window/Tab ... collects all relevant Files from the Athletes directory subfolders (keeps the folder structure) and add them to a .zip file which can be stored in a different folder ... runs automatically when closing an Athlete (window or tab) ... configurable per Athlete -- Folder into which the .zip shall be stored -- #of times GC shall close without backup before the backup is activated (0 == no Auto Backup, every other number "x" means that GC closes x-1 times without running the backup and after running, resets the counter) --- src/Athlete.cpp | 6 ++ src/AthleteBackup.cpp | 172 ++++++++++++++++++++++++++++++++++++++++++ src/AthleteBackup.h | 44 +++++++++++ src/Pages.cpp | 43 ++++++++++- src/Pages.h | 5 ++ src/Settings.h | 3 + src/src.pro | 2 + 7 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 src/AthleteBackup.cpp create mode 100644 src/AthleteBackup.h diff --git a/src/Athlete.cpp b/src/Athlete.cpp index 7ce4c38fc..a3ce82f04 100644 --- a/src/Athlete.cpp +++ b/src/Athlete.cpp @@ -46,6 +46,7 @@ #include "LTMSettings.h" #include "RideImportWizard.h" #include "RideAutoImportConfig.h" +#include "AthleteBackup.h" #include "Route.h" @@ -185,6 +186,11 @@ Athlete::close() // set to latest so we don't repeat appsettings->setCValue(context->athlete->home->root().dirName(), GC_VERSION_USED, VERSION_LATEST); appsettings->setCValue(context->athlete->home->root().dirName(), GC_SAFEEXIT, true); + + // run autobackup on close (if configured) + AthleteBackup *backup = new AthleteBackup(context); + backup->backupOnClose(); + } void Athlete::loadCharts() diff --git a/src/AthleteBackup.cpp b/src/AthleteBackup.cpp new file mode 100644 index 000000000..8dedb4760 --- /dev/null +++ b/src/AthleteBackup.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2015 Joern Rischmueller (joern.rm@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; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#if QT_VERSION > 0x050400 +#include +#endif + +#include "Athlete.h" +#include "AthleteBackup.h" +#include "Settings.h" +#include "GcUpgrade.h" + +#include "../qzip/zipwriter.h" +#include "../qzip/zipreader.h" + + + +AthleteBackup::AthleteBackup(Context *context) +{ + + this->context = context; + cyclist = context->athlete->cyclist; + + // set the directories to be backed up + // for a FULL backup basically all data folders + sourceFolder.append(context->athlete->home->activities()); + sourceFolder.append(context->athlete->home->imports()); + sourceFolder.append(context->athlete->home->records()); + sourceFolder.append(context->athlete->home->downloads()); + sourceFolder.append(context->athlete->home->fileBackup()); + sourceFolder.append(context->athlete->home->config()); + sourceFolder.append(context->athlete->home->calendar()); + sourceFolder.append(context->athlete->home->workouts()); + +} + +AthleteBackup::~AthleteBackup() +{ + +} + +void +AthleteBackup::backupOnClose() +{ + int backupPeriod = appsettings->cvalue(context->athlete->cyclist, GC_AUTOBACKUP_PERIOD, 0).toInt(); + QString backupFolder = appsettings->cvalue(context->athlete->cyclist, GC_AUTOBACKUP_FOLDER, "").toString(); + if (backupPeriod == 0 || backupFolder == "" ) return; + int backupCounter = appsettings->cvalue(context->athlete->cyclist, GC_AUTOBACKUP_COUNTER, 0).toInt(); + backupCounter++; + if (backupCounter < backupPeriod) { + appsettings->setCValue(context->athlete->cyclist, GC_AUTOBACKUP_COUNTER, backupCounter); + return; + } + + // backup requested so lets see if we have something to backup and if yes, how much + int fileCount = 0; + qint64 fileSize = 0; + // count the files for the progress bar and the calculate the overall size + foreach (QDir folder, sourceFolder) { + // get all files + foreach (QFileInfo fileName, folder.entryInfoList(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks)) { + fileCount++; + fileSize += fileName.size(); + + } + } + + if (fileCount == 0) { + QMessageBox::information(NULL, tr("Auto Backup"), tr("No files found for athlete %1 - all athlete sub-directories are empty.").arg(cyclist)); + return; + } + +#if QT_VERSION > 0x050400 + // if if there is enough space available for the backup + QStorageInfo storage(backupFolder); + if (storage.isValid() && storage.isReady()) { + // let's assume a 1:5 Zip compression to have enough space available for the ZIP + if (storage.bytesAvailable() < fileSize / 5) { + QMessageBox::warning(NULL, tr("Auto Backup"), tr("Not enough space available on disk: %1 - no backup .zip file created").arg(storage.rootPath())); + return; + } + } else { + QMessageBox::warning(NULL, tr("Auto Backup"), tr("Directory %1 not available. No backup .zip file created for athlete %2.").arg(backupFolder).arg(cyclist)); + return; + } +#else + QDir checkDir(backupFolder); + if (!checkDir.exists()) { + QMessageBox::warning(NULL, tr("Auto Backup"), tr("Directory %1 not available. No backup .zip file created for athlete %2.").arg(backupFolder).arg(cyclist)); + return; + } +#endif + + QChar zero = QLatin1Char('0'); + QString targetFileName = QString( "GC_%1_%2_%3_%4_%5_%6_%7_%8.zip" ) + .arg ( VERSION_LATEST ) + .arg ( cyclist ) + .arg ( QDate::currentDate().year(), 4, 10, zero ) + .arg ( QDate::currentDate().month(), 2, 10, zero ) + .arg ( QDate::currentDate().day(), 2, 10, zero ) + .arg ( QTime::currentTime().hour(), 2, 10, zero ) + .arg ( QTime::currentTime().minute(), 2, 10, zero ) + .arg ( QTime::currentTime().second(), 2, 10, zero ); + + + // add files using zip writer + QFile zipFile(backupFolder+"/"+targetFileName); + if (!zipFile.open(QIODevice::WriteOnly)) { + QMessageBox::warning(NULL, tr("Auto Backup"), tr("Backup file %1 cannot be created.").arg(zipFile.fileName())); + return; + } + zipFile.close(); + ZipWriter writer(zipFile.fileName()); + + QProgressDialog progress(tr("Adding files to backup %1 for athlete %2 ...").arg(targetFileName).arg(cyclist), tr("Abort Backup"), 0, fileCount, NULL); + progress.setWindowModality(Qt::WindowModal); + + // now do the Zipping + bool userCanceled = false; + int fileCounter = 0; + foreach (QDir folder, sourceFolder) { + // get all files + writer.addDirectory(folder.dirName()); + foreach (QFileInfo fileName, folder.entryInfoList(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks)) { + QFile file(fileName.canonicalFilePath()); + if (file.open(QIODevice::ReadOnly)) { + if (progress.wasCanceled()) { + userCanceled = true; + break; + } + writer.addFile(folder.dirName()+"/"+fileName.fileName(), file.readAll()); + progress.setValue(fileCounter); + fileCounter++; + file.close(); + } + } + if (userCanceled) break; + } + + // final processing + writer.close(); + + // delete the .ZIP file if the user canceled the backup + if (userCanceled) { + zipFile.remove(); + } else { + // we are done, full progress and reset of counter + progress.setValue(fileCount); + appsettings->setCValue(context->athlete->cyclist, GC_AUTOBACKUP_COUNTER, 0); + } + +} + + + diff --git a/src/AthleteBackup.h b/src/AthleteBackup.h new file mode 100644 index 000000000..09fe16502 --- /dev/null +++ b/src/AthleteBackup.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2015 Joern Rischmueller (joern.rm@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; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _GC_AthleteBackup_h +#define _GC_AthleteBackup_h 1 + +#include + +#include "Context.h" +#include "Athlete.h" + + +class AthleteBackup : public QObject +{ + Q_OBJECT + + public: + AthleteBackup(Context *context); + ~AthleteBackup(); + void backupOnClose(); + + private: + Context *context; + QString cyclist; + QList sourceFolder; +}; + + +#endif diff --git a/src/Pages.cpp b/src/Pages.cpp index 9baa243d4..f4b901806 100644 --- a/src/Pages.cpp +++ b/src/Pages.cpp @@ -1049,6 +1049,24 @@ RiderPage::RiderPage(QWidget *parent, Context *context) : QWidget(parent), conte showSBToday = new QCheckBox(tr("PMC Stress Balance Today"), this); showSBToday->setChecked(appsettings->cvalue(context->athlete->cyclist, GC_SB_TODAY).toInt()); + + // + // Auto Backup + // + // Selecting the storage folder folder of the Local File Store + QLabel *autoBackupFolderLabel = new QLabel(tr("Auto Backup Folder")); + autoBackupFolder = new QLineEdit(this); + autoBackupFolder->setText(appsettings->cvalue(context->athlete->cyclist, GC_AUTOBACKUP_FOLDER, "").toString()); + autoBackupFolderBrowse = new QPushButton(tr("Browse")); + connect(autoBackupFolderBrowse, SIGNAL(clicked()), this, SLOT(chooseAutoBackupFolder())); + autoBackupPeriod = new QSpinBox(this); + autoBackupPeriod->setMinimum(0); + autoBackupPeriod->setMaximum(9999); + autoBackupPeriod->setSingleStep(1); + QLabel *autoBackupPeriodLabel = new QLabel(tr("Auto Backup Period")); + autoBackupPeriod->setValue(appsettings->cvalue(context->athlete->cyclist, GC_AUTOBACKUP_PERIOD, 0).toInt()); + + Qt::Alignment alignment = Qt::AlignLeft|Qt::AlignVCenter; grid->addWidget(nicklabel, 0, 0, alignment); @@ -1075,9 +1093,14 @@ RiderPage::RiderPage(QWidget *parent, Context *context) : QWidget(parent), conte grid->addWidget(perfManLTSLabel, 9, 0, alignment); grid->addWidget(perfManLTSavg, 9, 1, alignment); grid->addWidget(showSBToday, 10, 1, alignment); + grid->addWidget(autoBackupFolderLabel, 11,0, alignment); + grid->addWidget(autoBackupFolder, 11, 1, alignment); + grid->addWidget(autoBackupFolderBrowse, 11, 2, alignment); + grid->addWidget(autoBackupPeriodLabel, 12, 0,alignment); + grid->addWidget(autoBackupPeriod, 12, 1, alignment); - grid->addWidget(biolabel, 11, 0, alignment); - grid->addWidget(bio, 12, 0, 1, 3); + grid->addWidget(biolabel, 13, 0, alignment); + grid->addWidget(bio, 14, 0, 1, 3); grid->addWidget(avatarButton, 0, 1, 5, 2, Qt::AlignRight|Qt::AlignVCenter); all->addLayout(grid); @@ -1113,6 +1136,17 @@ RiderPage::chooseAvatar() } } +void RiderPage::chooseAutoBackupFolder() +{ + // did the user type something ? if not, get it from the Settings + QString path = autoBackupFolder->text(); + if (path == "") path = appsettings->cvalue(context->athlete->cyclist, GC_AUTOBACKUP_FOLDER, "").toString(); + QString dir = QFileDialog::getExistingDirectory(this, tr("Choose Backup Directory"), + path, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (dir != "") autoBackupFolder->setText(dir); //only overwrite current dir, if a new was selected + +} + void RiderPage::unitChanged(int currentIndex) { @@ -1173,6 +1207,11 @@ RiderPage::saveClicked() appsettings->setCValue(context->athlete->cyclist, GC_LTS_DAYS, perfManLTSavg->text()); appsettings->setCValue(context->athlete->cyclist, GC_SB_TODAY, (int) showSBToday->isChecked()); + // Auto Backup + appsettings->setCValue(context->athlete->cyclist, GC_AUTOBACKUP_FOLDER, autoBackupFolder->text()); + appsettings->setCValue(context->athlete->cyclist, GC_AUTOBACKUP_PERIOD, autoBackupPeriod->value()); + + qint32 state=0; // general stuff changed ? diff --git a/src/Pages.h b/src/Pages.h index c605e555a..638e9a330 100644 --- a/src/Pages.h +++ b/src/Pages.h @@ -158,6 +158,9 @@ class RiderPage : public QWidget QIntValidator *perfManSTSavgValidator; QIntValidator *perfManLTSavgValidator; QCheckBox *showSBToday; + QSpinBox *autoBackupPeriod; + QLineEdit *autoBackupFolder; + QPushButton *autoBackupFolderBrowse; struct { @@ -171,6 +174,8 @@ class RiderPage : public QWidget private slots: void calcWheelSize(); void resetWheelSize(); + void chooseAutoBackupFolder(); + }; diff --git a/src/Settings.h b/src/Settings.h index 3ce5cc061..3cd64792e 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -210,6 +210,9 @@ #define GC_WHEELSIZE "wheelsize" #define GC_USE_CP_FOR_FTP "cp/useforftp" // use CP for FTP #define GC_NETWORKFILESTORE_FOLDER "networkfilestore/folder" // folder to sync with +#define GC_AUTOBACKUP_FOLDER "autobackup/folder" +#define GC_AUTOBACKUP_PERIOD "autobackup/period" // how often is the Athlete Folder backuped up / 0 == never +#define GC_AUTOBACKUP_COUNTER "autobackup/counter" // counts to the next backup // ride navigator #define GC_NAVHEADINGS "navigator/headings" diff --git a/src/src.pro b/src/src.pro index d1284df7b..158729d35 100644 --- a/src/src.pro +++ b/src/src.pro @@ -334,6 +334,7 @@ HEADERS += \ ANTMessages.h \ ANTlocalController.h \ Athlete.h \ + AthleteBackup.h \ BatchExportDialog.h \ BestIntervalDialog.h \ BinRideFile.h \ @@ -558,6 +559,7 @@ SOURCES += \ ANTMessage.cpp \ ANTlocalController.cpp \ Athlete.cpp \ + AthleteBackup.cpp \ BasicRideMetrics.cpp \ BatchExportDialog.cpp \ BestIntervalDialog.cpp \