/***************************************************************************
* Copyright ( C ) 2003 by S <EFBFBD> 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? " ) ,
Bring filenew, fileopen, fileprint, filequickprint, filesave, filesaveas, fileclose, editclear, editcopy, editcut, editdelete, editpaste, folder_new, and gohome icons into XDG compliance
10 years ago
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? " ) ,
Bring filenew, fileopen, fileprint, filequickprint, filesave, filesaveas, fileclose, editclear, editcopy, editcut, editdelete, editpaste, folder_new, and gohome icons into XDG compliance
10 years ago
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"