Tag Parser  6.4.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 
41 string &backupDirectory()
42 {
43  static string backupDir;
44  return backupDir;
45 }
46 
65 void restoreOriginalFileFromBackupFile(const std::string &originalPath, const std::string &backupPath, NativeFileStream &originalStream, NativeFileStream &backupStream)
66 {
67  // ensure the orignal stream is closed
68  if(originalStream.is_open()) {
69  originalStream.close();
70  }
71  // check wether backup file actually exists and close the backup stream afterwards
72  backupStream.exceptions(ios_base::goodbit);
73  backupStream.close();
74  backupStream.clear();
75  backupStream.open(backupPath, ios_base::in | ios_base::binary);
76  if(backupStream.is_open()) {
77  backupStream.close();
78  } else {
79  throwIoFailure("Backup/temporary file has not been created.");
80  }
81  // remove original file and restore backup
82  std::remove(originalPath.c_str());
83  if(std::rename(backupPath.c_str(), originalPath.c_str()) != 0) { // restore backup
84  // unable to move the file
85  try { // to copy
86  // need to open all streams again
87  backupStream.exceptions(ios_base::failbit | ios_base::badbit);
88  originalStream.exceptions(ios_base::failbit | ios_base::badbit);
89  backupStream.open(backupPath, ios_base::in | ios_base::binary);
90  originalStream.open(originalPath, ios_base::out | ios_base::binary);
91  originalStream << backupStream.rdbuf();
92  // TODO: callback for progress updates
93  } catch(...) {
94  catchIoFailure();
95  throwIoFailure(("Unable to restore original file from backup file \"" % backupPath + "\" after failure.").data());
96  }
97  }
98 }
99 
123 void createBackupFile(const std::string &originalPath, std::string &backupPath, NativeFileStream &originalStream, NativeFileStream &backupStream)
124 {
125  // determine the backup path
126  const string &backupDir = backupDirectory();
127 #ifndef PLATFORM_WINDOWS
128  struct stat backupStat;
129 #endif
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 string fileName = BasicFileInfo::fileName(originalPath, i);
139  if(i) {
140  const string ext = BasicFileInfo::extension(originalPath);
141  if(backupDir.at(0) != '/' && (backupDir.size() < 2 || backupDir.at(1) != ':')) {
142  // backupDir is a relative path
143  backupPath = BasicFileInfo::containingDirectory(originalPath);
144  backupPath += '/';
145  backupPath += backupDir;
146  backupPath += '/';
147  backupPath += fileName;
148  backupPath += '.';
149  backupPath += numberToString(i);
150  backupPath += ext;
151  } else {
152  // backupDir is an absolute path
153  backupPath = backupDir;
154  backupPath += '/';
155  backupPath += fileName;
156  backupPath += '.';
157  backupPath += numberToString(i);
158  backupPath += ext;
159  }
160  } else {
161  if(backupDir.at(0) != '/' && (backupDir.size() < 2 || backupDir.at(1) != ':')) {
162  // backupDir is a relative path
163  backupPath = BasicFileInfo::containingDirectory(originalPath);
164  backupPath += '/';
165  backupPath += backupDir;
166  backupPath += '/';
167  backupPath += fileName;
168  } else {
169  // backupDir is an absolute path
170  backupPath = backupDir;
171  backupPath += '/';
172  backupPath += fileName;
173  }
174  }
175 
176  }
177  // test whether the backup file already exists
178 #ifdef PLATFORM_WINDOWS
179  if(GetFileAttributes(backupPath.c_str()) == INVALID_FILE_ATTRIBUTES) {
180 #else
181  if(stat(backupPath.c_str(), &backupStat)) {
182 #endif
183  break;
184  } // else: the backup file already exists -> find another file name
185  }
186 
187  // ensure original file is closed
188  if(originalStream.is_open()) {
189  originalStream.close();
190  }
191  // rename original file
192  if(std::rename(originalPath.c_str(), backupPath.c_str()) != 0) {
193  // can't rename/move the file
194  try { // to copy
195  backupStream.exceptions(ios_base::failbit | ios_base::badbit);
196  originalStream.exceptions(ios_base::failbit | ios_base::badbit);
197  // ensure backupStream is opened as write-only
198  if(backupStream.is_open()) {
199  backupStream.close();
200  }
201  backupStream.open(backupPath, ios_base::out | ios_base::binary);
202  // ensure originalStream is opened with read permissions
203  originalStream.open(originalPath, ios_base::in | ios_base::binary);
204  // do the actual copying
205  backupStream << originalStream.rdbuf();
206  // streams are closed in the next try-block
207  // TODO: callback for progress updates
208  } catch(...) {
209  catchIoFailure();
210  throwIoFailure("Unable to rename original file before rewriting it.");
211  }
212  }
213  try {
214  // ensure there is not file associated with the originalStream object
215  if(originalStream.is_open()) {
216  originalStream.close();
217  }
218  // ensure there is no file associated with the backupStream object
219  if(backupStream.is_open()) {
220  backupStream.close();
221  }
222  // open backup stream
223  backupStream.exceptions(ios_base::failbit | ios_base::badbit);
224  backupStream.open(backupPath, ios_base::in | ios_base::binary);
225  } catch(...) {
226  catchIoFailure();
227  // can't open the new file
228  // -> try to re-rename backup file in the error case to restore previous state
229  if(std::rename(backupPath.c_str(), originalPath.c_str()) != 0) {
230  throwIoFailure(("Unable to restore original file from backup file \"" % backupPath + "\" after failure.").data());
231  } else {
232  throwIoFailure("Unable to open backup file.");
233  }
234  }
235 }
236 
255 void handleFailureAfterFileModified(MediaFileInfo &fileInfo, const std::string &backupPath, NativeFileStream &outputStream, NativeFileStream &backupStream, const std::string &context)
256 {
257  // reset the associated container in any case
258  if(fileInfo.container()) {
259  fileInfo.container()->reset();
260  }
261 
262  // re-throw the current exception
263  try {
264  throw;
265  } catch(const OperationAbortedException &) {
266  if(!backupPath.empty()) {
267  // a temp/backup file has been created -> restore original file
268  fileInfo.addNotification(NotificationType::Information, "Rewriting the file to apply changed tag information has been aborted.", context);
269  try {
270  restoreOriginalFileFromBackupFile(fileInfo.path(), backupPath, outputStream, backupStream);
271  fileInfo.addNotification(NotificationType::Information, "The original file has been restored.", context);
272  } catch(...) {
273  fileInfo.addNotification(NotificationType::Critical, catchIoFailure(), context);
274  }
275  } else {
276  fileInfo.addNotification(NotificationType::Information, "Applying new tag information has been aborted.", context);
277  }
278  throw;
279 
280  } catch(const Failure &) {
281  if(!backupPath.empty()) {
282  // a temp/backup file has been created -> restore original file
283  fileInfo.addNotification(NotificationType::Critical, "Rewriting the file to apply changed tag information failed.", context);
284  try {
285  restoreOriginalFileFromBackupFile(fileInfo.path(), backupPath, outputStream, backupStream);
286  fileInfo.addNotification(NotificationType::Information, "The original file has been restored.", context);
287  } catch(...) {
288  fileInfo.addNotification(NotificationType::Critical, catchIoFailure(), context);
289  }
290  } else {
291  fileInfo.addNotification(NotificationType::Critical, "Applying new tag information failed.", context);
292  }
293  throw;
294 
295  } catch(...) {
296  const char *what = catchIoFailure();
297  if(!backupPath.empty()) {
298  // a temp/backup file has been created -> restore original file
299  fileInfo.addNotification(NotificationType::Critical, "An IO error occured when rewriting the file to apply changed tag information.", context);
300  try {
301  restoreOriginalFileFromBackupFile(fileInfo.path(), backupPath, outputStream, backupStream);
302  fileInfo.addNotification(NotificationType::Information, "The original file has been restored.", context);
303  } catch(...) {
304  fileInfo.addNotification(NotificationType::Critical, catchIoFailure(), context);
305  }
306  } else {
307  fileInfo.addNotification(NotificationType::Critical, "An IO error occured when applying tag information.", context);
308  }
309  throwIoFailure(what);
310  }
311 }
312 
313 }
314 
315 }
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.