• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • 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 + QLatin1String("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 completely
00118     // only if we 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             // failed to set user and group => try to restore group only.
00124             fchown(tempFile.handle(), -1, fi.groupId());
00125         }
00126 
00127         tempFile.setPermissions(fi.permissions());
00128     }
00129     else {
00130         mode_t umsk = KGlobal::umask();
00131         fchmod(tempFile.handle(), 0666&(~umsk));
00132     }
00133 
00134     //Open oursleves with the temporary file
00135     QFile::setFileName(tempFile.fileName());
00136     if (!QFile::open(flags)) {
00137         tempFile.setAutoRemove(true);
00138         return false;
00139     }
00140 
00141     d->tempFileName = tempFile.fileName();
00142     d->error=QFile::NoError;
00143     d->errorString.clear();
00144     return true;
00145 }
00146 
00147 void KSaveFile::setFileName(const QString &filename)
00148 {
00149     d->realFileName = filename;
00150 
00151     // make absolute if needed
00152     if ( QDir::isRelativePath( filename ) ) {
00153         d->realFileName = QDir::current().absoluteFilePath( filename );
00154     }
00155 
00156     // follow symbolic link, if any
00157     d->realFileName = KStandardDirs::realFilePath( d->realFileName );
00158 
00159     return;
00160 }
00161 
00162 QFile::FileError KSaveFile::error() const
00163 {
00164     if ( d->error != QFile::NoError ) {
00165         return d->error;
00166     } else {
00167         return QFile::error();
00168     }
00169 }
00170 
00171 QString KSaveFile::errorString() const
00172 {
00173     if ( !d->errorString.isEmpty() ) {
00174         return d->errorString;
00175     } else {
00176         return QFile::errorString();
00177     }
00178 }
00179 
00180 QString KSaveFile::fileName() const
00181 {
00182     return d->realFileName;
00183 }
00184 
00185 void KSaveFile::abort()
00186 {
00187     close();
00188     QFile::remove(d->tempFileName); //non-static QFile::remove() does not work.
00189     d->wasFinalized = true;
00190 }
00191 
00192 #ifdef HAVE_FDATASYNC
00193 #  define FDATASYNC fdatasync
00194 #else
00195 #  define FDATASYNC fsync
00196 #endif
00197 
00198 bool KSaveFile::finalize()
00199 {
00200     bool success = false;
00201 
00202     if ( !d->wasFinalized ) {
00203 
00204 #ifdef Q_OS_UNIX
00205         static int extraSync = -1;
00206         if (extraSync < 0)
00207             extraSync = getenv("KDE_EXTRA_FSYNC") != 0 ? 1 : 0;
00208         if (extraSync) {
00209             if (flush()) {
00210                 forever {
00211                     if (!FDATASYNC(handle()))
00212                         break;
00213                     if (errno != EINTR) {
00214                         d->error = QFile::WriteError;
00215                         d->errorString = i18n("Synchronization to disk failed");
00216                         break;
00217                     }
00218                 }
00219             }
00220         }
00221 #endif
00222 
00223         close();
00224 
00225         if( error() != NoError ) {
00226             QFile::remove(d->tempFileName);
00227         }
00228         //Qt does not allow us to atomically overwrite an existing file,
00229         //so if the target file already exists, there is no way to change it
00230         //to the temp file without creating a small race condition. So we use
00231         //the standard rename call instead, which will do the copy without the
00232         //race condition.
00233         else if (0 == KDE::rename(d->tempFileName,d->realFileName)) {
00234             d->error=QFile::NoError;
00235             d->errorString.clear();
00236             success = true;
00237         } else {
00238             d->error=QFile::OpenError;
00239             d->errorString=i18n("Error during rename.");
00240             QFile::remove(d->tempFileName);
00241         }
00242 
00243         d->wasFinalized = true;
00244     }
00245 
00246     return success;
00247 }
00248 
00249 #undef FDATASYNC
00250 
00251 bool KSaveFile::backupFile( const QString& qFilename, const QString& backupDir )
00252 {
00253     // get backup type from config, by default use "simple"
00254     // get extension from config, by default use "~"
00255     // get max number of backups from config, by default set to 10
00256 
00257     KConfigGroup g(KGlobal::config(), "Backups"); // look in the Backups section
00258     QString type = g.readEntry( "Type", "simple" );
00259     QString extension = g.readEntry( "Extension", "~" );
00260     QString message = g.readEntry( "Message", "Automated KDE Commit" );
00261     int maxnum = g.readEntry( "MaxBackups", 10 );
00262     if ( type.toLower() == QLatin1String("numbered") ) {
00263         return( numberedBackupFile( qFilename, backupDir, extension, maxnum ) );
00264     } else if ( type.toLower() == QLatin1String("rcs") ) {
00265         return( rcsBackupFile( qFilename, backupDir, message ) );
00266     } else {
00267         return( simpleBackupFile( qFilename, backupDir, extension ) );
00268     }
00269 }
00270 
00271 bool KSaveFile::simpleBackupFile( const QString& qFilename,
00272                                   const QString& backupDir,
00273                                   const QString& backupExtension )
00274 {
00275     QString backupFileName = qFilename + backupExtension;
00276 
00277     if ( !backupDir.isEmpty() ) {
00278         QFileInfo fileInfo ( qFilename );
00279         backupFileName = backupDir + QLatin1Char('/') + fileInfo.fileName() + backupExtension;
00280     }
00281 
00282 //    kDebug(180) << "KSaveFile copying " << qFilename << " to " << backupFileName;
00283     QFile::remove(backupFileName);
00284     return QFile::copy(qFilename, backupFileName);
00285 }
00286 
00287 bool KSaveFile::rcsBackupFile( const QString& qFilename,
00288                                const QString& backupDir,
00289                                const QString& backupMessage )
00290 {
00291     QFileInfo fileInfo ( qFilename );
00292 
00293     QString qBackupFilename;
00294     if ( backupDir.isEmpty() ) {
00295         qBackupFilename = qFilename;
00296     } else {
00297         qBackupFilename = backupDir + fileInfo.fileName();
00298     }
00299     qBackupFilename += QString::fromLatin1( ",v" );
00300 
00301     // If backupDir is specified, copy qFilename to the
00302     // backupDir and perform the commit there, unlinking
00303     // backupDir/qFilename when finished.
00304     if ( !backupDir.isEmpty() )
00305     {
00306         if ( !QFile::copy(qFilename, backupDir + fileInfo.fileName()) ) {
00307             return false;
00308         }
00309         fileInfo.setFile(backupDir + QLatin1Char('/') + fileInfo.fileName());
00310     }
00311 
00312     QString cipath = KStandardDirs::findExe(QString::fromLatin1("ci"));
00313     QString copath = KStandardDirs::findExe(QString::fromLatin1("co"));
00314     QString rcspath = KStandardDirs::findExe(QString::fromLatin1("rcs"));
00315     if ( cipath.isEmpty() || copath.isEmpty() || rcspath.isEmpty() )
00316         return false;
00317 
00318     // Check in the file unlocked with 'ci'
00319     QProcess ci;
00320     if ( !backupDir.isEmpty() )
00321         ci.setWorkingDirectory( backupDir );
00322     ci.start( cipath, QStringList() << QString::fromLatin1("-u") << fileInfo.filePath() );
00323     if ( !ci.waitForStarted() )
00324         return false;
00325     ci.write( backupMessage.toLatin1() );
00326     ci.write(".");
00327     ci.closeWriteChannel();
00328     if( !ci.waitForFinished() )
00329         return false;
00330 
00331     // Use 'rcs' to unset strict locking
00332     QProcess rcs;
00333     if ( !backupDir.isEmpty() )
00334         rcs.setWorkingDirectory( backupDir );
00335     rcs.start( rcspath, QStringList() << QString::fromLatin1("-U") << qBackupFilename );
00336     if ( !rcs.waitForFinished() )
00337         return false;
00338 
00339     // Use 'co' to checkout the current revision and restore permissions
00340     QProcess co;
00341     if ( !backupDir.isEmpty() )
00342         co.setWorkingDirectory( backupDir );
00343     co.start( copath, QStringList() << qBackupFilename );
00344     if ( !co.waitForFinished() )
00345         return false;
00346 
00347     if ( !backupDir.isEmpty() ) {
00348         return QFile::remove( fileInfo.filePath() );
00349     } else {
00350         return true;
00351     }
00352 }
00353 
00354 bool KSaveFile::numberedBackupFile( const QString& qFilename,
00355                                     const QString& backupDir,
00356                                     const QString& backupExtension,
00357                                     const uint maxBackups )
00358 {
00359     QFileInfo fileInfo ( qFilename );
00360 
00361     // The backup file name template.
00362     QString sTemplate;
00363     if ( backupDir.isEmpty() ) {
00364         sTemplate = qFilename + QLatin1String(".%1") + backupExtension;
00365     } else {
00366         sTemplate = backupDir + QLatin1Char('/') + fileInfo.fileName() + QLatin1String(".%1") + backupExtension;
00367     }
00368 
00369     // First, search backupDir for numbered backup files to remove.
00370     // Remove all with number 'maxBackups' and greater.
00371     QDir d = backupDir.isEmpty() ? fileInfo.dir() : backupDir;
00372     d.setFilter( QDir::Files | QDir::Hidden | QDir::NoSymLinks );
00373     const QStringList nameFilters = QStringList( fileInfo.fileName() + QLatin1String(".*") + backupExtension );
00374     d.setNameFilters( nameFilters );
00375     d.setSorting( QDir::Name );
00376 
00377     uint maxBackupFound = 0;
00378     foreach ( const QFileInfo &fi, d.entryInfoList() ) {
00379         if ( fi.fileName().endsWith( backupExtension ) ) {
00380             // sTemp holds the file name, without the ending backupExtension
00381             QString sTemp = fi.fileName();
00382             sTemp.truncate( fi.fileName().length()-backupExtension.length() );
00383             // compute the backup number
00384             int idex = sTemp.lastIndexOf( QLatin1Char('.') );
00385             if ( idex > 0 ) {
00386                 bool ok;
00387                 uint num = sTemp.mid( idex+1 ).toUInt( &ok );
00388                 if ( ok ) {
00389                     if ( num >= maxBackups ) {
00390                         QFile::remove( fi.filePath() );
00391                     } else {
00392                         maxBackupFound = qMax( maxBackupFound, num );
00393                     }
00394                 }
00395             }
00396         }
00397     }
00398 
00399     // Next, rename max-1 to max, max-2 to max-1, etc.
00400     QString to=sTemplate.arg( maxBackupFound+1 );
00401     for ( int i=maxBackupFound; i>0; i-- ) {
00402         QString from = sTemplate.arg( i );
00403 //        kDebug(180) << "KSaveFile renaming " << from << " to " << to;
00404         QFile::rename( from, to );
00405         to = from;
00406     }
00407 
00408     // Finally create most recent backup by copying the file to backup number 1.
00409 //    kDebug(180) << "KSaveFile copying " << qFilename << " to " << sTemplate.arg(1);
00410     return QFile::copy(qFilename, sTemplate.arg(1));
00411 }
00412 

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
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
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