Tag Parser 10.3.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
backuphelper.cpp
Go to the documentation of this file.
1#include "./backuphelper.h"
2#include "./diagnostics.h"
3#include "./mediafileinfo.h"
4
5#include <c++utilities/conversion/stringbuilder.h>
6#include <c++utilities/conversion/stringconversion.h>
7
8#include <cstdio>
9#include <filesystem>
10#include <fstream>
11#include <stdexcept>
12#include <string>
13
14using namespace std;
15using namespace CppUtilities;
16
17namespace TagParser {
18
27namespace BackupHelper {
28
48 const std::string &originalPath, const std::string &backupPath, NativeFileStream &originalStream, NativeFileStream &backupStream)
49{
50 // ensure streams are closed but don't handle any errors anymore at this point
51 originalStream.exceptions(ios_base::goodbit);
52 backupStream.exceptions(ios_base::goodbit);
53 originalStream.close();
54 backupStream.close();
55 originalStream.clear();
56 backupStream.clear();
57
58 // restore usual exception handling of the streams
59 originalStream.exceptions(ios_base::badbit | ios_base::failbit);
60 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
61
62 // check whether backup file actually exists and close the backup stream afterwards
63 const auto originalPathForOpen = std::filesystem::path(BasicFileInfo::pathForOpen(originalPath));
64 const auto backupPathForOpen = std::filesystem::path(BasicFileInfo::pathForOpen(backupPath));
65 auto ec = std::error_code();
66 if (!std::filesystem::exists(backupPathForOpen, ec) && !ec) {
67 throw std::ios_base::failure("Backup/temporary file has not been created.");
68 }
69
70 // remove original file and restore backup
71 std::filesystem::remove(originalPath, ec);
72 if (ec) {
73 throw std::ios_base::failure("Unable to remove original file: " + ec.message());
74 }
75 std::filesystem::rename(backupPathForOpen, originalPathForOpen, ec);
76 if (ec) {
77 // try making a copy instead, maybe backup dir is on another partition
78 std::filesystem::copy_file(backupPathForOpen, originalPathForOpen, ec);
79 }
80 if (ec) {
81 throw std::ios_base::failure("Unable to restore original file from backup file \"" % backupPath % "\" after failure: " + ec.message());
82 }
83}
84
110void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, NativeFileStream &originalStream,
111 NativeFileStream &backupStream)
112{
113 // determine dirs
114 const auto backupDirRelative = std::filesystem::path(backupDir).is_relative();
115 const auto originalDir = backupDirRelative ? BasicFileInfo::containingDirectory(originalPath) : string();
116
117 // determine the backup path
118 auto ec = std::error_code();
119 for (unsigned int i = 0;; ++i) {
120 if (backupDir.empty()) {
121 if (i) {
122 backupPath = originalPath % '.' % i + ".bak";
123 } else {
124 backupPath = originalPath + ".bak";
125 }
126 } else {
127 const auto fileName(BasicFileInfo::fileName(originalPath, i));
128 if (i) {
129 const auto ext(BasicFileInfo::extension(originalPath));
130 if (backupDirRelative) {
131 backupPath = originalDir % '/' % backupDir % '/' % fileName % '.' % i + ext;
132 } else {
133 backupPath = backupDir % '/' % fileName % '.' % i + ext;
134 }
135 } else {
136 if (backupDirRelative) {
137 backupPath = originalDir % '/' % backupDir % '/' + fileName;
138 } else {
139 backupPath = backupDir % '/' + fileName;
140 }
141 }
142 }
143
144 // test whether the backup path is still unused; otherwise continue loop
145 if (!std::filesystem::exists(BasicFileInfo::pathForOpen(backupPath), ec)) {
146 break;
147 }
148 }
149
150 // ensure original file is closed
151 if (originalStream.is_open()) {
152 originalStream.close();
153 }
154
155 // rename original file
156 const auto backupPathForOpen = BasicFileInfo::pathForOpen(backupPath);
157 std::filesystem::rename(originalPath, backupPathForOpen, ec);
158 if (ec) {
159 // try making a copy instead, maybe backup dir is on another partition
160 std::filesystem::copy_file(originalPath, backupPathForOpen, ec);
161 }
162 if (ec) {
163 throw std::ios_base::failure(
164 argsToString("Unable to create backup file \"", backupPathForOpen, "\" of \"", originalPath, "\" before rewriting it: " + ec.message()));
165 }
166
167 // manage streams
168 try {
169 // ensure there is no file associated with the originalStream object
170 if (originalStream.is_open()) {
171 originalStream.close();
172 }
173 // ensure there is no file associated with the backupStream object
174 if (backupStream.is_open()) {
175 backupStream.close();
176 }
177 // open backup stream
178 backupStream.exceptions(ios_base::failbit | ios_base::badbit);
179 backupStream.open(BasicFileInfo::pathForOpen(backupPath).data(), ios_base::in | ios_base::binary);
180 } catch (const std::ios_base::failure &failure) {
181 // try to restore the previous state in the error case
182 try {
183 restoreOriginalFileFromBackupFile(originalPath, backupPath, originalStream, backupStream);
184 } catch (const std::ios_base::failure &) {
185 throw std::ios_base::failure("Unable to restore original file from backup file \"" % backupPath % "\" after failure: " + failure.what());
186 }
187 throw std::ios_base::failure(argsToString("Unable to open backup file: ", failure.what()));
188 }
189}
190
197void createBackupFileCanonical(const std::string &backupDir, std::string &originalPath, std::string &backupPath,
198 CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
199{
200 auto ec = std::error_code();
201 if (const auto canonicalPath = std::filesystem::canonical(BasicFileInfo::pathForOpen(originalPath), ec); !ec) {
202 originalPath = canonicalPath.string();
203 } else {
204 throw std::ios_base::failure("Unable to canonicalize path of original file before rewriting it: " + ec.message());
205 }
206 createBackupFile(backupDir, originalPath, backupPath, originalStream, backupStream);
207}
208
228void handleFailureAfterFileModified(MediaFileInfo &fileInfo, const std::string &backupPath, NativeFileStream &outputStream,
229 NativeFileStream &backupStream, Diagnostics &diag, const std::string &context)
230{
231 handleFailureAfterFileModifiedCanonical(fileInfo, fileInfo.path(), backupPath, outputStream, backupStream, diag, context);
232}
233
239void handleFailureAfterFileModifiedCanonical(MediaFileInfo &fileInfo, const std::string &originalPath, const std::string &backupPath,
240 CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context)
241{
242 // reset the associated container in any case
243 if (fileInfo.container()) {
244 fileInfo.container()->reset();
245 }
246
247 // re-throw the current exception
248 try {
249 throw;
250 } catch (const OperationAbortedException &) {
251 if (!backupPath.empty()) {
252 // a temp/backup file has been created -> restore original file
253 diag.emplace_back(DiagLevel::Information, "Rewriting the file to apply changed tag information has been aborted.", context);
254 try {
255 restoreOriginalFileFromBackupFile(originalPath, backupPath, outputStream, backupStream);
256 diag.emplace_back(DiagLevel::Warning, "The original file has been restored.", context);
257 } catch (const std::ios_base::failure &failure) {
258 diag.emplace_back(DiagLevel::Critical, argsToString("The original file could not be restored: ", failure.what()), context);
259 }
260 } else {
261 diag.emplace_back(DiagLevel::Information, "Applying new tag information has been aborted.", context);
262 }
263 throw;
264
265 } catch (const Failure &) {
266 if (!backupPath.empty()) {
267 // a temp/backup file has been created -> restore original file
268 diag.emplace_back(DiagLevel::Critical, "Rewriting the file to apply changed tag information failed.", context);
269 try {
270 restoreOriginalFileFromBackupFile(originalPath, backupPath, outputStream, backupStream);
271 diag.emplace_back(DiagLevel::Warning, "The original file has been restored.", context);
272 } catch (const std::ios_base::failure &failure) {
273 diag.emplace_back(DiagLevel::Critical, argsToString("The original file could not be restored: ", failure.what()), context);
274 }
275 } else {
276 diag.emplace_back(DiagLevel::Critical, "Applying new tag information failed.", context);
277 }
278 throw;
279
280 } catch (const std::ios_base::failure &) {
281 if (!backupPath.empty()) {
282 // a temp/backup file has been created -> restore original file
283 diag.emplace_back(DiagLevel::Critical, "An IO error occurred when rewriting the file to apply changed tag information.", context);
284 try {
285 restoreOriginalFileFromBackupFile(originalPath, backupPath, outputStream, backupStream);
286 diag.emplace_back(DiagLevel::Warning, "The original file has been restored.", context);
287 } catch (const std::ios_base::failure &failure) {
288 diag.emplace_back(DiagLevel::Critical, argsToString("The original file could not be restored: ", failure.what()), context);
289 }
290 } else {
291 diag.emplace_back(DiagLevel::Critical, "An IO error occurred when applying tag information.", context);
292 }
293 throw;
294 }
295}
296
297} // namespace BackupHelper
298
299} // namespace TagParser
virtual void reset()
Discards all parsing results.
std::string containingDirectory() const
Returns the path of the directory containing the current file.
static std::string fileName(std::string_view path, bool cutExtension=false)
Returns the file name of the given file.
const std::string & path() const
Returns the path of the current file.
static std::string_view pathForOpen(std::string_view url)
Returns removes the "file:/" prefix from url to be able to pass it to functions like open(),...
std::string extension() const
Returns the extension of the current file.
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition: mediafileinfo.h:75
AbstractContainer * container() const
Returns the container for the current file.
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
Definition: exceptions.h:46
TAG_PARSER_EXPORT void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
TAG_PARSER_EXPORT void handleFailureAfterFileModifiedCanonical(MediaFileInfo &fileInfo, const std::string &originalPath, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
Handles a failure/abort which occurred after the file has been modified.
TAG_PARSER_EXPORT void restoreOriginalFileFromBackupFile(const std::string &originalPath, const std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
TAG_PARSER_EXPORT void createBackupFileCanonical(const std::string &backupDir, std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
Creates a backup file like createBackupFile() but canonicalizes originalPath before doing the backup.
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &fileInfo, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10