|
|
/***************************************************************************
|
|
|
* Copyright (C) 2003 by S<>astien Laot *
|
|
|
* slaout@linux62.org *
|
|
|
* *
|
|
|
* 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. *
|
|
|
***************************************************************************/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
#include "config.h"
|
|
|
#endif
|
|
|
|
|
|
#include "backup.h"
|
|
|
|
|
|
#include "global.h"
|
|
|
#include "variouswidgets.h"
|
|
|
#include "settings.h"
|
|
|
#include "tools.h"
|
|
|
#include "formatimporter.h" // To move a folder
|
|
|
|
|
|
#include <tqhbox.h>
|
|
|
#include <tqvbox.h>
|
|
|
#include <tqlayout.h>
|
|
|
#include <tqlabel.h>
|
|
|
#include <tqpushbutton.h>
|
|
|
#include <tdelocale.h>
|
|
|
#include <tqdir.h>
|
|
|
#include <tdeapplication.h>
|
|
|
#include <tdeaboutdata.h>
|
|
|
#include <tqgroupbox.h>
|
|
|
#include <kdirselectdialog.h>
|
|
|
#include <krun.h>
|
|
|
#include <tdeconfig.h>
|
|
|
#include <ktar.h>
|
|
|
#include <tdefiledialog.h>
|
|
|
#include <kprogress.h>
|
|
|
#include <tdemessagebox.h>
|
|
|
#include <cstdlib>
|
|
|
#include <unistd.h> // usleep()
|
|
|
|
|
|
/**
|
|
|
* Backups are wrapped in a .tar.gz, inside that folder name.
|
|
|
* An archive is not a backup or is corrupted if data are not in that folder!
|
|
|
*/
|
|
|
const TQString backupMagicFolder = "BasKet-Note-Pads_Backup";
|
|
|
|
|
|
/** class BackupDialog: */
|
|
|
|
|
|
BackupDialog::BackupDialog(TQWidget *parent, const char *name)
|
|
|
: KDialogBase(parent, name, /*modal=*/true, i18n("Backup & Restore"),
|
|
|
KDialogBase::Close, KDialogBase::Close, /*separator=*/false)
|
|
|
{
|
|
|
TQVBox *page = makeVBoxMainWidget();
|
|
|
// page->setSpacing(spacingHint());
|
|
|
|
|
|
TQString savesFolder = Global::savesFolder();
|
|
|
savesFolder = savesFolder.left(savesFolder.length() - 1); // savesFolder ends with "/"
|
|
|
|
|
|
TQGroupBox *folderGroup = new TQGroupBox(1, TQt::Horizontal, i18n("Save Folder"), page);
|
|
|
new TQLabel("<qt><nobr>" + i18n("Your baskets are currently stored in that folder:<br><b>%1</b>").arg(savesFolder), folderGroup);
|
|
|
TQWidget *folderWidget = new TQWidget(folderGroup);
|
|
|
TQHBoxLayout *folderLayout = new TQHBoxLayout(folderWidget, 0, spacingHint());
|
|
|
TQPushButton *moveFolder = new TQPushButton(i18n("&Move to Another Folder..."), folderWidget);
|
|
|
TQPushButton *useFolder = new TQPushButton(i18n("&Use Another Existing Folder..."), folderWidget);
|
|
|
HelpLabel *helpLabel = new HelpLabel(i18n("Why to do that?"), i18n(
|
|
|
"<p>You can move the folder where %1 store your baskets to:</p><ul>"
|
|
|
"<li>Store your baskets in a visible place in your home folder, like ~/Notes or ~/Baskets, so you can manually backup them when you want.</li>"
|
|
|
"<li>Store your baskets on a server to share them between two computers.<br>"
|
|
|
"In this case, mount the shared-folder to the local file system and ask %2 to use that mount point.<br>"
|
|
|
"Warning: you should not run %3 at the same time on both computers, or you risk to loss data while the two applications are desynced.</li>"
|
|
|
"</ul><p>Please remember that you should not change the content of that folder manually (eg. adding a file in a basket folder will not add that file to the basket).</p>")
|
|
|
.arg(kapp->aboutData()->programName())
|
|
|
.arg(kapp->aboutData()->programName())
|
|
|
.arg(kapp->aboutData()->programName()),
|
|
|
folderWidget);
|
|
|
folderLayout->addWidget(moveFolder);
|
|
|
folderLayout->addWidget(useFolder);
|
|
|
folderLayout->addWidget(helpLabel);
|
|
|
folderLayout->addStretch();
|
|
|
connect( moveFolder, TQT_SIGNAL(clicked()), this, TQT_SLOT(moveToAnotherFolder()) );
|
|
|
connect( useFolder, TQT_SIGNAL(clicked()), this, TQT_SLOT(useAnotherExistingFolder()) );
|
|
|
|
|
|
TQGroupBox *backupGroup = new TQGroupBox(1, TQt::Horizontal, i18n("Backups"), page);
|
|
|
TQWidget *backupWidget = new TQWidget(backupGroup);
|
|
|
TQHBoxLayout *backupLayout = new TQHBoxLayout(backupWidget, 0, spacingHint());
|
|
|
TQPushButton *backupButton = new TQPushButton(i18n("&Backup..."), backupWidget);
|
|
|
TQPushButton *restoreButton = new TQPushButton(i18n("&Restore a Backup..."), backupWidget);
|
|
|
m_lastBackup = new TQLabel("", backupWidget);
|
|
|
backupLayout->addWidget(backupButton);
|
|
|
backupLayout->addWidget(restoreButton);
|
|
|
backupLayout->addWidget(m_lastBackup);
|
|
|
backupLayout->addStretch();
|
|
|
connect( backupButton, TQT_SIGNAL(clicked()), this, TQT_SLOT(backup()) );
|
|
|
connect( restoreButton, TQT_SIGNAL(clicked()), this, TQT_SLOT(restore()) );
|
|
|
|
|
|
populateLastBackup();
|
|
|
|
|
|
(new TQWidget(page))->setSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding);
|
|
|
}
|
|
|
|
|
|
BackupDialog::~BackupDialog()
|
|
|
{
|
|
|
}
|
|
|
|
|
|
void BackupDialog::populateLastBackup()
|
|
|
{
|
|
|
TQString lastBackupText = i18n("Last backup: never");
|
|
|
if (Settings::lastBackup().isValid())
|
|
|
lastBackupText = i18n("Last backup: %1").arg(Settings::lastBackup().toString(TQt::LocalDate));
|
|
|
|
|
|
m_lastBackup->setText(lastBackupText);
|
|
|
}
|
|
|
|
|
|
void BackupDialog::moveToAnotherFolder()
|
|
|
{
|
|
|
KURL selectedURL = KDirSelectDialog::selectDirectory(
|
|
|
/*startDir=*/Global::savesFolder(), /*localOnly=*/true, /*parent=*/0,
|
|
|
/*caption=*/i18n("Choose a Folder Where to Move Baskets"));
|
|
|
|
|
|
if (!selectedURL.isEmpty()) {
|
|
|
TQString folder = selectedURL.path();
|
|
|
TQDir dir(folder);
|
|
|
// The folder should not exists, or be empty (because KDirSelectDialog will likely create it anyway):
|
|
|
if (dir.exists()) {
|
|
|
// Get the content of the folder:
|
|
|
TQStringList content = dir.entryList();
|
|
|
if (content.count() > 2) { // "." and ".."
|
|
|
int result = KMessageBox::questionYesNo(
|
|
|
0,
|
|
|
"<qt>" + i18n("The folder <b>%1</b> is not empty. Do you want to override it?").arg(folder),
|
|
|
i18n("Override Folder?"),
|
|
|
KGuiItem(i18n("&Override"), "document-save")
|
|
|
);
|
|
|
if (result == KMessageBox::No)
|
|
|
return;
|
|
|
}
|
|
|
Tools::deleteRecursively(folder);
|
|
|
}
|
|
|
FormatImporter copier;
|
|
|
copier.moveFolder(Global::savesFolder(), folder);
|
|
|
Backup::setFolderAndRestart(folder, i18n("Your baskets have been successfuly moved to <b>%1</b>. %2 is going to be restarted to take this change into account."));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void BackupDialog::useAnotherExistingFolder()
|
|
|
{
|
|
|
KURL selectedURL = KDirSelectDialog::selectDirectory(
|
|
|
/*startDir=*/Global::savesFolder(), /*localOnly=*/true, /*parent=*/0,
|
|
|
/*caption=*/i18n("Choose an Existing Folder to Store Baskets"));
|
|
|
|
|
|
if (!selectedURL.isEmpty()) {
|
|
|
Backup::setFolderAndRestart(selectedURL.path(), i18n("Your basket save folder has been successfuly changed to <b>%1</b>. %2 is going to be restarted to take this change into account."));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void BackupDialog::backup()
|
|
|
{
|
|
|
TQDir dir;
|
|
|
|
|
|
// Compute a default file name & path (eg. "Baskets_2007-01-31.tar.gz"):
|
|
|
TDEConfig *config = TDEGlobal::config();
|
|
|
config->setGroup("Backups");
|
|
|
TQString folder = config->readEntry("lastFolder", TQDir::homeDirPath()) + "/";
|
|
|
TQString fileName = i18n("Backup filename (without extension), %1 is the date", "Baskets_%1")
|
|
|
.arg(TQDate::currentDate().toString(TQt::ISODate));
|
|
|
TQString url = folder + fileName;
|
|
|
|
|
|
// Ask a file name & path to the user:
|
|
|
TQString filter = "*.tar.gz|" + i18n("Tar Archives Compressed by Gzip") + "\n*|" + i18n("All Files");
|
|
|
TQString destination = url;
|
|
|
for (bool askAgain = true; askAgain; ) {
|
|
|
// Ask:
|
|
|
destination = KFileDialog::getSaveFileName(destination, filter, 0, i18n("Backup Baskets"));
|
|
|
// User canceled?
|
|
|
if (destination.isEmpty())
|
|
|
return;
|
|
|
// File already existing? Ask for overriding:
|
|
|
if (dir.exists(destination)) {
|
|
|
int result = KMessageBox::questionYesNoCancel(
|
|
|
0,
|
|
|
"<qt>" + i18n("The file <b>%1</b> already exists. Do you really want to override it?")
|
|
|
.arg(KURL(destination).fileName()),
|
|
|
i18n("Override File?"),
|
|
|
KGuiItem(i18n("&Override"), "document-save")
|
|
|
);
|
|
|
if (result == KMessageBox::Cancel)
|
|
|
return;
|
|
|
else if (result == KMessageBox::Yes)
|
|
|
askAgain = false;
|
|
|
} else
|
|
|
askAgain = false;
|
|
|
}
|
|
|
|
|
|
KProgressDialog dialog(0, 0, i18n("Backup Baskets"), i18n("Backing up baskets. Please wait..."), /*modal=*/true);
|
|
|
dialog.setAllowCancel(false);
|
|
|
dialog.setAutoClose(true);
|
|
|
dialog.show();
|
|
|
KProgress *progress = dialog.progressBar();
|
|
|
progress->setTotalSteps(0/*Busy/Undefined*/);
|
|
|
progress->setProgress(0);
|
|
|
progress->setPercentageVisible(false);
|
|
|
|
|
|
BackupThread thread(destination, Global::savesFolder());
|
|
|
thread.start();
|
|
|
while (thread.running()) {
|
|
|
progress->advance(1); // Or else, the animation is not played!
|
|
|
kapp->processEvents();
|
|
|
usleep(300); // Not too long because if the backup process is finished, we wait for nothing
|
|
|
}
|
|
|
|
|
|
Settings::setLastBackup(TQDate::currentDate());
|
|
|
Settings::saveConfig();
|
|
|
populateLastBackup();
|
|
|
}
|
|
|
|
|
|
void BackupDialog::restore()
|
|
|
{
|
|
|
// Get last backup folder:
|
|
|
TDEConfig *config = TDEGlobal::config();
|
|
|
config->setGroup("Backups");
|
|
|
TQString folder = config->readEntry("lastFolder", TQDir::homeDirPath()) + "/";
|
|
|
|
|
|
// Ask a file name to the user:
|
|
|
TQString filter = "*.tar.gz|" + i18n("Tar Archives Compressed by Gzip") + "\n*|" + i18n("All Files");
|
|
|
TQString path = KFileDialog::getOpenFileName(folder, filter, this, i18n("Open Basket Archive"));
|
|
|
if (path.isEmpty()) // User has canceled
|
|
|
return;
|
|
|
|
|
|
// Before replacing the basket data folder with the backup content, we safely backup the current baskets to the home folder.
|
|
|
// So if the backup is corrupted or something goes wrong while restoring (power cut...) the user will be able to restore the old working data:
|
|
|
TQString safetyPath = Backup::newSafetyFolder();
|
|
|
FormatImporter copier;
|
|
|
copier.moveFolder(Global::savesFolder(), safetyPath);
|
|
|
|
|
|
// Add the README file for user to cancel a bad restoration:
|
|
|
TQString readmePath = safetyPath + i18n("README.txt");
|
|
|
TQFile file(readmePath);
|
|
|
if (file.open(IO_WriteOnly)) {
|
|
|
TQTextStream stream(&file);
|
|
|
stream << i18n("This is a safety copy of your baskets like they were before you started to restore the backup %1.").arg(KURL(path).fileName()) + "\n\n"
|
|
|
<< i18n("If the restoration was a success and you restored what you wanted to restore, you can remove this folder.") + "\n\n"
|
|
|
<< i18n("If something went wrong during the restoration process, you can re-use this folder to store your baskets and nothing will be lost.") + "\n\n"
|
|
|
<< i18n("Choose \"Basket\" -> \"Backup & Restore...\" -> \"Use Another Existing Folder...\" and select that folder.") + "\n";
|
|
|
file.close();
|
|
|
}
|
|
|
|
|
|
TQString message =
|
|
|
"<p><nobr>" + i18n("Restoring <b>%1</b>. Please wait...").arg(KURL(path).fileName()) + "</nobr></p><p>" +
|
|
|
i18n("If something goes wrong during the restoration process, read the file <b>%1</b>.").arg(readmePath);
|
|
|
|
|
|
KProgressDialog *dialog = new KProgressDialog(0, 0, i18n("Restore Baskets"), message, /*modal=*/true);
|
|
|
dialog->setAllowCancel(false);
|
|
|
dialog->setAutoClose(true);
|
|
|
dialog->show();
|
|
|
KProgress *progress = dialog->progressBar();
|
|
|
progress->setTotalSteps(0/*Busy/Undefined*/);
|
|
|
progress->setProgress(0);
|
|
|
progress->setPercentageVisible(false);
|
|
|
|
|
|
// Uncompress:
|
|
|
RestoreThread thread(path, Global::savesFolder());
|
|
|
thread.start();
|
|
|
while (thread.running()) {
|
|
|
progress->advance(1); // Or else, the animation is not played!
|
|
|
kapp->processEvents();
|
|
|
usleep(300); // Not too long because if the restore process is finished, we wait for nothing
|
|
|
}
|
|
|
|
|
|
dialog->hide(); // The restore is finished, do not continue to show it while telling the user the application is going to be restarted
|
|
|
delete dialog; // If we only hidden it, it reappeared just after having restored a small backup... Very strange.
|
|
|
dialog = 0; // This was annoying since it is modal and the "BasKet Note Pads is going to be restarted" message was not reachable.
|
|
|
//kapp->processEvents();
|
|
|
|
|
|
// Check for errors:
|
|
|
if (!thread.success()) {
|
|
|
// Restore the old baskets:
|
|
|
TQDir dir;
|
|
|
dir.remove(readmePath);
|
|
|
copier.moveFolder(safetyPath, Global::savesFolder());
|
|
|
// Tell the user:
|
|
|
KMessageBox::error(0, i18n("This archive is either not a backup of baskets or is corrupted. It cannot be imported. Your old baskets have been preserved instead."), i18n("Restore Error"));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// Note: The safety backup is not removed now because the code can has been wrong, somehow, or the user perhapse restored an older backup by error...
|
|
|
// The restore process will not be called very often (it is possible it will only be called once or twice arround the world during the next years).
|
|
|
// So it is rare enough to force the user to remove the safety folder, but keep him in control and let him safely recover from restoration errors.
|
|
|
|
|
|
Backup::setFolderAndRestart(Global::savesFolder()/*No change*/, i18n("Your backup has been successfuly restored to <b>%1</b>. %2 is going to be restarted to take this change into account."));
|
|
|
}
|
|
|
|
|
|
/** class Backup: */
|
|
|
|
|
|
TQString Backup::binaryPath = "";
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
void Backup::figureOutBinaryPath(const char *argv0, TQApplication &app)
|
|
|
{
|
|
|
/*
|
|
|
The application can be launched by two ways:
|
|
|
- Globaly (app.applicationFilePath() is good)
|
|
|
- In KDevelop or with an absolute path (app.applicationFilePath() is wrong)
|
|
|
This function is called at the very start of main() so that the current directory has not been changed yet.
|
|
|
|
|
|
Command line (argv[0]) TQDir(argv[0]).canonicalPath() app.applicationFilePath()
|
|
|
====================== ============================================= =========================
|
|
|
"basket" "" "/opt/trinity/bin/basket"
|
|
|
"./src/.libs/basket" "/home/seb/prog/basket/debug/src/.lib/basket" "/opt/trinity/bin/basket"
|
|
|
*/
|
|
|
|
|
|
binaryPath = TQDir(argv0).canonicalPath();
|
|
|
if (binaryPath.isEmpty())
|
|
|
binaryPath = app.applicationFilePath();
|
|
|
}
|
|
|
|
|
|
void Backup::setFolderAndRestart(const TQString &folder, const TQString &message)
|
|
|
{
|
|
|
// Set the folder:
|
|
|
Settings::setDataFolder(folder);
|
|
|
Settings::saveConfig();
|
|
|
|
|
|
// Rassure the user that the application main window disapearition is not a crash, but a normal restart.
|
|
|
// This is important for users to trust the application in such a critical phase and understands what's happening:
|
|
|
KMessageBox::information(
|
|
|
0,
|
|
|
"<qt>" + message.arg(
|
|
|
(folder.endsWith("/") ? folder.left(folder.length() - 1) : folder),
|
|
|
kapp->aboutData()->programName()),
|
|
|
i18n("Restart")
|
|
|
);
|
|
|
|
|
|
// Restart the application:
|
|
|
KRun::runCommand(binaryPath, kapp->aboutData()->programName(), kapp->iconName());
|
|
|
exit(0);
|
|
|
}
|
|
|
|
|
|
TQString Backup::newSafetyFolder()
|
|
|
{
|
|
|
TQDir dir;
|
|
|
TQString fullPath;
|
|
|
|
|
|
fullPath = TQDir::homeDirPath() + "/" + i18n("Safety folder name before restoring a basket data archive", "Baskets Before Restoration") + "/";
|
|
|
if (!dir.exists(fullPath))
|
|
|
return fullPath;
|
|
|
|
|
|
for (int i = 2; ; ++i) {
|
|
|
fullPath = TQDir::homeDirPath() + "/" + i18n("Safety folder name before restoring a basket data archive", "Baskets Before Restoration (%1)").arg(i) + "/";
|
|
|
if (!dir.exists(fullPath))
|
|
|
return fullPath;
|
|
|
}
|
|
|
|
|
|
return "";
|
|
|
}
|
|
|
|
|
|
/** class BackupThread: */
|
|
|
|
|
|
BackupThread::BackupThread(const TQString &tarFile, const TQString &folderToBackup)
|
|
|
: m_tarFile(tarFile), m_folderToBackup(folderToBackup)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
void BackupThread::run()
|
|
|
{
|
|
|
KTar tar(m_tarFile, "application/x-gzip");
|
|
|
tar.open(IO_WriteOnly);
|
|
|
tar.addLocalDirectory(m_folderToBackup, backupMagicFolder);
|
|
|
// KArchive does not add hidden files. Basket description files (".basket") are hidden, we add them manually:
|
|
|
TQDir dir(m_folderToBackup + "baskets/");
|
|
|
TQStringList baskets = dir.entryList(TQDir::Dirs);
|
|
|
for (TQStringList::Iterator it = baskets.begin(); it != baskets.end(); ++it) {
|
|
|
tar.addLocalFile(
|
|
|
m_folderToBackup + "baskets/" + *it + "/.basket",
|
|
|
backupMagicFolder + "/baskets/" + *it + "/.basket"
|
|
|
);
|
|
|
}
|
|
|
// We finished:
|
|
|
tar.close();
|
|
|
}
|
|
|
|
|
|
/** class RestoreThread: */
|
|
|
|
|
|
RestoreThread::RestoreThread(const TQString &tarFile, const TQString &destFolder)
|
|
|
: m_tarFile(tarFile), m_destFolder(destFolder)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
void RestoreThread::run()
|
|
|
{
|
|
|
m_success = false;
|
|
|
KTar tar(m_tarFile, "application/x-gzip");
|
|
|
tar.open(IO_ReadOnly);
|
|
|
if (tar.isOpened()) {
|
|
|
const KArchiveDirectory *directory = tar.directory();
|
|
|
if (directory->entries().contains(backupMagicFolder)) {
|
|
|
const KArchiveEntry *entry = directory->entry(backupMagicFolder);
|
|
|
if (entry->isDirectory()) {
|
|
|
((const KArchiveDirectory*) entry)->copyTo(m_destFolder);
|
|
|
m_success = true;
|
|
|
}
|
|
|
}
|
|
|
tar.close();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#include "backup.moc"
|