Tag Parser  10.1.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 
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  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 
94 static bool isRelative(const std::string &path)
95 {
96  return path.empty() || (path.front() != '/' && (path.size() < 2 || path[1] != ':'));
97 }
98 
124 void 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 
239 void 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.
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