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