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
|
|
|
|
const auto originalPathForOpen = std::filesystem::path(BasicFileInfo::pathForOpen(originalPath));
|
|
|
|
const auto backupPathForOpen = std::filesystem::path(BasicFileInfo::pathForOpen(backupPath));
|
|
|
|
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
|
2021-09-11 23:05:22 +02:00
|
|
|
const auto backupDirRelative = std::filesystem::path(backupDir).is_relative();
|
|
|
|
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
|
2021-09-11 23:05:22 +02:00
|
|
|
if (!std::filesystem::exists(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
|
2021-09-11 23:05:22 +02:00
|
|
|
const auto backupPathForOpen = BasicFileInfo::pathForOpen(backupPath);
|
|
|
|
std::filesystem::rename(originalPath, backupPathForOpen, ec);
|
|
|
|
if (ec) {
|
|
|
|
// try making a copy instead, maybe backup dir is on another partition
|
|
|
|
std::filesystem::copy_file(originalPath, backupPathForOpen, ec);
|
|
|
|
}
|
|
|
|
if (ec) {
|
|
|
|
throw std::ios_base::failure(
|
|
|
|
argsToString("Unable to create backup file \"", backupPathForOpen, "\" 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();
|
|
|
|
if (const auto canonicalPath = std::filesystem::canonical(BasicFileInfo::pathForOpen(originalPath), ec); !ec) {
|
|
|
|
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
|