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