// Jeff Mitchell , (C) 2006 // License: GNU General Public License V2 #define DEBUG_PREFIX "MetaBundleSaver" #include #include #include #include #include #include #include #include #include "amarok.h" #include "amarokconfig.h" #include "collectiondb.h" #include "debug.h" #include "metabundlesaver.h" #include "scancontroller.h" #include #include #include #include #include #include #include #include #include //decodePath() #include #include #include #include #include "metabundle.h" MetaBundleSaver::MetaBundleSaver( MetaBundle *bundle ) : TQObject() , m_bundle( bundle ) , m_tempSavePath( TQString() ) , m_origRenamedSavePath( TQString() ) , m_tempSaveDigest( 0 ) , m_saveFileref( 0 ) , m_maxlen( 8192 ) , m_cleanupNeeded( false ) { DEBUG_BLOCK } MetaBundleSaver::~MetaBundleSaver() { DEBUG_BLOCK if( m_cleanupNeeded ) cleanupSave(); } TagLib::FileRef * MetaBundleSaver::prepareToSave() { DEBUG_BLOCK m_cleanupNeeded = true; KMD5 md5sum( 0, 0 ); const KURL origPath = m_bundle->url(); char hostbuf[32]; hostbuf[0] = '\0'; int hostname = gethostname( hostbuf, 32 ); hostbuf[31] = '\0'; if( hostname != 0 ) { debug() << "Could not determine hostname!" << endl; return 0; } TQString pid; TQString randomString = m_bundle->getRandomString( 8, true ); m_tempSavePath = origPath.path() + ".amaroktemp.host-" + TQString( hostbuf ) + ".pid-" + pid.setNum( getpid() ) + ".random-" + randomString + '.' + m_bundle->type(); m_origRenamedSavePath = origPath.path() + ".amarokoriginal.host-" + TQString( hostbuf ) + ".pid-" + pid.setNum( getpid() ) + ".random-" + randomString + '.' + m_bundle->type(); //The next long step is to copy the file over. We can't use TDEIO because it's not thread save, //and std and TQFile only have provisions for renaming and removing, so manual it is //doing it block-by-block results it not needing a huge amount of memory overhead debug() << "Copying original file to copy and caluclating MD5" << endl; if( TQFile::exists( m_tempSavePath ) ) { debug() << "Temp file already exists!" << endl; return 0; } TQFile orig( m_bundle->url().path() ); TQFile copy( m_tempSavePath ); if( !orig.open( IO_Raw | IO_ReadOnly ) ) { debug() << "Could not open original file!" << endl; return 0; } //Do this separately so as not to create a zero-length file if you can't read from input if( !copy.open( IO_Raw | IO_WriteOnly | IO_Truncate ) ) { debug() << "Could not create file copy" << endl; return 0; } TQ_LONG actualreadlen, actualwritelen; while( ( actualreadlen = orig.readBlock( m_databuf, m_maxlen ) ) > 0 ) { md5sum.update( m_databuf, actualreadlen ); if( ( actualwritelen = copy.writeBlock( m_databuf, actualreadlen ) ) != actualreadlen ) { debug() << "Error during copying of original file data to copy!" << endl; return 0; } } if( actualreadlen == -1 ) { debug() << "Error during reading original file!" << endl; return 0; } m_tempSaveDigest = md5sum.hexDigest(); //By this point, we have the following: //The original file is copied at path m_tempSavePath //We have generated what will be the filename to rename the original to in m_origRenamedSavePath //We have successfully copied the original file to the temp location //We've calculated the md5sum of the original file debug() << "MD5 sum of temp file: " << m_tempSaveDigest.data() << endl; //Now, we have a MD5 sum of the original file at the time of copying saved in m_tempSaveDigest //Create a fileref on the copied file, for modification m_saveFileref = new TagLib::FileRef( TQFile::encodeName( m_tempSavePath ), false ); if( m_saveFileref && !m_saveFileref->isNull() ) return m_saveFileref; debug() << "Error creating temp file's fileref!" << endl; return 0; } bool MetaBundleSaver::doSave() { //TODO: much commenting needed. For now this pretty much follows algorithm laid out in bug 131353, //but isn't useable since I need to find a good way to switch the file path with taglib, or a good way //to get all the metadata copied over. DEBUG_BLOCK m_cleanupNeeded = true; bool revert = false; TQFile origRenamedFile( m_origRenamedSavePath ); KMD5 md5sum( 0, 0 ); TQ_LONG actualreadlen; int errcode; TQCString origRenamedDigest; if( !m_saveFileref || m_tempSavePath.isEmpty() || m_tempSaveDigest.isEmpty() || m_origRenamedSavePath.isEmpty() ) { debug() << "You must run prepareToSave() and it must return successfully before calling doSave()!" << endl; return false; } debug() << "Saving tag changes to the temporary file..." << endl; //We've made our changes to the fileref; save it first, then do the logic to move the correct file back if( !m_saveFileref->save() ) { debug() << "Could not save the new file!" << endl; goto fail_remove_copy; } debug() << "Renaming original file to temporary name " << m_origRenamedSavePath << endl; errcode = std::rename( TQFile::encodeName( m_bundle->url().path() ).data(), TQFile::encodeName( m_origRenamedSavePath ).data() ); if( errcode != 0 ) { debug() << "Could not move original!" << endl; perror( "Could not move original!" ); goto fail_remove_copy; } revert = true; debug() << "Calculating MD5 of " << m_origRenamedSavePath << endl; if( !origRenamedFile.open( IO_Raw | IO_ReadOnly ) ) { debug() << "Could not open temporary file!" << endl; goto fail_remove_copy; } while( ( actualreadlen = origRenamedFile.readBlock( m_databuf, m_maxlen ) ) > 0 ) md5sum.update( m_databuf, actualreadlen ); if( actualreadlen == -1 ) { debug() << "Error during checksumming temp file!" << endl; goto fail_remove_copy; } origRenamedDigest = md5sum.hexDigest(); debug() << "md5sum of original file: " << origRenamedDigest.data() << endl; if( origRenamedDigest != m_tempSaveDigest ) { debug() << "Original checksum did not match current checksum!" << endl; goto fail_remove_copy; } debug() << "Renaming temp file to original's filename" << endl; errcode = std::rename( TQFile::encodeName( m_tempSavePath ).data(), TQFile::encodeName( m_bundle->url().path() ).data() ); if( errcode != 0 ) { debug() << "Could not rename newly-tagged file to original!" << endl; perror( "Could not rename newly-tagged file to original!" ); goto fail_remove_copy; } debug() << "Deleting original" << endl; errcode = std::remove( TQFile::encodeName( m_origRenamedSavePath ) ); if( errcode != 0 ) { debug() << "Could not delete the original file!" << endl; perror( "Could not delete the original file!" ); return false; } debug() << "Save done, returning true!" << endl; return true; fail_remove_copy: debug() << "Deleting temporary file..." << endl; errcode = std::remove( TQFile::encodeName( m_tempSavePath ).data() ); if( errcode != 0 ) { debug() << "Could not delete the temporary file!" << endl; perror( "Could not delete the temporary file!" ); } if( !revert ) return false; debug() << "Reverting original file to original filename!" << endl; errcode = std::rename( TQFile::encodeName( m_origRenamedSavePath ).data(), TQFile::encodeName( m_bundle->url().path() ).data() ); if( errcode != 0 ) { debug() << "Could not revert file to original filename!" << endl; perror( "Could not revert file to original filename!" ); } return false; } bool MetaBundleSaver::cleanupSave() { DEBUG_BLOCK bool dirty = false; if( !m_tempSavePath.isEmpty() && TQFile::exists( m_tempSavePath ) ) { int errcode; errcode = std::remove( TQFile::encodeName( m_tempSavePath ).data() ); if( errcode != 0 ) { dirty = true; debug() << "Could not delete the temporary file!" << endl; } } m_tempSavePath = TQString(); m_origRenamedSavePath = TQString(); m_tempSaveDigest = TQCString( 0 ); if( m_saveFileref ) { delete m_saveFileref; m_saveFileref = 0; } m_cleanupNeeded = false; return !dirty; } #include "metabundlesaver.moc"