Tag Parser  10.0.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 #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 
19 using namespace std;
20 using namespace CppUtilities;
21 
22 namespace TagParser {
23 
32 namespace 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  backupStream.exceptions(ios_base::goodbit);
61  backupStream.close();
62  backupStream.clear();
63  backupStream.open(backupPath, ios_base::in | ios_base::binary);
64  if (backupStream.is_open()) {
65  backupStream.close();
66  } else {
67  throw std::ios_base::failure("Backup/temporary file has not been created.");
68  }
69  // remove original file and restore backup
70  std::remove(originalPath.c_str());
71  if (std::rename(BasicFileInfo::pathForOpen(backupPath).data(), BasicFileInfo::pathForOpen(originalPath).data()) == 0) {
72  return;
73  }
74  // can't rename/move the file (maybe backup dir on another partition) -> make a copy instead
75  try {
76  // need to open all streams again
77  backupStream.exceptions(ios_base::failbit | ios_base::badbit);
78  originalStream.exceptions(ios_base::failbit | ios_base::badbit);
79  backupStream.open(backupPath, ios_base::in | ios_base::binary);
80  originalStream.open(originalPath, ios_base::out | ios_base::binary);
81  originalStream << backupStream.rdbuf();
82  originalStream.flush();
83  // TODO: callback for progress updates
84  } catch (const std::ios_base::failure &failure) {
85  throw std::ios_base::failure("Unable to restore original file from backup file \"" % backupPath % "\" after failure: " + failure.what());
86  }
87 }
88 
92 static bool isRelative(const std::string &path)
93 {
94  return path.empty() || (path.front() != '/' && (path.size() < 2 || path[1] != ':'));
95 }
96 
122 void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, NativeFileStream &originalStream,
123  NativeFileStream &backupStream)
124 {
125  // determine dirs
126  const auto backupDirRelative(isRelative(backupDir));
127  const auto originalDir(backupDirRelative ? BasicFileInfo::containingDirectory(originalPath) : string());
128 
129  // determine the backup path
130  for (unsigned int i = 0;; ++i) {
131  if (backupDir.empty()) {
132  if (i) {
133  backupPath = originalPath % '.' % i + ".bak";
134  } else {
135  backupPath = originalPath + ".bak";
136  }
137  } else {
138  const auto fileName(BasicFileInfo::fileName(originalPath, i));
139  if (i) {
140  const auto ext(BasicFileInfo::extension(originalPath));
141  if (backupDirRelative) {
142  backupPath = originalDir % '/' % backupDir % '/' % fileName % '.' % i + ext;
143  } else {
144  backupPath = backupDir % '/' % fileName % '.' % i + ext;
145  }
146  } else {
147  if (backupDirRelative) {
148  backupPath = originalDir % '/' % backupDir % '/' + fileName;
149  } else {
150  backupPath = backupDir % '/' + fileName;
151  }
152  }
153  }
154 
155  // test whether the backup path is still unused; otherwise continue loop
156 #ifdef PLATFORM_WINDOWS
157  if (GetFileAttributes(BasicFileInfo::pathForOpen(backupPath).data()) == INVALID_FILE_ATTRIBUTES) {
158 #else
159  struct stat backupStat;
160  if (stat(BasicFileInfo::pathForOpen(backupPath).data(), &backupStat)) {
161 #endif
162  break;
163  }
164  }
165 
166  // ensure original file is closed
167  if (originalStream.is_open()) {
168  originalStream.close();
169  }
170 
171  // rename original file
172  if (std::rename(BasicFileInfo::pathForOpen(originalPath).data(), BasicFileInfo::pathForOpen(backupPath).data())) {
173  // can't rename/move the file (maybe backup dir on another partition) -> make a copy instead
174  try {
175  backupStream.exceptions(ios_base::failbit | ios_base::badbit);
176  originalStream.exceptions(ios_base::failbit | ios_base::badbit);
177  // ensure backupStream is opened as write-only
178  if (backupStream.is_open()) {
179  backupStream.close();
180  }
181  backupStream.open(BasicFileInfo::pathForOpen(backupPath).data(), ios_base::out | ios_base::binary);
182  // ensure originalStream is opened with read permissions
183  originalStream.open(BasicFileInfo::pathForOpen(originalPath).data(), ios_base::in | ios_base::binary);
184  // do the actual copying
185  backupStream << originalStream.rdbuf();
186  backupStream.flush();
187  // streams are closed in the next try-block
188  // TODO: callback for progress updates
189  } catch (const std::ios_base::failure &failure) {
190  throw std::ios_base::failure(argsToString("Unable to rename original file before rewriting it: ", failure.what()));
191  }
192  }
193 
194  // manage streams
195  try {
196  // ensure there is no file associated with the originalStream object
197  if (originalStream.is_open()) {
198  originalStream.close();
199  }
200  // ensure there is no file associated with the backupStream object
201  if (backupStream.is_open()) {
202  backupStream.close();
203  }
204  // open backup stream
205  backupStream.exceptions(ios_base::failbit | ios_base::badbit);
206  backupStream.open(BasicFileInfo::pathForOpen(backupPath).data(), ios_base::in | ios_base::binary);
207  } catch (const std::ios_base::failure &failure) {
208  // can't open the new file
209  // -> try to re-rename backup file in the error case to restore previous state
210  if (std::rename(BasicFileInfo::pathForOpen(backupPath).data(), BasicFileInfo::pathForOpen(originalPath).data())) {
211  throw std::ios_base::failure("Unable to restore original file from backup file \"" % backupPath % "\" after failure: " + failure.what());
212  } else {
213  throw std::ios_base::failure(argsToString("Unable to open backup file: ", failure.what()));
214  }
215  }
216 }
217 
237 void handleFailureAfterFileModified(MediaFileInfo &fileInfo, const std::string &backupPath, NativeFileStream &outputStream,
238  NativeFileStream &backupStream, Diagnostics &diag, const std::string &context)
239 {
240  // reset the associated container in any case
241  if (fileInfo.container()) {
242  fileInfo.container()->reset();
243  }
244 
245  // re-throw the current exception
246  try {
247  throw;
248  } catch (const OperationAbortedException &) {
249  if (!backupPath.empty()) {
250  // a temp/backup file has been created -> restore original file
251  diag.emplace_back(DiagLevel::Information, "Rewriting the file to apply changed tag information has been aborted.", context);
252  try {
253  restoreOriginalFileFromBackupFile(fileInfo.path(), backupPath, outputStream, backupStream);
254  diag.emplace_back(DiagLevel::Information, "The original file has been restored.", context);
255  } catch (const std::ios_base::failure &failure) {
256  diag.emplace_back(DiagLevel::Critical, failure.what(), context);
257  }
258  } else {
259  diag.emplace_back(DiagLevel::Information, "Applying new tag information has been aborted.", context);
260  }
261  throw;
262 
263  } catch (const Failure &) {
264  if (!backupPath.empty()) {
265  // a temp/backup file has been created -> restore original file
266  diag.emplace_back(DiagLevel::Critical, "Rewriting the file to apply changed tag information failed.", context);
267  try {
268  restoreOriginalFileFromBackupFile(fileInfo.path(), backupPath, outputStream, backupStream);
269  diag.emplace_back(DiagLevel::Information, "The original file has been restored.", context);
270  } catch (const std::ios_base::failure &failure) {
271  diag.emplace_back(DiagLevel::Critical, failure.what(), context);
272  }
273  } else {
274  diag.emplace_back(DiagLevel::Critical, "Applying new tag information failed.", context);
275  }
276  throw;
277 
278  } catch (const std::ios_base::failure &) {
279  if (!backupPath.empty()) {
280  // a temp/backup file has been created -> restore original file
281  diag.emplace_back(DiagLevel::Critical, "An IO error occurred when rewriting the file to apply changed tag information.", context);
282  try {
283  restoreOriginalFileFromBackupFile(fileInfo.path(), backupPath, outputStream, backupStream);
284  diag.emplace_back(DiagLevel::Information, "The original file has been restored.", context);
285  } catch (const std::ios_base::failure &failure) {
286  diag.emplace_back(DiagLevel::Critical, failure.what(), context);
287  }
288  } else {
289  diag.emplace_back(DiagLevel::Critical, "An IO error occurred when applying tag information.", context);
290  }
291  throw;
292  }
293 }
294 
295 } // namespace BackupHelper
296 
297 } // namespace TagParser
virtual void reset()
Discards all parsing results.
const std::string & path() const
Returns the path 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:74
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
void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, NativeFileStream &originalStream, NativeFileStream &backupStream)
Creates a backup file for the specified file.
void restoreOriginalFileFromBackupFile(const std::string &originalPath, const std::string &backupPath, NativeFileStream &originalStream, NativeFileStream &backupStream)
Restores the original file from the specified backup file.
void handleFailureAfterFileModified(MediaFileInfo &fileInfo, const std::string &backupPath, NativeFileStream &outputStream, NativeFileStream &backupStream, Diagnostics &diag, const std::string &context)
Handles a failure/abort which occurred after the file has been modified.
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10