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