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