• Skip to content
  • Skip to link menu
KDE 4.3 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KDECore

ksavefile.cpp

Go to the documentation of this file.
00001 /* kate: tab-indents off; replace-tabs on; tab-width 4; remove-trailing-space on; encoding utf-8;*/
00002 /*
00003   This file is part of the KDE libraries
00004   Copyright 1999 Waldo Bastian <bastian@kde.org>
00005   Copyright 2006 Allen Winter <winter@kde.org>
00006   Copyright 2006 Gregory S. Hayes <syncomm@kde.org>
00007   Copyright 2006 Jaison Lee <lee.jaison@gmail.com>
00008 
00009   This library is free software; you can redistribute it and/or
00010   modify it under the terms of the GNU Library General Public
00011   License version 2 as published by the Free Software Foundation.
00012 
00013   This library is distributed in the hope that it will be useful,
00014   but WITHOUT ANY WARRANTY; without even the implied warranty of
00015   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016   Library General Public License for more details.
00017 
00018   You should have received a copy of the GNU Library General Public License
00019   along with this library; see the file COPYING.LIB.  If not, write to
00020   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00021   Boston, MA 02110-1301, USA.
00022 */
00023 
00024 #include "ksavefile.h"
00025 
00026 #include <config.h>
00027 
00028 #include <QtCore/QDir>
00029 #include <QProcess>
00030 #include <QTemporaryFile>
00031 
00032 #include <kconfig.h>
00033 #include <kde_file.h>
00034 #include <klocale.h>
00035 #include <kstandarddirs.h>
00036 #include <kconfiggroup.h>
00037 #include <kcomponentdata.h>
00038 
00039 #include <stdlib.h>
00040 #include <errno.h>
00041 
00042 class KSaveFile::Private
00043 {
00044 public:
00045     QString realFileName; //The name of the end-result file
00046     QString tempFileName; //The name of the temp file we are using
00047     
00048     QFile::FileError error;
00049     QString errorString;
00050     bool wasFinalized;
00051     KComponentData componentData;
00052 
00053     Private(const KComponentData &c)
00054         : componentData(c)
00055     {
00056         error = QFile::NoError;
00057         wasFinalized = false;
00058     }
00059 };
00060 
00061 KSaveFile::KSaveFile()
00062  : d(new Private(KGlobal::mainComponent()))
00063 {
00064 }
00065 
00066 KSaveFile::KSaveFile(const QString &filename, const KComponentData &componentData)
00067  : d(new Private(componentData))
00068 {
00069     KSaveFile::setFileName(filename);
00070 }
00071 
00072 KSaveFile::~KSaveFile()
00073 {
00074     if (!d->wasFinalized)
00075         finalize();
00076 
00077     delete d;
00078 }
00079 
00080 bool KSaveFile::open(OpenMode flags)
00081 {
00082     if ( d->realFileName.isNull() ) {
00083         d->error=QFile::OpenError;
00084         d->errorString=i18n("No target filename has been given.");
00085         return false;
00086     }
00087 
00088     if ( !d->tempFileName.isNull() ) {
00089 #if 0 // do not set an error here, this open() fails, but the file itself is without errors
00090         d->error=QFile::OpenError;
00091         d->errorString=i18n("Already opened.");
00092 #endif
00093         return false;
00094     }
00095 
00096     // we only check here if the directory can be written to
00097     // the actual filename isn't written to, but replaced later
00098     // with the contents of our tempfile
00099     if (!KStandardDirs::checkAccess(d->realFileName, W_OK)) {
00100         d->error=QFile::PermissionsError;
00101         d->errorString=i18n("Insufficient permissions in target directory.");
00102         return false;
00103     }
00104 
00105     //Create our temporary file
00106     QTemporaryFile tempFile;
00107     tempFile.setAutoRemove(false);
00108     tempFile.setFileTemplate(d->realFileName + "XXXXXX.new");
00109     if (!tempFile.open()) {
00110         d->error=QFile::OpenError;
00111         d->errorString=i18n("Unable to open temporary file.");
00112         return false;
00113     }
00114     
00115     // if we're overwriting an existing file, ensure temp file's
00116     // permissions are the same as existing file so the existing
00117     // file's permissions are preserved. this will succeed only if we
00118     // are the same owner and group - or allmighty root.
00119     QFileInfo fi ( d->realFileName );
00120     if (fi.exists()) {
00121         //Qt apparently has no way to change owner/group of file :(
00122         if (!fchown(tempFile.handle(), fi.ownerId(), fi.groupId()))
00123             tempFile.setPermissions(fi.permissions());
00124     }
00125     else {
00126         mode_t umsk = KGlobal::umask();
00127         fchmod(tempFile.handle(), 0666&(~umsk));
00128     }
00129 
00130     //Open oursleves with the temporary file
00131     QFile::setFileName(tempFile.fileName());
00132     if (!QFile::open(flags)) {
00133         tempFile.setAutoRemove(true);
00134         return false;
00135     }
00136 
00137     d->tempFileName = tempFile.fileName();
00138     d->error=QFile::NoError;
00139     d->errorString.clear();
00140     return true;
00141 }
00142 
00143 void KSaveFile::setFileName(const QString &filename)
00144 {
00145     d->realFileName = filename;
00146     
00147     // make absolute if needed
00148     if ( QDir::isRelativePath( filename ) ) {
00149         d->realFileName = QDir::current().absoluteFilePath( filename );
00150     }
00151 
00152     // follow symbolic link, if any
00153     d->realFileName = KStandardDirs::realFilePath( d->realFileName );
00154 
00155     return;
00156 }
00157 
00158 QFile::FileError KSaveFile::error() const
00159 {
00160     if ( d->error != QFile::NoError ) {
00161         return d->error;
00162     } else {
00163         return QFile::error();
00164     }
00165 }
00166 
00167 QString KSaveFile::errorString() const
00168 {
00169     if ( !d->errorString.isEmpty() ) {
00170         return d->errorString;
00171     } else {
00172         return QFile::errorString();
00173     }
00174 }
00175 
00176 QString KSaveFile::fileName() const
00177 {
00178     return d->realFileName;
00179 }
00180 
00181 void KSaveFile::abort()
00182 {
00183     close();
00184     QFile::remove(d->tempFileName); //non-static QFile::remove() does not work. 
00185     d->wasFinalized = true;
00186 }
00187 
00188 #ifdef HAVE_FDATASYNC
00189 #  define FDATASYNC fdatasync
00190 #else
00191 #  define FDATASYNC fsync
00192 #endif
00193 
00194 bool KSaveFile::finalize()
00195 {
00196     bool success = false;
00197 
00198     if ( !d->wasFinalized ) {
00199 
00200 #ifdef Q_OS_UNIX
00201         static int extraSync = -1;
00202         if (extraSync < 0)
00203             extraSync = getenv("KDE_EXTRA_FSYNC") != 0 ? 1 : 0;
00204         if (extraSync) {
00205             if (flush()) {
00206                 forever {
00207                     if (!FDATASYNC(handle()))
00208                         break;
00209                     if (errno != EINTR) {
00210                         d->error = QFile::WriteError;
00211                         d->errorString = i18n("Synchronization to disk failed");
00212                         break;
00213                     }
00214                 }
00215             }
00216         }
00217 #endif
00218 
00219         close();
00220         
00221         if( error() != NoError ) {
00222             QFile::remove(d->tempFileName);
00223         }
00224         //Qt does not allow us to atomically overwrite an existing file,
00225         //so if the target file already exists, there is no way to change it
00226         //to the temp file without creating a small race condition. So we use
00227         //the standard rename call instead, which will do the copy without the
00228         //race condition.
00229         else if (0 == KDE::rename(d->tempFileName,d->realFileName)) {
00230             d->error=QFile::NoError;
00231             d->errorString.clear();
00232             success = true;
00233         } else {
00234             d->error=QFile::OpenError;
00235             d->errorString=i18n("Error during rename.");
00236             QFile::remove(d->tempFileName);
00237         }
00238 
00239         d->wasFinalized = true;
00240     }
00241 
00242     return success;
00243 }
00244 
00245 #undef FDATASYNC
00246 
00247 bool KSaveFile::backupFile( const QString& qFilename, const QString& backupDir )
00248 {
00249     // get backup type from config, by default use "simple"
00250     // get extension from config, by default use "~"
00251     // get max number of backups from config, by default set to 10
00252 
00253     KConfigGroup g(KGlobal::config(), "Backups"); // look in the Backups section
00254     QString type = g.readEntry( "Type", "simple" );
00255     QString extension = g.readEntry( "Extension", "~" );
00256     QString message = g.readEntry( "Message", "Automated KDE Commit" );
00257     int maxnum = g.readEntry( "MaxBackups", 10 );
00258     if ( type.toLower() == "numbered" ) {
00259         return( numberedBackupFile( qFilename, backupDir, extension, maxnum ) );
00260     } else if ( type.toLower() == "rcs" ) {
00261         return( rcsBackupFile( qFilename, backupDir, message ) );
00262     } else {
00263         return( simpleBackupFile( qFilename, backupDir, extension ) );
00264     }
00265 }
00266 
00267 bool KSaveFile::simpleBackupFile( const QString& qFilename,
00268                                   const QString& backupDir,
00269                                   const QString& backupExtension )
00270 {
00271     QString backupFileName = qFilename + backupExtension;
00272     
00273     if ( !backupDir.isEmpty() ) {
00274         QFileInfo fileInfo ( qFilename );
00275         backupFileName = backupDir + '/' + fileInfo.fileName() + backupExtension;
00276     }
00277 
00278 //    kDebug(180) << "KSaveFile copying " << qFilename << " to " << backupFileName;
00279     QFile::remove(backupFileName);
00280     return QFile::copy(qFilename, backupFileName);
00281 }
00282 
00283 bool KSaveFile::rcsBackupFile( const QString& qFilename,
00284                                const QString& backupDir,
00285                                const QString& backupMessage )
00286 {
00287     QFileInfo fileInfo ( qFilename );
00288     
00289     QString qBackupFilename;
00290     if ( backupDir.isEmpty() ) {
00291         qBackupFilename = qFilename;
00292     } else {
00293         qBackupFilename = backupDir + fileInfo.fileName();
00294     }
00295     qBackupFilename += QString::fromLatin1( ",v" );
00296 
00297     // If backupDir is specified, copy qFilename to the
00298     // backupDir and perform the commit there, unlinking
00299     // backupDir/qFilename when finished.
00300     if ( !backupDir.isEmpty() )
00301     {
00302         if ( !QFile::copy(qFilename, backupDir + fileInfo.fileName()) ) {
00303             return false;
00304         }
00305         fileInfo.setFile(backupDir + '/' + fileInfo.fileName());
00306     }
00307     
00308     QString cipath = KStandardDirs::findExe("ci");
00309     QString copath = KStandardDirs::findExe("co");
00310     QString rcspath = KStandardDirs::findExe("rcs");
00311     if ( cipath.isEmpty() || copath.isEmpty() || rcspath.isEmpty() )
00312         return false;
00313     
00314     // Check in the file unlocked with 'ci'
00315     QProcess ci;
00316     if ( !backupDir.isEmpty() )
00317         ci.setWorkingDirectory( backupDir );
00318     ci.start( cipath, QStringList() << "-u" << fileInfo.filePath() );
00319     if ( !ci.waitForStarted() )
00320         return false;
00321     ci.write( backupMessage.toLatin1() );
00322     ci.write(".");
00323     ci.closeWriteChannel();
00324     if( !ci.waitForFinished() )
00325         return false;
00326 
00327     // Use 'rcs' to unset strict locking
00328     QProcess rcs;
00329     if ( !backupDir.isEmpty() )
00330         rcs.setWorkingDirectory( backupDir );
00331     rcs.start( rcspath, QStringList() << "-U" << qBackupFilename );
00332     if ( !rcs.waitForFinished() )
00333         return false;
00334 
00335     // Use 'co' to checkout the current revision and restore permissions
00336     QProcess co;
00337     if ( !backupDir.isEmpty() )
00338         co.setWorkingDirectory( backupDir );
00339     co.start( copath, QStringList() << qBackupFilename );
00340     if ( !co.waitForFinished() )
00341         return false;
00342 
00343     if ( !backupDir.isEmpty() ) {
00344         return QFile::remove( fileInfo.filePath() );
00345     } else {
00346         return true;
00347     }
00348 }
00349 
00350 bool KSaveFile::numberedBackupFile( const QString& qFilename,
00351                                     const QString& backupDir,
00352                                     const QString& backupExtension,
00353                                     const uint maxBackups )
00354 {
00355     QFileInfo fileInfo ( qFilename );
00356     
00357     // The backup file name template.
00358     QString sTemplate;
00359     if ( backupDir.isEmpty() ) {
00360         sTemplate = qFilename + ".%1" + backupExtension;
00361     } else {
00362         sTemplate = backupDir + '/' + fileInfo.fileName() + ".%1" + backupExtension;
00363     }
00364 
00365     // First, search backupDir for numbered backup files to remove.
00366     // Remove all with number 'maxBackups' and greater.
00367     QDir d = backupDir.isEmpty() ? fileInfo.dir() : backupDir;
00368     d.setFilter( QDir::Files | QDir::Hidden | QDir::NoSymLinks );
00369     QStringList nameFilters = QStringList( fileInfo.fileName() + ".*" + backupExtension );
00370     d.setNameFilters( nameFilters );
00371     d.setSorting( QDir::Name );
00372 
00373     uint maxBackupFound = 0;
00374     foreach ( const QFileInfo &fi, d.entryInfoList() ) {
00375         if ( fi.fileName().endsWith( backupExtension ) ) {
00376             // sTemp holds the file name, without the ending backupExtension
00377             QString sTemp = fi.fileName();
00378             sTemp.truncate( fi.fileName().length()-backupExtension.length() );
00379             // compute the backup number
00380             int idex = sTemp.lastIndexOf( '.' );
00381             if ( idex > 0 ) {
00382                 bool ok;
00383                 uint num = sTemp.mid( idex+1 ).toUInt( &ok );
00384                 if ( ok ) {
00385                     if ( num >= maxBackups ) {
00386                         QFile::remove( fi.filePath() );
00387                     } else {
00388                         maxBackupFound = qMax( maxBackupFound, num );
00389                     }
00390                 }
00391             }
00392         }
00393     }
00394 
00395     // Next, rename max-1 to max, max-2 to max-1, etc.
00396     QString to=sTemplate.arg( maxBackupFound+1 );
00397     for ( int i=maxBackupFound; i>0; i-- ) {
00398         QString from = sTemplate.arg( i );
00399 //        kDebug(180) << "KSaveFile renaming " << from << " to " << to;
00400         QFile::rename( from, to );
00401         to = from;
00402     }
00403 
00404     // Finally create most recent backup by copying the file to backup number 1.
00405 //    kDebug(180) << "KSaveFile copying " << qFilename << " to " << sTemplate.arg(1);
00406     return QFile::copy(qFilename, sTemplate.arg(1));
00407 }
00408 

KDECore

Skip menu "KDECore"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal