File indexing completed on 2024-05-12 16:20:57

0001 /*
0002  *  Copyright (c) 2008-2009 Jeff Mitchell <mitchell@kde.org>
0003  *
0004  *  This program is free software; you can redistribute it and/or modify
0005  *  it under the terms of the GNU General Public License as published by
0006  *  the Free Software Foundation; either version 2 of the License, or
0007  *  (at your option) any later version.
0008  *
0009  *  This program is distributed in the hope that it will be useful,
0010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012  *  GNU General Public License for more details.
0013  *
0014  *  You should have received a copy of the GNU General Public License
0015  *  along with this program; if not, write to the Free Software
0016  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0017  */
0018 
0019 #include "SafeFileSaver.h"
0020 
0021 #include <QtDebug>
0022 #include <QCryptographicHash>
0023 #include <QFile>
0024 #include <QFileInfo>
0025 #include <QRandomGenerator>
0026 #include <QString>
0027 
0028 #ifdef Q_WS_WIN
0029 #include <process.h>
0030 #else
0031 #include <unistd.h>
0032 #include <sys/types.h>
0033 #endif
0034 
0035 SafeFileSaver::SafeFileSaver( const QString &origPath )
0036     : m_origPath( origPath )
0037     , m_tempSavePath()
0038     , m_origRenamedSavePath()
0039     , m_cleanupNeeded( false )
0040     , m_verbose( false )
0041     , m_prefix( QStringLiteral("safefilesaver") )
0042 {
0043 }
0044 
0045 SafeFileSaver::~SafeFileSaver()
0046 {
0047     if( m_cleanupNeeded )
0048         cleanupSave();
0049 }
0050 
0051 QString
0052 SafeFileSaver::prepareToSave()
0053 {
0054     if( m_verbose )
0055         qDebug() << "prepareToSave start";
0056     m_cleanupNeeded = true;
0057     QCryptographicHash md5sum( QCryptographicHash::Md5 ); 
0058 
0059     QString pid;
0060 #ifdef Q_WS_WIN
0061     pid.setNum( _getpid() );
0062 #else
0063     pid.setNum( getpid() );
0064 #endif
0065 
0066     int length = 8;
0067     //The following snippet of code is copied from kdelibs, and is copyright Matthias Kalle Dalheimer, Charles Samuels, Joseph Wenninger, and maybe more
0068     QString str; str.resize( length );
0069     int i = 0;
0070     while( length-- )
0071     {
0072         int r = QRandomGenerator::global()->generate() % 62;
0073         r+=48;
0074         if( r > 57 ) r+=7;
0075         if( r > 90 ) r+=6;
0076         str[i++] = char( r );
0077     }
0078 
0079     QString randomString = str;
0080 
0081     m_tempSavePath = m_origPath + QLatin1Char('.') + m_prefix + "temp.pid-" + pid + ".random-" + randomString + QLatin1Char('.') + QFileInfo( m_origPath ).suffix();
0082     m_origRenamedSavePath = m_origPath + QLatin1Char('.') + m_prefix + "original.pid-" + pid + ".random-" + randomString + QLatin1Char('.') + QFileInfo( m_origPath ).suffix();
0083 
0084 
0085     if( m_verbose )
0086         qDebug() << "Copying original file " << m_origPath << " to copy at " << m_tempSavePath << " and caluclating MD5";
0087 
0088     if( !QFile::copy( m_origPath, m_tempSavePath ) )
0089     {
0090         if( m_verbose )
0091             qDebug() << "Could not copy the file.  Check that you have sufficient permissions/disk space "
0092                    "and that the destination path does not already exist!";
0093         return QString();
0094     }
0095 
0096     QFile tempFile( m_tempSavePath );
0097     if( !tempFile.open( QIODevice::ReadOnly | QIODevice::Unbuffered ) )
0098     {
0099         if( m_verbose )
0100             qDebug() << "Could not open temp file for MD5 calculation!";
0101         return QString();
0102     }
0103 
0104     md5sum.addData( tempFile.readLine() );
0105     m_tempSaveDigest = md5sum.result().toHex();
0106 
0107     tempFile.close();
0108 
0109     //By this point, we have the following:
0110     //The original file is copied at path m_tempSavePath
0111     //We have generated what will be the filename to rename the original to in m_origRenamedSavePath
0112     //We have successfully copied the original file to the temp location
0113     //We've calculated the md5sum of the original file
0114 
0115     if( m_verbose )
0116         qDebug() << "MD5 sum of temp file: " << m_tempSaveDigest;
0117 
0118     //Now, we have a MD5 sum of the original file at the time of copying saved in m_tempSaveDigest
0119 
0120     return m_tempSavePath;
0121 }
0122 
0123 bool
0124 SafeFileSaver::doSave()
0125 {
0126     if( m_verbose )
0127         qDebug() << "doSave start";
0128     //TODO: much commenting needed.  For now this pretty much follows algorithm laid out in bug 131353,
0129     //but isn't useable since I need to find a good way to switch the file path with taglib, or a good way
0130     //to get all the metadata copied over.
0131 
0132     m_cleanupNeeded = true;
0133 
0134     QCryptographicHash md5sum( QCryptographicHash::Md5 ); 
0135 
0136     QString origRenamedDigest;
0137 
0138     if( m_tempSavePath.isEmpty() || m_tempSaveDigest.isEmpty() || m_origRenamedSavePath.isEmpty() )
0139     {
0140         if( m_verbose)
0141             qDebug() << "You must run prepareToSave() and it must return successfully before calling doSave()!";
0142         return false;
0143     }
0144 
0145     if( m_verbose )
0146         qDebug() << "Renaming original file to temporary name " << m_origRenamedSavePath;
0147 
0148     if( !QFile::rename( m_origPath, m_origRenamedSavePath ) )
0149     {
0150         if( m_verbose )
0151             qDebug() << "Could not move original!";
0152         failRemoveCopy( false );
0153         return false;
0154     }
0155 
0156     if( m_verbose )
0157         qDebug() << "Calculating MD5 of " << m_origRenamedSavePath;
0158 
0159     QFile origRenamedFile( m_origRenamedSavePath );
0160     if( !origRenamedFile.open( QIODevice::ReadOnly | QIODevice::Unbuffered ) )
0161     {
0162         if( m_verbose )
0163             qDebug() << "Could not open temporary file!";
0164         failRemoveCopy( true );
0165         return false;
0166     }
0167 
0168     md5sum.addData( origRenamedFile.readLine() );
0169     origRenamedDigest = md5sum.result().toHex();
0170     origRenamedFile.close();
0171 
0172     if( m_verbose )
0173         qDebug() << "md5sum of original renamed file: " << origRenamedDigest;
0174 
0175     if( origRenamedDigest != m_tempSaveDigest )
0176     {
0177         if( m_verbose )
0178             qDebug() << "Original checksum did not match current checksum!";
0179         failRemoveCopy( true );
0180         return false;
0181     }
0182 
0183     if( m_verbose )
0184         qDebug() << "Renaming temp file to original's filename";
0185 
0186     if( !QFile::rename( m_tempSavePath, m_origPath ) )
0187     {
0188         if( m_verbose )
0189             qDebug() << "Could not rename new file to original!";
0190         failRemoveCopy( true );
0191         return false;
0192     }
0193 
0194     if( m_verbose )
0195         qDebug() << "Deleting original";
0196 
0197     if( !QFile::remove( m_origRenamedSavePath ) )
0198     {
0199         if( m_verbose )
0200             qDebug() << "Could not delete the original file!";
0201         return false;
0202     }
0203 
0204     if( m_verbose )
0205         qDebug() << "Save done, returning true!";
0206 
0207     return true;
0208 }
0209 
0210 void
0211 SafeFileSaver::failRemoveCopy( bool revert )
0212 {
0213     if( m_verbose )
0214         qDebug() << "failRemoveCopy start";
0215     if( !QFile::remove( m_tempSavePath ) )
0216     {
0217         if( m_verbose )
0218             qDebug() << "Could not delete the temporary file!";
0219     }
0220 
0221     if( !revert )
0222         return;
0223 
0224     if( m_verbose )
0225         qDebug() << "Reverting original file to original filename!";
0226     if( !QFile::rename( m_origRenamedSavePath, m_origPath ) )
0227     {
0228         if( m_verbose )
0229             qDebug() << "Could not revert file to original filename!";
0230     }
0231 }
0232 
0233 bool
0234 SafeFileSaver::cleanupSave()
0235 {
0236     if( m_verbose )
0237         qDebug() << "cleanupSave start";
0238     bool dirty = false;
0239 
0240     if( !m_tempSavePath.isEmpty() && QFile::exists( m_tempSavePath ) )
0241     {
0242         if( !QFile::remove( m_tempSavePath ) )
0243         {
0244             dirty = true;
0245             if( m_verbose )
0246                 qDebug() << "Could not delete the temporary file!";
0247         }
0248     }
0249 
0250     m_tempSavePath.clear();
0251     m_origRenamedSavePath.clear();
0252     m_tempSaveDigest.clear();
0253 
0254     m_cleanupNeeded = false;
0255     return !dirty;
0256 }
0257