Tag Parser  6.5.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 "./mediafileinfo.h"
3 
4 #include <c++utilities/conversion/stringconversion.h>
5 #include <c++utilities/conversion/stringbuilder.h>
6 #include <c++utilities/io/catchiofailure.h>
7 
8 #ifdef PLATFORM_WINDOWS
9 # include <windows.h>
10 #else
11 # include <sys/stat.h>
12 #endif
13 
14 #include <string>
15 #include <fstream>
16 #include <cstdio>
17 #include <stdexcept>
18 
19 using namespace std;
20 using namespace ConversionUtilities;
21 using namespace IoUtilities;
22 
23 namespace Media {
24 
33 namespace BackupHelper {
34 
43 string &backupDirectory()
44 {
45  static string backupDir;
46  return backupDir;
47 }
48 
67 void restoreOriginalFileFromBackupFile(const std::string &originalPath, const std::string &backupPath, NativeFileStream &originalStream, NativeFileStream &backupStream)
68 {
69  // ensure the orignal stream is closed
70  if(originalStream.is_open()) {
71  originalStream.close();
72  }
73  // check wether backup file actually exists and close the backup stream afterwards
74  backupStream.exceptions(ios_base::goodbit);
75  backupStream.close();
76  backupStream.clear();
77  backupStream.open(backupPath, ios_base::in | ios_base::binary);
78  if(backupStream.is_open()) {
79  backupStream.close();
80  } else {
81  throwIoFailure("Backup/temporary file has not been created.");
82  }
83  // remove original file and restore backup
84  std::remove(originalPath.c_str());
85  if(std::rename(backupPath.c_str(), originalPath.c_str())) {
86  // can't rename/move the file (maybe backup dir on another partition) -> make a copy instead
87  try {
88  // need to open all streams again
89  backupStream.exceptions(ios_base::failbit | ios_base::badbit);
90  originalStream.exceptions(ios_base::failbit | ios_base::badbit);
91  backupStream.open(backupPath, ios_base::in | ios_base::binary);
92  originalStream.open(originalPath, ios_base::out | ios_base::binary);
93  originalStream << backupStream.rdbuf();
94  // TODO: callback for progress updates
95  } catch(...) {
96  catchIoFailure();
97  throwIoFailure(("Unable to restore original file from backup file \"" % backupPath + "\" after failure.").data());
98  }
99  }
100 }
101 
105 static bool isRelative(const std::string &path)
106 {
107  return path.empty() || (path.front() != '/' && (path.size() < 2 || path[1] != ':'));
108 }
109 
133 void createBackupFile(const std::string &originalPath, std::string &backupPath, NativeFileStream &originalStream, NativeFileStream &backupStream)
134 {
135  // determine dirs
136  const auto &backupDir(backupDirectory());
137  const auto backupDirRelative(isRelative(backupDir));
138  const auto originalDir(backupDirRelative ? BasicFileInfo::containingDirectory(originalPath) : string());
139 
140  // determine the backup path
141  for(unsigned int i = 0; ; ++i) {
142  if(backupDir.empty()) {
143  if(i) {
144  backupPath = originalPath % '.' % i + ".bak";
145  } else {
146  backupPath = originalPath + ".bak";
147  }
148  } else {
149  const auto fileName(BasicFileInfo::fileName(originalPath, i));
150  if(i) {
151  const auto ext(BasicFileInfo::extension(originalPath));
152  if(backupDirRelative) {
153  backupPath = originalDir % '/' % backupDir % '/' % fileName % '.' % i + ext;
154  } else {
155  backupPath = backupDir % '/' % fileName % '.' % i + ext;
156  }
157  } else {
158  if(backupDirRelative) {
159  backupPath = originalDir % '/' % backupDir % '/' + fileName;
160  } else {
161  backupPath = backupDir % '/' + fileName;
162  }
163  }
164 
165  }
166 
167  // test whether the backup path is still unused; otherwise continue loop
168 #ifdef PLATFORM_WINDOWS
169  if(GetFileAttributes(backupPath.c_str()) == INVALID_FILE_ATTRIBUTES) {
170 #else
171  struct stat backupStat;
172  if(stat(backupPath.c_str(), &backupStat)) {
173 #endif
174  break;
175  }
176  }
177 
178  // ensure original file is closed
179  if(originalStream.is_open()) {
180  originalStream.close();
181  }
182 
183  // rename original file
184  if(std::rename(originalPath.c_str(), backupPath.c_str())) {
185  // can't rename/move the file (maybe backup dir on another partition) -> make a copy instead
186  try {
187  backupStream.exceptions(ios_base::failbit | ios_base::badbit);
188  originalStream.exceptions(ios_base::failbit | ios_base::badbit);
189  // ensure backupStream is opened as write-only
190  if(backupStream.is_open()) {
191  backupStream.close();
192  }
193  backupStream.open(backupPath, ios_base::out | ios_base::binary);
194  // ensure originalStream is opened with read permissions
195  originalStream.open(originalPath, ios_base::in | ios_base::binary);
196  // do the actual copying
197  backupStream << originalStream.rdbuf();
198  // streams are closed in the next try-block
199  // TODO: callback for progress updates
200  } catch(...) {
201  catchIoFailure();
202  throwIoFailure("Unable to rename original file before rewriting it.");
203  }
204  }
205 
206  // manage streams
207  try {
208  // ensure there is no file associated with the originalStream object
209  if(originalStream.is_open()) {
210  originalStream.close();
211  }
212  // ensure there is no file associated with the backupStream object
213  if(backupStream.is_open()) {
214  backupStream.close();
215  }
216  // open backup stream
217  backupStream.exceptions(ios_base::failbit | ios_base::badbit);
218  backupStream.open(backupPath, ios_base::in | ios_base::binary);
219  } catch(...) {
220  catchIoFailure();
221  // can't open the new file
222  // -> try to re-rename backup file in the error case to restore previous state
223  if(std::rename(backupPath.c_str(), originalPath.c_str())) {
224  throwIoFailure(("Unable to restore original file from backup file \"" % backupPath + "\" after failure.").data());
225  } else {
226  throwIoFailure("Unable to open backup file.");
227  }
228  }
229 }
230 
249 void handleFailureAfterFileModified(MediaFileInfo &fileInfo, const std::string &backupPath, NativeFileStream &outputStream, NativeFileStream &backupStream, const std::string &context)
250 {
251  // reset the associated container in any case
252  if(fileInfo.container()) {
253  fileInfo.container()->reset();
254  }
255 
256  // re-throw the current exception
257  try {
258  throw;
259  } catch(const OperationAbortedException &) {
260  if(!backupPath.empty()) {
261  // a temp/backup file has been created -> restore original file
262  fileInfo.addNotification(NotificationType::Information, "Rewriting the file to apply changed tag information has been aborted.", context);
263  try {
264  restoreOriginalFileFromBackupFile(fileInfo.path(), backupPath, outputStream, backupStream);
265  fileInfo.addNotification(NotificationType::Information, "The original file has been restored.", context);
266  } catch(...) {
267  fileInfo.addNotification(NotificationType::Critical, catchIoFailure(), context);
268  }
269  } else {
270  fileInfo.addNotification(NotificationType::Information, "Applying new tag information has been aborted.", context);
271  }
272  throw;
273 
274  } catch(const Failure &) {
275  if(!backupPath.empty()) {
276  // a temp/backup file has been created -> restore original file
277  fileInfo.addNotification(NotificationType::Critical, "Rewriting the file to apply changed tag information failed.", context);
278  try {
279  restoreOriginalFileFromBackupFile(fileInfo.path(), backupPath, outputStream, backupStream);
280  fileInfo.addNotification(NotificationType::Information, "The original file has been restored.", context);
281  } catch(...) {
282  fileInfo.addNotification(NotificationType::Critical, catchIoFailure(), context);
283  }
284  } else {
285  fileInfo.addNotification(NotificationType::Critical, "Applying new tag information failed.", context);
286  }
287  throw;
288 
289  } catch(...) {
290  const char *what = catchIoFailure();
291  if(!backupPath.empty()) {
292  // a temp/backup file has been created -> restore original file
293  fileInfo.addNotification(NotificationType::Critical, "An IO error occured when rewriting the file to apply changed tag information.", context);
294  try {
295  restoreOriginalFileFromBackupFile(fileInfo.path(), backupPath, outputStream, backupStream);
296  fileInfo.addNotification(NotificationType::Information, "The original file has been restored.", context);
297  } catch(...) {
298  fileInfo.addNotification(NotificationType::Critical, catchIoFailure(), context);
299  }
300  } else {
301  fileInfo.addNotification(NotificationType::Critical, "An IO error occured when applying tag information.", context);
302  }
303  throwIoFailure(what);
304  }
305 }
306 
307 }
308 
309 }
void restoreOriginalFileFromBackupFile(const std::string &originalPath, const std::string &backupPath, NativeFileStream &originalStream, NativeFileStream &backupStream)
Restores the original file from the specified backup file.
AbstractContainer * container() const
Returns the container for the current file.
STL namespace.
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
Definition: exceptions.h:43
void addNotification(const Notification &notification)
This method is meant to be called by the derived class to add a notification.
void createBackupFile(const std::string &originalPath, std::string &backupPath, NativeFileStream &originalStream, NativeFileStream &backupStream)
Creates a backup file for the specified file.
Contains utility classes helping to read and write streams.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
const std::string & path() const
Returns the path of the current file.
Definition: basicfileinfo.h:98
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:53
Contains all classes and functions of the TagInfo library.
Definition: exceptions.h:9
void handleFailureAfterFileModified(MediaFileInfo &fileInfo, const std::string &backupPath, NativeFileStream &outputStream, NativeFileStream &backupStream, const std::string &context)
Handles a failure/abort which occured after the file has been modified.
TAG_PARSER_EXPORT std::string & backupDirectory()
Returns the directory used to store backup files.