Tag Parser  8.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 #include <c++utilities/io/catchiofailure.h>
8 
9 #ifdef PLATFORM_WINDOWS
10 #include <windows.h>
11 #else
12 #include <sys/stat.h>
13 #endif
14 
15 #include <cstdio>
16 #include <fstream>
17 #include <stdexcept>
18 #include <string>
19 
20 using namespace std;
21 using namespace ConversionUtilities;
22 using namespace IoUtilities;
23 
24 namespace TagParser {
25 
34 namespace BackupHelper {
35 
55  const std::string &originalPath, const std::string &backupPath, NativeFileStream &originalStream, NativeFileStream &backupStream)
56 {
57  // ensure the orignal stream is closed
58  if (originalStream.is_open()) {
59  originalStream.close();
60  }
61  // check wether backup file actually exists and close the backup stream afterwards
62  backupStream.exceptions(ios_base::goodbit);
63  backupStream.close();
64  backupStream.clear();
65  backupStream.open(backupPath, ios_base::in | ios_base::binary);
66  if (backupStream.is_open()) {
67  backupStream.close();
68  } else {
69  throwIoFailure("Backup/temporary file has not been created.");
70  }
71  // remove original file and restore backup
72  std::remove(originalPath.c_str());
73  if (std::rename(backupPath.c_str(), originalPath.c_str())) {
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  // TODO: callback for progress updates
83  } catch (...) {
84  catchIoFailure();
85  throwIoFailure(("Unable to restore original file from backup file \"" % backupPath + "\" after failure.").data());
86  }
87  }
88 }
89 
93 static bool isRelative(const std::string &path)
94 {
95  return path.empty() || (path.front() != '/' && (path.size() < 2 || path[1] != ':'));
96 }
97 
123 void createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, NativeFileStream &originalStream,
124  NativeFileStream &backupStream)
125 {
126  // determine dirs
127  const auto backupDirRelative(isRelative(backupDir));
128  const auto originalDir(backupDirRelative ? BasicFileInfo::containingDirectory(originalPath) : string());
129 
130  // determine the backup path
131  for (unsigned int i = 0;; ++i) {
132  if (backupDir.empty()) {
133  if (i) {
134  backupPath = originalPath % '.' % i + ".bak";
135  } else {
136  backupPath = originalPath + ".bak";
137  }
138  } else {
139  const auto fileName(BasicFileInfo::fileName(originalPath, i));
140  if (i) {
141  const auto ext(BasicFileInfo::extension(originalPath));
142  if (backupDirRelative) {
143  backupPath = originalDir % '/' % backupDir % '/' % fileName % '.' % i + ext;
144  } else {
145  backupPath = backupDir % '/' % fileName % '.' % i + ext;
146  }
147  } else {
148  if (backupDirRelative) {
149  backupPath = originalDir % '/' % backupDir % '/' + fileName;
150  } else {
151  backupPath = backupDir % '/' + fileName;
152  }
153  }
154  }
155 
156  // test whether the backup path is still unused; otherwise continue loop
157 #ifdef PLATFORM_WINDOWS
158  if (GetFileAttributes(backupPath.c_str()) == INVALID_FILE_ATTRIBUTES) {
159 #else
160  struct stat backupStat;
161  if (stat(backupPath.c_str(), &backupStat)) {
162 #endif
163  break;
164  }
165  }
166 
167  // ensure original file is closed
168  if (originalStream.is_open()) {
169  originalStream.close();
170  }
171 
172  // rename original file
173  if (std::rename(originalPath.c_str(), backupPath.c_str())) {
174  // can't rename/move the file (maybe backup dir on another partition) -> make a copy instead
175  try {
176  backupStream.exceptions(ios_base::failbit | ios_base::badbit);
177  originalStream.exceptions(ios_base::failbit | ios_base::badbit);
178  // ensure backupStream is opened as write-only
179  if (backupStream.is_open()) {
180  backupStream.close();
181  }
182  backupStream.open(backupPath, ios_base::out | ios_base::binary);
183  // ensure originalStream is opened with read permissions
184  originalStream.open(originalPath, ios_base::in | ios_base::binary);
185  // do the actual copying
186  backupStream << originalStream.rdbuf();
187  // streams are closed in the next try-block
188  // TODO: callback for progress updates
189  } catch (...) {
190  catchIoFailure();
191  throwIoFailure("Unable to rename original file before rewriting it.");
192  }
193  }
194 
195  // manage streams
196  try {
197  // ensure there is no file associated with the originalStream object
198  if (originalStream.is_open()) {
199  originalStream.close();
200  }
201  // ensure there is no file associated with the backupStream object
202  if (backupStream.is_open()) {
203  backupStream.close();
204  }
205  // open backup stream
206  backupStream.exceptions(ios_base::failbit | ios_base::badbit);
207  backupStream.open(backupPath, ios_base::in | ios_base::binary);
208  } catch (...) {
209  catchIoFailure();
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(backupPath.c_str(), originalPath.c_str())) {
213  throwIoFailure(("Unable to restore original file from backup file \"" % backupPath + "\" after failure.").data());
214  } else {
215  throwIoFailure("Unable to open backup file.");
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::Information, "The original file has been restored.", context);
257  } catch (...) {
258  diag.emplace_back(DiagLevel::Critical, catchIoFailure(), 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::Information, "The original file has been restored.", context);
272  } catch (...) {
273  diag.emplace_back(DiagLevel::Critical, catchIoFailure(), context);
274  }
275  } else {
276  diag.emplace_back(DiagLevel::Critical, "Applying new tag information failed.", context);
277  }
278  throw;
279 
280  } catch (...) {
281  const char *what = catchIoFailure();
282  if (!backupPath.empty()) {
283  // a temp/backup file has been created -> restore original file
284  diag.emplace_back(DiagLevel::Critical, "An IO error occurred when rewriting the file to apply changed tag information.", context);
285  try {
286  restoreOriginalFileFromBackupFile(fileInfo.path(), backupPath, outputStream, backupStream);
287  diag.emplace_back(DiagLevel::Information, "The original file has been restored.", context);
288  } catch (...) {
289  diag.emplace_back(DiagLevel::Critical, catchIoFailure(), context);
290  }
291  } else {
292  diag.emplace_back(DiagLevel::Critical, "An IO error occurred when applying tag information.", context);
293  }
294  throwIoFailure(what);
295  }
296 }
297 
298 } // namespace BackupHelper
299 
300 } // namespace TagParser
const std::string & path() const
Returns the path of the current file.
Definition: basicfileinfo.h:97
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.
virtual void reset()
Discards all parsing results.
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition: mediafileinfo.h:44
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 utility classes helping to read and write streams.
AbstractContainer * container() const
Returns the container for the current file.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
Definition: exceptions.h:46
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156