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