2015-09-06 19:57:33 +02:00
# include "./backuphelper.h"
2018-03-05 17:49:29 +01:00
# include "./diagnostics.h"
2018-03-07 01:17:50 +01:00
# include "./mediafileinfo.h"
2016-05-01 20:02:44 +02:00
2017-01-27 18:59:22 +01:00
# include <c++utilities/conversion/stringbuilder.h>
2018-03-07 01:17:50 +01:00
# include <c++utilities/conversion/stringconversion.h>
2016-05-01 20:02:44 +02:00
2015-04-22 19:22:01 +02:00
# include <cstdio>
2021-09-11 21:52:49 +02:00
# include <filesystem>
2018-03-07 01:17:50 +01:00
# include <fstream>
2015-04-22 19:22:01 +02:00
# include <stdexcept>
2018-03-07 01:17:50 +01:00
# include <string>
2015-04-22 19:22:01 +02:00
using namespace std ;
2019-06-10 22:49:11 +02:00
using namespace CppUtilities ;
2015-04-22 19:22:01 +02:00
2018-03-06 23:09:15 +01:00
namespace TagParser {
2015-04-22 19:22:01 +02:00
/*!
2018-06-03 20:38:32 +02:00
* \ namespace TagParser : : BackupHelper
2015-04-22 19:22:01 +02:00
* \ brief Helps to create and restore backup files when rewriting
* files to apply changed tag information .
*
2018-02-04 23:55:52 +01:00
* Methods in this namespace are internally used eg . in implementations of AbstractContainer : : internalMakeFile ( ) .
2015-04-22 19:22:01 +02:00
*/
namespace BackupHelper {
/*!
* \ brief Restores the original file from the specified backup file .
* \ param originalPath Specifies the path to the original file .
* \ param backupPath Specifies the path to the backup file .
2016-12-18 20:17:50 +01:00
* \ param originalStream Specifies a std : : fstream instance for the original file .
* \ param backupStream Specifies a std : : fstream instance for the backup file .
2015-04-22 19:22:01 +02:00
*
2016-05-01 20:02:44 +02:00
* This helper function is used by MediaFileInfo and container implementations
* to restore the original file from the specified backup file in the case a Failure
2015-04-22 19:22:01 +02:00
* or an IO error occurs . The specified streams will be closed if
* currently open .
*
2016-05-01 20:02:44 +02:00
* If moving isn ' t possible ( eg . \ a originalPath and \ a backupPath refer to different partitions ) the backup
* file will be restored by copying .
*
2015-04-22 19:22:01 +02:00
* \ throws Throws std : : ios_base : : failure on failure .
2017-08-17 19:04:58 +02:00
* \ todo Implement callback for progress updates ( copy ) .
2015-04-22 19:22:01 +02:00
*/
2018-03-07 01:17:50 +01:00
void restoreOriginalFileFromBackupFile (
const std : : string & originalPath , const std : : string & backupPath , NativeFileStream & originalStream , NativeFileStream & backupStream )
2015-04-22 19:22:01 +02:00
{
2021-09-11 23:05:22 +02:00
// ensure streams are closed but don't handle any errors anymore at this point
originalStream . exceptions ( ios_base : : goodbit ) ;
2016-03-19 18:32:39 +01:00
backupStream . exceptions ( ios_base : : goodbit ) ;
2021-09-11 23:05:22 +02:00
originalStream . close ( ) ;
2016-03-19 18:32:39 +01:00
backupStream . close ( ) ;
2021-09-11 23:05:22 +02:00
originalStream . clear ( ) ;
2016-03-19 18:32:39 +01:00
backupStream . clear ( ) ;
2021-09-11 23:05:22 +02:00
// restore usual exception handling of the streams
originalStream . exceptions ( ios_base : : badbit | ios_base : : failbit ) ;
backupStream . exceptions ( ios_base : : badbit | ios_base : : failbit ) ;
// check whether backup file actually exists and close the backup stream afterwards
2022-03-16 19:13:09 +01:00
const auto originalPathForOpen = std : : filesystem : : u8path ( BasicFileInfo : : pathForOpen ( originalPath ) ) ;
const auto backupPathForOpen = std : : filesystem : : u8path ( BasicFileInfo : : pathForOpen ( backupPath ) ) ;
2021-09-11 23:05:22 +02:00
auto ec = std : : error_code ( ) ;
if ( ! std : : filesystem : : exists ( backupPathForOpen , ec ) & & ! ec ) {
2019-03-13 19:06:42 +01:00
throw std : : ios_base : : failure ( " Backup/temporary file has not been created. " ) ;
2015-04-22 19:22:01 +02:00
}
2021-09-11 23:05:22 +02:00
2016-03-19 18:32:39 +01:00
// remove original file and restore backup
2021-09-11 23:05:22 +02:00
std : : filesystem : : remove ( originalPath , ec ) ;
if ( ec ) {
throw std : : ios_base : : failure ( " Unable to remove original file: " + ec . message ( ) ) ;
2019-12-15 19:43:16 +01:00
}
2021-09-11 23:05:22 +02:00
std : : filesystem : : rename ( backupPathForOpen , originalPathForOpen , ec ) ;
if ( ec ) {
// try making a copy instead, maybe backup dir is on another partition
std : : filesystem : : copy_file ( backupPathForOpen , originalPathForOpen , ec ) ;
}
if ( ec ) {
throw std : : ios_base : : failure ( " Unable to restore original file from backup file \" " % backupPath % " \" after failure: " + ec . message ( ) ) ;
2015-04-22 19:22:01 +02:00
}
2018-02-04 23:55:52 +01:00
}
2015-04-22 19:22:01 +02:00
/*!
* \ brief Creates a backup file for the specified file .
2018-07-10 16:34:57 +02:00
* \ param backupDir Specifies the directory to store backup files . If empty , the directory of the file
* to be backuped is used .
2015-04-22 19:22:01 +02:00
* \ param originalPath Specifies the path of the file to be backuped .
* \ param backupPath Contains the path of the created backup file when this function returns .
2016-05-01 20:02:44 +02:00
* \ param originalStream Specifies a std : : fstream for the original file .
2015-04-22 19:22:01 +02:00
* \ param backupStream Specifies a std : : fstream for creating the backup file .
*
2016-05-01 20:02:44 +02:00
* This helper function is used by MediaFileInfo and container implementations to create a backup file
2017-08-17 19:04:58 +02:00
* when applying changes . The specified \ a backupPath is set to the path of the created backup file .
2017-03-01 18:21:00 +01:00
* The specified \ a backupStream will be closed if currently open . Then it is
2015-04-22 19:22:01 +02:00
* used to open the backup file using the flags ios_base : : in and ios_base : : binary .
*
2016-05-01 20:02:44 +02:00
* The specified \ a originalStream is closed before performing the move operation .
*
* If moving isn ' t possible ( eg . \ a originalPath and \ a backupPath refer to different partitions ) the backup
* file will be created by copying .
*
2015-04-22 19:22:01 +02:00
* The original file can now be rewritten to apply changes . When this operation fails
* the created backup file can be restored using restoreOriginalFileFromBackupFile ( ) .
*
* \ throws Throws std : : ios_base : : failure on failure .
2017-08-17 19:04:58 +02:00
* \ todo Implement callback for progress updates ( copy ) .
2015-04-22 19:22:01 +02:00
*/
2018-07-10 16:34:57 +02:00
void createBackupFile ( const std : : string & backupDir , const std : : string & originalPath , std : : string & backupPath , NativeFileStream & originalStream ,
NativeFileStream & backupStream )
2015-04-22 19:22:01 +02:00
{
2018-02-04 23:55:52 +01:00
// determine dirs
2022-03-16 19:13:09 +01:00
const auto backupDirRelative = std : : filesystem : : u8path ( backupDir ) . is_relative ( ) ;
2021-09-11 23:05:22 +02:00
const auto originalDir = backupDirRelative ? BasicFileInfo : : containingDirectory ( originalPath ) : string ( ) ;
2018-02-04 23:55:52 +01:00
2016-05-01 20:02:44 +02:00
// determine the backup path
2021-09-11 23:05:22 +02:00
auto ec = std : : error_code ( ) ;
2018-03-07 01:17:50 +01:00
for ( unsigned int i = 0 ; ; + + i ) {
if ( backupDir . empty ( ) ) {
if ( i ) {
2017-01-30 00:42:35 +01:00
backupPath = originalPath % ' . ' % i + " .bak " ;
2016-05-01 20:02:44 +02:00
} else {
backupPath = originalPath + " .bak " ;
}
2015-04-22 19:22:01 +02:00
} else {
2018-02-04 23:55:52 +01:00
const auto fileName ( BasicFileInfo : : fileName ( originalPath , i ) ) ;
2018-03-07 01:17:50 +01:00
if ( i ) {
2018-02-04 23:55:52 +01:00
const auto ext ( BasicFileInfo : : extension ( originalPath ) ) ;
2018-03-07 01:17:50 +01:00
if ( backupDirRelative ) {
2018-02-04 23:55:52 +01:00
backupPath = originalDir % ' / ' % backupDir % ' / ' % fileName % ' . ' % i + ext ;
2016-05-01 20:02:44 +02:00
} else {
2018-02-04 23:55:52 +01:00
backupPath = backupDir % ' / ' % fileName % ' . ' % i + ext ;
2016-05-01 20:02:44 +02:00
}
} else {
2018-03-07 01:17:50 +01:00
if ( backupDirRelative ) {
2018-02-04 23:55:52 +01:00
backupPath = originalDir % ' / ' % backupDir % ' / ' + fileName ;
2016-05-01 20:02:44 +02:00
} else {
2018-02-04 23:55:52 +01:00
backupPath = backupDir % ' / ' + fileName ;
2016-05-01 20:02:44 +02:00
}
}
2015-04-22 19:22:01 +02:00
}
2018-02-04 23:55:52 +01:00
2018-04-29 17:19:33 +02:00
// test whether the backup path is still unused; otherwise continue loop
2022-03-16 19:13:09 +01:00
if ( ! std : : filesystem : : exists ( std : : filesystem : : u8path ( BasicFileInfo : : pathForOpen ( backupPath ) ) , ec ) ) {
2016-05-01 20:02:44 +02:00
break ;
2018-02-04 23:55:52 +01:00
}
2015-04-22 19:22:01 +02:00
}
2016-05-01 20:02:44 +02:00
// ensure original file is closed
2018-03-07 01:17:50 +01:00
if ( originalStream . is_open ( ) ) {
2016-05-01 20:02:44 +02:00
originalStream . close ( ) ;
}
2018-02-04 23:55:52 +01:00
2015-04-22 19:22:01 +02:00
// rename original file
2022-03-16 19:13:09 +01:00
const auto u8originalPath = std : : filesystem : : u8path ( originalPath ) ;
const auto backupPathForOpen = std : : filesystem : : u8path ( BasicFileInfo : : pathForOpen ( backupPath ) ) ;
std : : filesystem : : rename ( u8originalPath , backupPathForOpen , ec ) ;
2021-09-11 23:05:22 +02:00
if ( ec ) {
// try making a copy instead, maybe backup dir is on another partition
2022-03-16 19:13:09 +01:00
std : : filesystem : : copy_file ( u8originalPath , backupPathForOpen , ec ) ;
2021-09-11 23:05:22 +02:00
}
if ( ec ) {
throw std : : ios_base : : failure (
2022-03-16 19:13:09 +01:00
argsToString ( " Unable to create backup file \" " , BasicFileInfo : : pathForOpen ( backupPath ) , " \" of \" " , originalPath , " \" before rewriting it: " + ec . message ( ) ) ) ;
2015-04-22 19:22:01 +02:00
}
2018-02-04 23:55:52 +01:00
// manage streams
2015-04-22 19:22:01 +02:00
try {
2018-02-04 23:55:52 +01:00
// ensure there is no file associated with the originalStream object
2018-03-07 01:17:50 +01:00
if ( originalStream . is_open ( ) ) {
2016-05-01 20:02:44 +02:00
originalStream . close ( ) ;
}
// ensure there is no file associated with the backupStream object
2018-03-07 01:17:50 +01:00
if ( backupStream . is_open ( ) ) {
2015-04-22 19:22:01 +02:00
backupStream . close ( ) ;
}
// open backup stream
2016-03-19 18:32:39 +01:00
backupStream . exceptions ( ios_base : : failbit | ios_base : : badbit ) ;
2021-01-30 21:53:06 +01:00
backupStream . open ( BasicFileInfo : : pathForOpen ( backupPath ) . data ( ) , ios_base : : in | ios_base : : binary ) ;
2019-03-13 19:06:42 +01:00
} catch ( const std : : ios_base : : failure & failure ) {
2021-09-11 23:05:22 +02:00
// try to restore the previous state in the error case
try {
restoreOriginalFileFromBackupFile ( originalPath , backupPath , originalStream , backupStream ) ;
} catch ( const std : : ios_base : : failure & ) {
2019-03-13 19:06:42 +01:00
throw std : : ios_base : : failure ( " Unable to restore original file from backup file \" " % backupPath % " \" after failure: " + failure . what ( ) ) ;
2015-04-22 19:22:01 +02:00
}
2021-09-11 23:05:22 +02:00
throw std : : ios_base : : failure ( argsToString ( " Unable to open backup file: " , failure . what ( ) ) ) ;
2015-04-22 19:22:01 +02:00
}
}
2021-09-11 23:05:22 +02:00
/*!
* \ brief Creates a backup file like createBackupFile ( ) but canonicalizes \ a originalPath before doing the backup .
* \ remarks
* - This function sets \ a originalPath to be a canonical path .
* - Using this function ( instead of createBackupFile ( ) ) is recommended so the actual file is being altered .
*/
void createBackupFileCanonical ( const std : : string & backupDir , std : : string & originalPath , std : : string & backupPath ,
CppUtilities : : NativeFileStream & originalStream , CppUtilities : : NativeFileStream & backupStream )
{
auto ec = std : : error_code ( ) ;
2022-03-16 19:13:09 +01:00
if ( const auto canonicalPath = std : : filesystem : : canonical ( std : : filesystem : : u8path ( BasicFileInfo : : pathForOpen ( originalPath ) ) , ec ) ; ! ec ) {
2021-09-11 23:05:22 +02:00
originalPath = canonicalPath . string ( ) ;
} else {
throw std : : ios_base : : failure ( " Unable to canonicalize path of original file before rewriting it: " + ec . message ( ) ) ;
}
createBackupFile ( backupDir , originalPath , backupPath , originalStream , backupStream ) ;
}
2016-05-01 20:02:44 +02:00
/*!
2018-07-23 14:44:06 +02:00
* \ brief Handles a failure / abort which occurred after the file has been modified .
2016-05-01 20:02:44 +02:00
*
* - Restores the backup file using restoreOriginalFileFromBackupFile ( ) if one has been created .
* - Adds appropriate notifications to the specified \ a fileInfo .
* - Re - throws the exception .
*
* \ remarks Must only be called when an exception derived from Failure or ios_base : : failure
2021-07-02 03:00:50 +02:00
* has been caught ; this method uses the " exception dispatcher " idiom .
2016-05-01 20:02:44 +02:00
*
2021-07-02 03:00:50 +02:00
* \ param fileInfo Specifies the MediaFileInfo instance which has been modified .
2016-05-01 20:02:44 +02:00
* \ param backupPath Specifies the path of the backup file ; might be empty if none has been created .
* \ param outputStream Specifies the stream used to write the output file . This is usually just the stream
* of \ a fileInfo , but is specified here explicitly for higher flexibility .
* \ param backupStream Specifies the stream assembled using createBackupFile ( ) ; might be a default fstream if
* no backup file has been created .
2018-07-09 12:40:14 +02:00
* \ param diag Specifies the container to add diagnostic messages to .
2016-05-01 20:02:44 +02:00
* \ param context Specifies the context used to add notifications .
*/
2018-03-07 01:17:50 +01:00
void handleFailureAfterFileModified ( MediaFileInfo & fileInfo , const std : : string & backupPath , NativeFileStream & outputStream ,
NativeFileStream & backupStream , Diagnostics & diag , const std : : string & context )
2021-09-11 23:05:22 +02:00
{
handleFailureAfterFileModifiedCanonical ( fileInfo , fileInfo . path ( ) , backupPath , outputStream , backupStream , diag , context ) ;
}
/*!
* \ brief Handles a failure / abort which occurred after the file has been modified .
* \ remarks Same as handleFailureAfterFileModified ( ) but allows specifying the original path instead of just using the
* path from \ a mediaFileInfo .
*/
void handleFailureAfterFileModifiedCanonical ( MediaFileInfo & fileInfo , const std : : string & originalPath , const std : : string & backupPath ,
CppUtilities : : NativeFileStream & outputStream , CppUtilities : : NativeFileStream & backupStream , Diagnostics & diag , const std : : string & context )
2016-05-01 20:02:44 +02:00
{
// reset the associated container in any case
2018-03-07 01:17:50 +01:00
if ( fileInfo . container ( ) ) {
2016-05-01 20:02:44 +02:00
fileInfo . container ( ) - > reset ( ) ;
}
// re-throw the current exception
try {
throw ;
2018-03-07 01:17:50 +01:00
} catch ( const OperationAbortedException & ) {
if ( ! backupPath . empty ( ) ) {
2016-05-01 20:02:44 +02:00
// a temp/backup file has been created -> restore original file
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Information , " Rewriting the file to apply changed tag information has been aborted. " , context ) ;
2016-05-01 20:02:44 +02:00
try {
2021-09-11 23:05:22 +02:00
restoreOriginalFileFromBackupFile ( originalPath , backupPath , outputStream , backupStream ) ;
2021-08-14 13:52:21 +02:00
diag . emplace_back ( DiagLevel : : Warning , " The original file has been restored. " , context ) ;
2019-03-13 19:06:42 +01:00
} catch ( const std : : ios_base : : failure & failure ) {
2021-08-14 13:52:21 +02:00
diag . emplace_back ( DiagLevel : : Critical , argsToString ( " The original file could not be restored: " , failure . what ( ) ) , context ) ;
2016-05-01 20:02:44 +02:00
}
} else {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Information , " Applying new tag information has been aborted. " , context ) ;
2016-05-01 20:02:44 +02:00
}
throw ;
2018-03-07 01:17:50 +01:00
} catch ( const Failure & ) {
if ( ! backupPath . empty ( ) ) {
2016-05-01 20:02:44 +02:00
// a temp/backup file has been created -> restore original file
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Rewriting the file to apply changed tag information failed. " , context ) ;
2016-05-01 20:02:44 +02:00
try {
2021-09-11 23:05:22 +02:00
restoreOriginalFileFromBackupFile ( originalPath , backupPath , outputStream , backupStream ) ;
2021-08-14 13:52:21 +02:00
diag . emplace_back ( DiagLevel : : Warning , " The original file has been restored. " , context ) ;
2019-03-13 19:06:42 +01:00
} catch ( const std : : ios_base : : failure & failure ) {
2021-08-14 13:52:21 +02:00
diag . emplace_back ( DiagLevel : : Critical , argsToString ( " The original file could not be restored: " , failure . what ( ) ) , context ) ;
2016-05-01 20:02:44 +02:00
}
} else {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Applying new tag information failed. " , context ) ;
2016-05-01 20:02:44 +02:00
}
throw ;
2019-03-13 19:06:42 +01:00
} catch ( const std : : ios_base : : failure & ) {
2018-03-07 01:17:50 +01:00
if ( ! backupPath . empty ( ) ) {
2016-05-01 20:02:44 +02:00
// a temp/backup file has been created -> restore original file
2018-07-23 14:44:06 +02:00
diag . emplace_back ( DiagLevel : : Critical , " An IO error occurred when rewriting the file to apply changed tag information. " , context ) ;
2016-05-01 20:02:44 +02:00
try {
2021-09-11 23:05:22 +02:00
restoreOriginalFileFromBackupFile ( originalPath , backupPath , outputStream , backupStream ) ;
2021-08-14 13:52:21 +02:00
diag . emplace_back ( DiagLevel : : Warning , " The original file has been restored. " , context ) ;
2019-03-13 19:06:42 +01:00
} catch ( const std : : ios_base : : failure & failure ) {
2021-08-14 13:52:21 +02:00
diag . emplace_back ( DiagLevel : : Critical , argsToString ( " The original file could not be restored: " , failure . what ( ) ) , context ) ;
2016-05-01 20:02:44 +02:00
}
} else {
2018-07-23 14:44:06 +02:00
diag . emplace_back ( DiagLevel : : Critical , " An IO error occurred when applying tag information. " , context ) ;
2016-05-01 20:02:44 +02:00
}
2019-03-13 19:06:42 +01:00
throw ;
2016-05-01 20:02:44 +02:00
}
}
2018-03-07 01:17:50 +01:00
} // namespace BackupHelper
2015-04-22 19:22:01 +02:00
2018-03-07 01:17:50 +01:00
} // namespace TagParser