Updated the README file with some contributor tips.
[basket4.git] / src / backup.cpp
blob61431438f231d45c2fa04cac7a8b73b200fa1b55
1 /***************************************************************************
2 * Copyright (C) 2003 by S�astien Laot *
3 * slaout@linux62.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
21 #include "backup.h"
23 #include "global.h"
24 #include "variouswidgets.h"
25 #include "settings.h"
26 #include "tools.h"
27 #include "formatimporter.h" // To move a folder
29 #include <q3hbox.h>
30 #include <q3vbox.h>
31 #include <qlayout.h>
32 #include <qlabel.h>
33 #include <qpushbutton.h>
34 //Added by qt3to4:
35 #include <Q3TextStream>
36 #include <Q3HBoxLayout>
37 #include <klocale.h>
38 #include <qdir.h>
39 #include <kapplication.h>
40 #include <kaboutdata.h>
41 #include <q3groupbox.h>
42 #include <kdirselectdialog.h>
43 #include <krun.h>
44 #include <kconfig.h>
45 #include <ktar.h>
46 #include <kfiledialog.h>
47 #include <kprogressdialog.h>
48 #include <kmessagebox.h>
49 #include <unistd.h> // usleep()
51 #include <KVBox>
53 /**
54 * Backups are wrapped in a .tar.gz, inside that folder name.
55 * An archive is not a backup or is corrupted if data are not in that folder!
57 const QString backupMagicFolder = "BasKet-Note-Pads_Backup";
59 /** class BackupDialog: */
61 BackupDialog::BackupDialog(QWidget *parent, const char *name)
62 : KDialog(parent)
64 setObjectName(name);
65 setModal(true);
66 setCaption(i18n("Backup & Restore"));
67 setButtons(KDialog::Close);
68 setDefaultButton(KDialog::Close);
69 showButtonSeparator(false);
71 KVBox *page = new KVBox(this);
72 setMainWidget(page);
74 // page->setSpacing(spacingHint());
76 QString savesFolder = Global::savesFolder();
77 savesFolder = savesFolder.left(savesFolder.length() - 1); // savesFolder ends with "/"
79 Q3GroupBox *folderGroup = new Q3GroupBox(1, Qt::Horizontal, i18n("Save Folder"), page);
80 new QLabel("<qt><nobr>" + i18n("Your baskets are currently stored in that folder:<br><b>%1</b>").arg(savesFolder), folderGroup);
81 QWidget *folderWidget = new QWidget(folderGroup);
82 Q3HBoxLayout *folderLayout = new Q3HBoxLayout(folderWidget, 0, spacingHint());
83 QPushButton *moveFolder = new QPushButton(i18n("&Move to Another Folder..."), folderWidget);
84 QPushButton *useFolder = new QPushButton(i18n("&Use Another Existing Folder..."), folderWidget);
85 HelpLabel *helpLabel = new HelpLabel(i18n("Why to do that?"), i18n(
86 "<p>You can move the folder where %1 store your baskets to:</p><ul>"
87 "<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>"
88 "<li>Store your baskets on a server to share them between two computers.<br>"
89 "In this case, mount the shared-folder to the local file system and ask %2 to use that mount point.<br>"
90 "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>"
91 "</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>")
92 .arg(kapp->aboutData()->programName())
93 .arg(kapp->aboutData()->programName())
94 .arg(kapp->aboutData()->programName()),
95 folderWidget);
96 folderLayout->addWidget(moveFolder);
97 folderLayout->addWidget(useFolder);
98 folderLayout->addWidget(helpLabel);
99 folderLayout->addStretch();
100 connect( moveFolder, SIGNAL(clicked()), this, SLOT(moveToAnotherFolder()) );
101 connect( useFolder, SIGNAL(clicked()), this, SLOT(useAnotherExistingFolder()) );
103 Q3GroupBox *backupGroup = new Q3GroupBox(1, Qt::Horizontal, i18n("Backups"), page);
104 QWidget *backupWidget = new QWidget(backupGroup);
105 Q3HBoxLayout *backupLayout = new Q3HBoxLayout(backupWidget, 0, spacingHint());
106 QPushButton *backupButton = new QPushButton(i18n("&Backup..."), backupWidget);
107 QPushButton *restoreButton = new QPushButton(i18n("&Restore a Backup..."), backupWidget);
108 m_lastBackup = new QLabel("", backupWidget);
109 backupLayout->addWidget(backupButton);
110 backupLayout->addWidget(restoreButton);
111 backupLayout->addWidget(m_lastBackup);
112 backupLayout->addStretch();
113 connect( backupButton, SIGNAL(clicked()), this, SLOT(backup()) );
114 connect( restoreButton, SIGNAL(clicked()), this, SLOT(restore()) );
116 populateLastBackup();
118 (new QWidget(page))->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
121 BackupDialog::~BackupDialog()
125 void BackupDialog::populateLastBackup()
127 QString lastBackupText = i18n("Last backup: never");
128 if (Settings::lastBackup().isValid())
129 lastBackupText = i18n("Last backup: %1").arg(Settings::lastBackup().toString(Qt::LocalDate));
131 m_lastBackup->setText(lastBackupText);
134 void BackupDialog::moveToAnotherFolder()
136 KUrl selectedURL = KDirSelectDialog::selectDirectory(
137 /*startDir=*/Global::savesFolder(), /*localOnly=*/true, /*parent=*/0,
138 /*caption=*/i18n("Choose a Folder Where to Move Baskets"));
140 if (!selectedURL.isEmpty()) {
141 QString folder = selectedURL.path();
142 QDir dir(folder);
143 // The folder should not exists, or be empty (because KDirSelectDialog will likely create it anyway):
144 if (dir.exists()) {
145 // Get the content of the folder:
146 QStringList content = dir.entryList();
147 if (content.count() > 2) { // "." and ".."
148 int result = KMessageBox::questionYesNo(
150 "<qt>" + i18n("The folder <b>%1</b> is not empty. Do you want to override it?").arg(folder),
151 i18n("Override Folder?"),
152 KGuiItem(i18n("&Override"), "filesave")
154 if (result == KMessageBox::No)
155 return;
157 Tools::deleteRecursively(folder);
159 FormatImporter copier;
160 copier.moveFolder(Global::savesFolder(), folder);
161 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."));
165 void BackupDialog::useAnotherExistingFolder()
167 KUrl selectedURL = KDirSelectDialog::selectDirectory(
168 /*startDir=*/Global::savesFolder(), /*localOnly=*/true, /*parent=*/0,
169 /*caption=*/i18n("Choose an Existing Folder to Store Baskets"));
171 if (!selectedURL.isEmpty()) {
172 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."));
176 void BackupDialog::backup()
178 QDir dir;
180 // Compute a default file name & path (eg. "Baskets_2007-01-31.tar.gz"):
181 KConfig *config = KGlobal::config();
182 config->setGroup("Backups");
183 QString folder = config->readEntry("lastFolder", QDir::homePath()) + "/";
184 QString fileName = i18n("Backup filename (without extension), %1 is the date", "Baskets_%1")
185 .arg(QDate::currentDate().toString(Qt::ISODate));
186 QString url = folder + fileName;
188 // Ask a file name & path to the user:
189 QString filter = "*.tar.gz|" + i18n("Tar Archives Compressed by Gzip") + "\n*|" + i18n("All Files");
190 QString destination = url;
191 for (bool askAgain = true; askAgain; ) {
192 // Ask:
193 destination = KFileDialog::getSaveFileName(destination, filter, 0, i18n("Backup Baskets"));
194 // User canceled?
195 if (destination.isEmpty())
196 return;
197 // File already existing? Ask for overriding:
198 if (dir.exists(destination)) {
199 int result = KMessageBox::questionYesNoCancel(
201 "<qt>" + i18n("The file <b>%1</b> already exists. Do you really want to override it?")
202 .arg(KUrl(destination).fileName()),
203 i18n("Override File?"),
204 KGuiItem(i18n("&Override"), "filesave")
206 if (result == KMessageBox::Cancel)
207 return;
208 else if (result == KMessageBox::Yes)
209 askAgain = false;
210 } else
211 askAgain = false;
214 KProgressDialog dialog(0, 0, i18n("Backup Baskets"), i18n("Backing up baskets. Please wait..."), /*modal=*/true);
215 dialog.setAllowCancel(false);
216 dialog.setAutoClose(true);
217 dialog.show();
218 KProgress *progress = dialog.progressBar();
219 progress->setTotalSteps(0/*Busy/Undefined*/);
220 progress->setProgress(0);
221 progress->setPercentageVisible(false);
223 BackupThread thread(destination, Global::savesFolder());
224 thread.start();
225 while (thread.running()) {
226 progress->advance(1); // Or else, the animation is not played!
227 kapp->processEvents();
228 usleep(300); // Not too long because if the backup process is finished, we wait for nothing
231 Settings::setLastBackup(QDate::currentDate());
232 Settings::saveConfig();
233 populateLastBackup();
236 void BackupDialog::restore()
238 // Get last backup folder:
239 KConfig *config = KGlobal::config();
240 config->setGroup("Backups");
241 QString folder = config->readEntry("lastFolder", QDir::homePath()) + "/";
243 // Ask a file name to the user:
244 QString filter = "*.tar.gz|" + i18n("Tar Archives Compressed by Gzip") + "\n*|" + i18n("All Files");
245 QString path = KFileDialog::getOpenFileName(folder, filter, this, i18n("Open Basket Archive"));
246 if (path.isEmpty()) // User has canceled
247 return;
249 // Before replacing the basket data folder with the backup content, we safely backup the current baskets to the home folder.
250 // 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:
251 QString safetyPath = Backup::newSafetyFolder();
252 FormatImporter copier;
253 copier.moveFolder(Global::savesFolder(), safetyPath);
255 // Add the README file for user to cancel a bad restoration:
256 QString readmePath = safetyPath + i18n("README.txt");
257 QFile file(readmePath);
258 if (file.open(QIODevice::WriteOnly)) {
259 Q3TextStream stream(&file);
260 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"
261 << i18n("If the restoration was a success and you restored what you wanted to restore, you can remove this folder.") + "\n\n"
262 << 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"
263 << i18n("Choose \"Basket\" -> \"Backup & Restore...\" -> \"Use Another Existing Folder...\" and select that folder.") + "\n";
264 file.close();
267 QString message =
268 "<p><nobr>" + i18n("Restoring <b>%1</b>. Please wait...").arg(KUrl(path).fileName()) + "</nobr></p><p>" +
269 i18n("If something goes wrong during the restoration process, read the file <b>%1</b>.").arg(readmePath);
271 KProgressDialog *dialog = new KProgressDialog(0, 0, i18n("Restore Baskets"), message, /*modal=*/true);
272 dialog->setAllowCancel(false);
273 dialog->setAutoClose(true);
274 dialog->show();
275 KProgress *progress = dialog->progressBar();
276 progress->setTotalSteps(0/*Busy/Undefined*/);
277 progress->setProgress(0);
278 progress->setPercentageVisible(false);
280 // Uncompress:
281 RestoreThread thread(path, Global::savesFolder());
282 thread.start();
283 while (thread.running()) {
284 progress->advance(1); // Or else, the animation is not played!
285 kapp->processEvents();
286 usleep(300); // Not too long because if the restore process is finished, we wait for nothing
289 dialog->hide(); // The restore is finished, do not continue to show it while telling the user the application is going to be restarted
290 delete dialog; // If we only hidden it, it reappeared just after having restored a small backup... Very strange.
291 dialog = 0; // This was annoying since it is modal and the "BasKet Note Pads is going to be restarted" message was not reachable.
292 //kapp->processEvents();
294 // Check for errors:
295 if (!thread.success()) {
296 // Restore the old baskets:
297 QDir dir;
298 dir.remove(readmePath);
299 copier.moveFolder(safetyPath, Global::savesFolder());
300 // Tell the user:
301 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"));
302 return;
305 // 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...
306 // 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).
307 // 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.
309 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."));
312 /** class Backup: */
314 QString Backup::binaryPath = "";
316 #include <iostream>
317 #include <kiconloader.h>
319 void Backup::figureOutBinaryPath(const char *argv0, QApplication &app)
322 The application can be launched by two ways:
323 - Globaly (app.applicationFilePath() is good)
324 - In KDevelop or with an absolute path (app.applicationFilePath() is wrong)
325 This function is called at the very start of main() so that the current directory has not been changed yet.
327 Command line (argv[0]) QDir(argv[0]).canonicalPath() app.applicationFilePath()
328 ====================== ============================================= =========================
329 "basket" "" "/opt/kde3/bin/basket"
330 "./src/.libs/basket" "/home/seb/prog/basket/debug/src/.lib/basket" "/opt/kde3/bin/basket"
333 binaryPath = QDir(argv0).canonicalPath();
334 if (binaryPath.isEmpty())
335 binaryPath = app.applicationFilePath();
338 void Backup::setFolderAndRestart(const QString &folder, const QString &message)
340 // Set the folder:
341 Settings::setDataFolder(folder);
342 Settings::saveConfig();
344 // Rassure the user that the application main window disapearition is not a crash, but a normal restart.
345 // This is important for users to trust the application in such a critical phase and understands what's happening:
346 KMessageBox::information(
348 "<qt>" + message.arg(
349 (folder.endsWith("/") ? folder.left(folder.length() - 1) : folder),
350 kapp->aboutData()->programName()),
351 i18n("Restart")
354 // Restart the application:
355 KRun::runCommand(binaryPath, kapp->aboutData()->programName(), kapp->iconName());
356 exit(0);
359 QString Backup::newSafetyFolder()
361 QDir dir;
362 QString fullPath;
364 fullPath = QDir::homePath() + "/" + i18n("Safety folder name before restoring a basket data archive", "Baskets Before Restoration") + "/";
365 if (!dir.exists(fullPath))
366 return fullPath;
368 for (int i = 2; ; ++i) {
369 fullPath = QDir::homePath() + "/" + i18n("Safety folder name before restoring a basket data archive", "Baskets Before Restoration (%1)").arg(i) + "/";
370 if (!dir.exists(fullPath))
371 return fullPath;
374 return "";
377 /** class BackupThread: */
379 BackupThread::BackupThread(const QString &tarFile, const QString &folderToBackup)
380 : m_tarFile(tarFile), m_folderToBackup(folderToBackup)
384 void BackupThread::run()
386 KTar tar(m_tarFile, "application/x-gzip");
387 tar.open(QIODevice::WriteOnly);
388 tar.addLocalDirectory(m_folderToBackup, backupMagicFolder);
389 // KArchive does not add hidden files. Basket description files (".basket") are hidden, we add them manually:
390 QDir dir(m_folderToBackup + "baskets/");
391 QStringList baskets = dir.entryList(QDir::Dirs);
392 for (QStringList::Iterator it = baskets.begin(); it != baskets.end(); ++it) {
393 tar.addLocalFile(
394 m_folderToBackup + "baskets/" + *it + "/.basket",
395 backupMagicFolder + "/baskets/" + *it + "/.basket"
398 // We finished:
399 tar.close();
402 /** class RestoreThread: */
404 RestoreThread::RestoreThread(const QString &tarFile, const QString &destFolder)
405 : m_tarFile(tarFile), m_destFolder(destFolder)
409 void RestoreThread::run()
411 m_success = false;
412 KTar tar(m_tarFile, "application/x-gzip");
413 tar.open(QIODevice::ReadOnly);
414 if (tar.isOpened()) {
415 const KArchiveDirectory *directory = tar.directory();
416 if (directory->entries().contains(backupMagicFolder)) {
417 const KArchiveEntry *entry = directory->entry(backupMagicFolder);
418 if (entry->isDirectory()) {
419 ((const KArchiveDirectory*) entry)->copyTo(m_destFolder);
420 m_success = true;
423 tar.close();
427 #include "backup.moc"