Tag Parser 10.2.0
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#ifdef PLATFORM_WINDOWS
9#include <windows.h>
10#else
11#include <sys/stat.h>
12#endif
13
14#include <cstdio>
15#include <fstream>
16#include <stdexcept>
17#include <string>
18
19using namespace std;
20using namespace CppUtilities;
21
22namespace TagParser {
23
32namespace BackupHelper {
33
53 const std::string &originalPath, const std::string &backupPath, NativeFileStream &originalStream, NativeFileStream &backupStream)
54{
55 // ensure the original stream is closed
56 if (originalStream.is_open()) {
57 originalStream.close();
58 }
59 // check whether backup file actually exists and close the backup stream afterwards
60 const auto backupPathForOpen = BasicFileInfo::pathForOpen(backupPath);
61 backupStream.exceptions(ios_base::goodbit);
62 backupStream.close();
63 backupStream.clear();
64 backupStream.open(backupPathForOpen.data(), ios_base::in | ios_base::binary);
65 if (backupStream.is_open()) {
66 backupStream.close();
67 } else {
68 throw std::ios_base::failure("Backup/temporary file has not been created.");
69 }
70 // remove original file and restore backup
71 const auto originalPathForOpen = BasicFileInfo::pathForOpen(originalPath);
72 std::remove(originalPathForOpen.data());
73 if (std::rename(backupPathForOpen.data(), originalPathForOpen.data()) == 0) {
74 return;
75 }
76 // can't rename/move the file (maybe backup dir on another partition) -> make a copy instead
77 try {
78 // need to open all streams again
79 backupStream.exceptions(ios_base::failbit | ios_base::badbit);
80 originalStream.exceptions(ios_base::failbit | ios_base::badbit);
81 backupStream.open(backupPathForOpen.data(), ios_base::in | ios_base::binary);
82 originalStream.open(originalPathForOpen.data(), ios_base::out | ios_base::binary);
83 originalStream << backupStream.rdbuf();
84 originalStream.flush();
85 // TODO: callback for progress updates
86 } catch (const std::ios_base::failure &failure) {
87 throw std::ios_base::failure("Unable to restore original file from backup file \"" % backupPath % "\" after failure: " + failure.what());
88 }
89}
90
94static bool isRelative(const std::string &path)
95{
96 return path.empty() || (path.front() != '/' && (path.size() < 2 || path[1] != ':'));
97}
98
124void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, NativeFileStream &originalStream,
125 NativeFileStream &backupStream)
126{
127 // determine dirs
128 const auto backupDirRelative(isRelative(backupDir));
129 const auto originalDir(backupDirRelative ? BasicFileInfo::containingDirectory(originalPath) : string());
130
131 // determine the backup path
132 for (unsigned int i = 0;; ++i) {
133 if (backupDir.empty()) {
134 if (i) {
135 backupPath = originalPath % '.' % i + ".bak";
136 } else {
137 backupPath = originalPath + ".bak";
138 }
139 } else {
140 const auto fileName(BasicFileInfo::fileName(originalPath, i));
141 if (i) {
142 const auto ext(BasicFileInfo::extension(originalPath));
143 if (backupDirRelative) {
144 backupPath = originalDir % '/' % backupDir % '/' % fileName % '.' % i + ext;
145 } else {
146 backupPath = backupDir % '/' % fileName % '.' % i + ext;
147 }
148 } else {
149 if (backupDirRelative) {
150 backupPath = originalDir % '/' % backupDir % '/' + fileName;
151 } else {
152 backupPath = backupDir % '/' + fileName;
153 }
154 }
155 }
156
157 // test whether the backup path is still unused; otherwise continue loop
158#ifdef PLATFORM_WINDOWS
159 if (GetFileAttributes(BasicFileInfo::pathForOpen(backupPath).data()) == INVALID_FILE_ATTRIBUTES) {
160#else
161 struct stat backupStat;
162 if (stat(BasicFileInfo::pathForOpen(backupPath).data(), &backupStat)) {
163#endif
164 break;
165 }
166 }
167
168 // ensure original file is closed
169 if (originalStream.is_open()) {
170 originalStream.close();
171 }
172
173 // rename original file
174 if (std::rename(BasicFileInfo::pathForOpen(originalPath).data(), BasicFileInfo::pathForOpen(backupPath).data())) {
175 // can't rename/move the file (maybe backup dir on another partition) -> make a copy instead
176 try {
177 backupStream.exceptions(ios_base::failbit | ios_base::badbit);
178 originalStream.exceptions(ios_base::failbit | ios_base::badbit);
179 // ensure backupStream is opened as write-only
180 if (backupStream.is_open()) {
181 backupStream.close();
182 }
183 backupStream.open(BasicFileInfo::pathForOpen(backupPath).data(), ios_base::out | ios_base::binary);
184 // ensure originalStream is opened with read permissions
185 originalStream.open(BasicFileInfo::pathForOpen(originalPath).data(), ios_base::in | ios_base::binary);
186 // do the actual copying
187 backupStream << originalStream.rdbuf();
188 backupStream.flush();
189 // streams are closed in the next try-block
190 // TODO: callback for progress updates
191 } catch (const std::ios_base::failure &failure) {
192 throw std::ios_base::failure(argsToString("Unable to rename original file before rewriting it: ", failure.what()));
193 }
194 }
195
196 // manage streams
197 try {
198 // ensure there is no file associated with the originalStream object
199 if (originalStream.is_open()) {
200 originalStream.close();
201 }
202 // ensure there is no file associated with the backupStream object
203 if (backupStream.is_open()) {
204 backupStream.close();
205 }
206 // open backup stream
207 backupStream.exceptions(ios_base::failbit | ios_base::badbit);
208 backupStream.open(BasicFileInfo::pathForOpen(backupPath).data(), ios_base::in | ios_base::binary);
209 } catch (const std::ios_base::failure &failure) {
210 // can't open the new file
211 // -> try to re-rename backup file in the error case to restore previous state
212 if (std::rename(BasicFileInfo::pathForOpen(backupPath).data(), BasicFileInfo::pathForOpen(originalPath).data())) {
213 throw std::ios_base::failure("Unable to restore original file from backup file \"" % backupPath % "\" after failure: " + failure.what());
214 } else {
215 throw std::ios_base::failure(argsToString("Unable to open backup file: ", failure.what()));
216 }
217 }
218}
219
239void handleFailureAfterFileModified(MediaFileInfo &fileInfo, const std::string &backupPath, NativeFileStream &outputStream,
240 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(fileInfo.path(), 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(fileInfo.path(), 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(fileInfo.path(), 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 handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
TAG_PARSER_EXPORT void restoreOriginalFileFromBackupFile(const std::string &originalPath, const std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10