Tag Parser 12.3.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
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