Tag Parser  6.4.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
utils.cpp
Go to the documentation of this file.
1 #include "./helper.h"
2 
3 #include "../size.h"
4 #include "../statusprovider.h"
5 #include "../tagtarget.h"
6 #include "../signature.h"
7 #include "../margin.h"
8 #include "../aspectratio.h"
9 #include "../mediaformat.h"
10 #include "../mediafileinfo.h"
11 #include "../exceptions.h"
12 #include "../backuphelper.h"
13 
14 #include <c++utilities/io/catchiofailure.h>
15 #include <c++utilities/tests/testutils.h>
16 using namespace TestUtilities;
17 
18 #include <cppunit/TestFixture.h>
19 #include <cppunit/extensions/HelperMacros.h>
20 
21 #include <cstdio>
22 
23 using namespace std;
24 using namespace Media;
25 using namespace IoUtilities;
26 using namespace TestUtilities::Literals;
27 
28 using namespace CPPUNIT_NS;
29 
34 {
35 public:
37 };
38 
40 {
41 }
42 
46 class UtilitiesTests : public TestFixture {
47  CPPUNIT_TEST_SUITE(UtilitiesTests);
48  CPPUNIT_TEST(testSize);
49  CPPUNIT_TEST(testStatusProvider);
50  CPPUNIT_TEST(testTagTarget);
51  CPPUNIT_TEST(testSignature);
52  CPPUNIT_TEST(testMargin);
53  CPPUNIT_TEST(testAspectRatio);
54  CPPUNIT_TEST(testMediaFormat);
55 #ifdef PLATFORM_UNIX
56  CPPUNIT_TEST(testBackupFile);
57 #endif
58  CPPUNIT_TEST_SUITE_END();
59 
60 public:
61  void setUp();
62  void tearDown();
63 
64  void testSize();
65  void testStatusProvider();
66  void testTagTarget();
67  void testSignature();
68  void testMargin();
69  void testAspectRatio();
70  void testMediaFormat();
71 #ifdef PLATFORM_UNIX
72  void testBackupFile();
73 #endif
74 };
75 
77 
79 {
80 }
81 
83 {
84 }
85 
87 {
88  static_assert(Size().isNull(), "Size::isNull()");
89  static_assert(!Size(3, 4).isNull(), "Size::isNull()");
90  static_assert(Size(3, 4).resolution() == 12, "Size::resolution");
91 
92  Size size(1920, 1080);
93  CPPUNIT_ASSERT_EQUAL("width: 1920, height: 1080"s, size.toString());
94  CPPUNIT_ASSERT_EQUAL("1080p"s, string(size.abbreviation()));
95  size.setWidth(1280);
96  size.setHeight(720);
97  CPPUNIT_ASSERT_EQUAL("720p"s, string(size.abbreviation()));
98 }
99 
101 {
102  const string context("unit tests");
103  TestStatusProvider status, status2;
104 
105  // notifications
106  CPPUNIT_ASSERT(!status.hasNotifications());
107  CPPUNIT_ASSERT_EQUAL(NotificationType::None, status.worstNotificationType());
108  status.addNotification(NotificationType::Debug, "debug notification", context);
109  CPPUNIT_ASSERT_EQUAL(NotificationType::Debug, status.worstNotificationType());
110  CPPUNIT_ASSERT(!status.hasCriticalNotifications());
111  status.addNotification(NotificationType::Warning, "warning", context);
112  CPPUNIT_ASSERT_EQUAL(NotificationType::Warning, status.worstNotificationType());
113  CPPUNIT_ASSERT_EQUAL("warning"s, status.notifications().back().message());
114  CPPUNIT_ASSERT(!status.hasCriticalNotifications());
115  status.addNotification(NotificationType::Critical, "error", context);
116  CPPUNIT_ASSERT_EQUAL(NotificationType::Critical, status.worstNotificationType());
117  CPPUNIT_ASSERT(status.hasCriticalNotifications());
118  CPPUNIT_ASSERT_EQUAL(3_st, status.notifications().size());
119  CPPUNIT_ASSERT(status.hasNotifications());
120  status2.addNotifications(status);
121  status.invalidateNotifications();
122  CPPUNIT_ASSERT(!status.hasNotifications());
123  CPPUNIT_ASSERT(!status.hasCriticalNotifications());
124  CPPUNIT_ASSERT_EQUAL(3_st, status2.notifications().size());
125  status.addNotification(status2.notifications().back());
126  CPPUNIT_ASSERT(status.hasCriticalNotifications());
127 
128  // status and percentage
129  CPPUNIT_ASSERT_EQUAL(string(), status.currentStatus());
130  CPPUNIT_ASSERT_EQUAL(0.0, status.currentPercentage());
131  CPPUNIT_ASSERT(!status.isAborted());
132  bool statusUpdateReceived = false, firstStatusUpdate = true;
133  const auto callbackId = status.registerCallback([&status, &statusUpdateReceived, &firstStatusUpdate] (StatusProvider &sender) {
134  CPPUNIT_ASSERT(&status == &sender);
135  if(firstStatusUpdate) {
136  CPPUNIT_ASSERT_EQUAL("test"s, sender.currentStatus());
137  CPPUNIT_ASSERT_EQUAL(0.5, sender.currentPercentage());
138  firstStatusUpdate = false;
139  }
140  sender.tryToAbort();
141  statusUpdateReceived = true;
142  });
143  status.updateStatus("test", 0.5);
144  CPPUNIT_ASSERT_MESSAGE("status update for updated status received", statusUpdateReceived);
145  CPPUNIT_ASSERT(status.isAborted());
146  statusUpdateReceived = false;
147  status.updatePercentage(0.625);
148  CPPUNIT_ASSERT_MESSAGE("status update for updated percentage received", statusUpdateReceived);
149  statusUpdateReceived = false;
150  status.addNotification(status2.notifications().front());
151  CPPUNIT_ASSERT_MESSAGE("status update for new notification received", statusUpdateReceived);
152  statusUpdateReceived = false;
153  status.unregisterCallback(callbackId);
154  status.updatePercentage(0.65);
155  CPPUNIT_ASSERT_MESSAGE("no status update received after callback unregistered", !statusUpdateReceived);
156 
157  // forwarding
158  TestStatusProvider forwardReceiver;
159  status.forwardStatusUpdateCalls(&forwardReceiver);
160  statusUpdateReceived = false;
161  forwardReceiver.registerCallback([&status, &statusUpdateReceived] (StatusProvider &sender) {
162  CPPUNIT_ASSERT(&status == &sender);
163  CPPUNIT_ASSERT_EQUAL("test2"s, sender.currentStatus());
164  CPPUNIT_ASSERT_EQUAL(0.75, sender.currentPercentage());
165  statusUpdateReceived = true;
166  });
167  status.updateStatus("test2", 0.75);
168  CPPUNIT_ASSERT(statusUpdateReceived);
169 }
170 
172 {
173  TagTarget target;
174  CPPUNIT_ASSERT(target.isEmpty());
175  CPPUNIT_ASSERT_EQUAL_MESSAGE("default level is 50", 50ul, target.level());
176  CPPUNIT_ASSERT_EQUAL("level 50"s, target.toString(TagTargetLevel::Unspecified));
177  target = TagTarget(30, {1, 2, 3}, {4}, {5, 6}, {7, 8, 9});
178  CPPUNIT_ASSERT(!target.isEmpty());
179  const auto mapping = [] (uint64 level) {
180  return level == 30 ? TagTargetLevel::Track : TagTargetLevel::Unspecified;
181  };
182  CPPUNIT_ASSERT_EQUAL("level 30 'track, song, chapter', track 1, track 2, track 3, chapter 4, edition 5, edition 6, attachment 7, attachment 8, attachment 9"s, target.toString(mapping));
183  target.setLevel(40);
184  CPPUNIT_ASSERT_EQUAL("level 40, track 1, track 2, track 3, chapter 4, edition 5, edition 6, attachment 7, attachment 8, attachment 9"s, target.toString(mapping));
185  target.setLevelName("test");
186  CPPUNIT_ASSERT_EQUAL("level 40 'test', track 1, track 2, track 3, chapter 4, edition 5, edition 6, attachment 7, attachment 8, attachment 9"s, target.toString(mapping));
187  CPPUNIT_ASSERT(target == TagTarget(40, {1, 2, 3}, {4}, {5, 6}, {7, 8, 9}));
188  target.clear();
189  CPPUNIT_ASSERT(target.isEmpty());
190 
191 }
192 
194 {
195  const unsigned char xzHead[12] = {
196  0xfd, 0x37, 0x7a, 0x58,
197  0x5a, 0x00, 0x00, 0x04,
198  0xe6, 0xd6, 0xb4, 0x46
199  };
200 
201  // truncated buffer
202  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Unknown, parseSignature(reinterpret_cast<const char *>(xzHead), 3));
203  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Unknown, parseSignature(reinterpret_cast<const char *>(xzHead), 2));
204  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Unknown, parseSignature(reinterpret_cast<const char *>(xzHead), 0));
205 
206  const auto containerFormat = parseSignature(reinterpret_cast<const char *>(xzHead), sizeof(xzHead));
207  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Xz, containerFormat);
208  CPPUNIT_ASSERT_EQUAL("xz compressed file"s, string(containerFormatName(containerFormat)));
209  CPPUNIT_ASSERT_EQUAL("xz"s, string(containerFormatAbbreviation(containerFormat)));
210  CPPUNIT_ASSERT_EQUAL(string(), string(containerFormatSubversion(containerFormat)));
211 }
212 
214 {
215  static_assert(Margin().isNull(), "empty margin");
216  static_assert(!Margin(0, 2).isNull(), "non-empty margin");
217 
218  CPPUNIT_ASSERT_EQUAL("top: 1; left: 2; bottom: 3; right: 4"s, Margin(1, 2, 3, 4).toString());
219 }
220 
222 {
223  static_assert(!AspectRatio().isValid(), "invalid aspect ratio");
224  static_assert(AspectRatio(16, 9).isValid(), "valid aspect ratio");
225  static_assert(AspectRatio(16, 9).isExtended(), "extended aspect ratio");
226 
227  const AspectRatio ratio(4);
228  CPPUNIT_ASSERT_EQUAL(static_cast<uint16>(16), ratio.numerator);
229  CPPUNIT_ASSERT_EQUAL(static_cast<uint16>(11), ratio.denominator);
230  const AspectRatio ratio2(77);
231  CPPUNIT_ASSERT_EQUAL(static_cast<uint16>(0), ratio2.numerator);
232  CPPUNIT_ASSERT_EQUAL(static_cast<uint16>(0), ratio2.denominator);
233 }
234 
236 {
237  // unspecific format
239  CPPUNIT_ASSERT_EQUAL("Advanced Audio Coding"s, string(aac.name()));
240  CPPUNIT_ASSERT_EQUAL("AAC"s, string(aac.abbreviation()));
241  CPPUNIT_ASSERT_EQUAL("AAC"s, string(aac.shortAbbreviation()));
242 
243  // specific format
245  CPPUNIT_ASSERT(aac == GeneralMediaFormat::Aac);
246  CPPUNIT_ASSERT(aac != GeneralMediaFormat::Mpeg1Audio);
247  CPPUNIT_ASSERT_EQUAL("Advanced Audio Coding Low Complexity Profile"s, string(aac.name()));
248  CPPUNIT_ASSERT_EQUAL("MPEG-4 AAC-LC"s, string(aac.abbreviation()));
249  CPPUNIT_ASSERT_EQUAL("HE-AAC"s, string(aac.shortAbbreviation()));
250  CPPUNIT_ASSERT_EQUAL("Spectral Band Replication / HE-AAC"s, string(aac.extensionName()));
251 }
252 
253 #ifdef PLATFORM_UNIX
254 void UtilitiesTests::testBackupFile()
255 {
256  using namespace BackupHelper;
257 
258  // ensure backup directory is empty, so backups will be created in the same directory
259  // as the original file
260  backupDirectory().clear();
261 
262  // setup testfile
263  MediaFileInfo file(workingCopyPath("unsupported.bin"));
264  const string workingDir(file.containingDirectory());
265  file.open();
266 
267  // create backup file
268  string backupPath1, backupPath2;
269  NativeFileStream backupStream1, backupStream2;
270  createBackupFile(file.path(), backupPath1, file.stream(), backupStream1);
271  CPPUNIT_ASSERT_EQUAL(workingDir + "/unsupported.bin.bak", backupPath1);
272 
273  // recreate original file
274  file.stream().open(file.path(), ios_base::out);
275  file.stream() << "test1" << endl;
276 
277  // create a 2nd backup which should not override the first one
278  createBackupFile(file.path(), backupPath2, file.stream(), backupStream2);
279  CPPUNIT_ASSERT_EQUAL(workingDir + "/unsupported.bin.1.bak", backupPath2);
280 
281  // get rid of 2nd backup, recreate original file
282  backupStream2.close();
283  remove(backupPath2.data());
284  file.stream().open(file.path(), ios_base::out);
285  file.stream() << "test2" << endl;
286 
287  // create backup under another location
288  backupDirectory() = workingDir + "/bak";
289  try {
290  createBackupFile(file.path(), backupPath2, file.stream(), backupStream2);
291  CPPUNIT_FAIL("renaming failed because backup dir does not exist");
292  } catch(...) {
293  const char *what = catchIoFailure();
294  CPPUNIT_ASSERT(strstr(what, "Unable to rename original file before rewriting it."));
295  }
296  backupStream2.clear();
297  workingCopyPathMode("bak/unsupported.bin", WorkingCopyMode::NoCopy);
298  createBackupFile(file.path(), backupPath2, file.stream(), backupStream2);
299  CPPUNIT_ASSERT_EQUAL(workingDir + "/bak/unsupported.bin", backupPath2);
300 
301  // get rid of 2nd backup (again)
302  backupStream2.close();
303  remove(backupPath2.data());
304  remove(backupDirectory().data());
305 
306  // should be able to use backup stream, eg. seek to the end
307  backupStream1.seekg(0, ios_base::end);
308  CPPUNIT_ASSERT_EQUAL(41_st, static_cast<size_t>(backupStream1.tellg()));
309 
310  // restore backup
311  restoreOriginalFileFromBackupFile(file.path(), backupPath1, file.stream(), backupStream1);
312 
313  // check restored backup
314  file.open(true);
315  file.stream().seekg(0x1D);
316  CPPUNIT_ASSERT_EQUAL(static_cast<ios::int_type>(0x34), file.stream().get());
317  file.close();
318 
319  CPPUNIT_ASSERT_MESSAGE("file has no critical notifications yet", !file.hasCriticalNotifications());
320 
321  // reset backup dir again
322  backupDirectory().clear();
323 
324  // restore after user aborted
325  createBackupFile(file.path(), backupPath1, file.stream(), backupStream1);
326  try {
328  } catch(...) {
329  CPPUNIT_ASSERT_THROW(handleFailureAfterFileModified(file, backupPath1, file.stream(), backupStream1, "test"), OperationAbortedException);
330  }
331  CPPUNIT_ASSERT(!file.hasCriticalNotifications());
332  CPPUNIT_ASSERT(file.hasNotifications());
333  CPPUNIT_ASSERT_EQUAL("Rewriting the file to apply changed tag information has been aborted."s, file.notifications().front().message());
334  CPPUNIT_ASSERT_EQUAL("The original file has been restored."s, file.notifications().back().message());
336 
337  // restore after error
338  createBackupFile(file.path(), backupPath1, file.stream(), backupStream1);
339  try {
340  throw Failure();
341  } catch(...) {
342  CPPUNIT_ASSERT_THROW(handleFailureAfterFileModified(file, backupPath1, file.stream(), backupStream1, "test"), Failure);
343  }
344  CPPUNIT_ASSERT(file.hasCriticalNotifications());
345  CPPUNIT_ASSERT_EQUAL("Rewriting the file to apply changed tag information failed."s, file.notifications().front().message());
346  CPPUNIT_ASSERT_EQUAL("The original file has been restored."s, file.notifications().back().message());
348 
349  // restore after io failure
350  createBackupFile(file.path(), backupPath1, file.stream(), backupStream1);
351  try {
352  throwIoFailure("simulated IO failure");
353  } catch(...) {
354  try {
355  handleFailureAfterFileModified(file, backupPath1, file.stream(), backupStream1, "test");
356  CPPUNIT_FAIL("IO failure rethrown");
357  } catch(...) {
358  catchIoFailure();
359  }
360  }
361  CPPUNIT_ASSERT(file.hasCriticalNotifications());
362  CPPUNIT_ASSERT_EQUAL("An IO error occured when rewriting the file to apply changed tag information."s, file.notifications().front().message());
363  CPPUNIT_ASSERT_EQUAL("The original file has been restored."s, file.notifications().back().message());
365 
366  remove(file.path().data());
367 }
368 #endif
bool hasCriticalNotifications() const
Returns an indication whether there are critical notifications for the current object.
size_t registerCallback(CallbackFunction callback)
Registers a callback function.
bool isAborted() const
Returns an indication whether the current operation should be aborted.
const char * name() const
Returns the name of the media format as C-style string.
Definition: mediaformat.cpp:17
const NotificationList & notifications() const
Returns notifications for the current object.
double currentPercentage() const
Returns the progress percentage of the current object.
The Size class defines the size of a two-dimensional object using integer point precision.
Definition: size.h:16
void tearDown()
Definition: utils.cpp:82
The AspectRatio struct defines an aspect ratio.
Definition: aspectratio.h:10
bool isEmpty() const
Returns an indication whether the target is empty.
Definition: tagtarget.h:177
NotificationType worstNotificationType() const
Returns the worst notification type.
void setWidth(uint32 value)
Sets the width.
Definition: size.h:74
TAG_PARSER_EXPORT const char * containerFormatAbbreviation(ContainerFormat containerFormat, MediaType mediaType=MediaType::Unknown, unsigned int version=0)
Returns the abbreviation of the container format as C-style string considering the specified media ty...
Definition: signature.cpp:243
void unregisterCallback(size_t id)
Unregisters a previously registered callback function whith the specified id.
static std::string containingDirectory(const std::string &path)
Returns the path of the directory containing the given file.
TAG_PARSER_EXPORT void createBackupFile(const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, IoUtilities::NativeFileStream &outputStream, IoUtilities::NativeFileStream &backupStream, const std::string &context="making file")
STL namespace.
const char * extensionName() const
Returns the abbreviation of the media format as C-style string.
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.
TAG_PARSER_EXPORT const char * containerFormatSubversion(ContainerFormat containerFormat)
Returns the subversion of the container format as C-style string.
Definition: signature.cpp:417
void testMargin()
Definition: utils.cpp:213
void updatePercentage(double percentage)
This method is meant to be called by the derived class to report updated progress percentage only...
The UtilitiesTests class tests various utility classes and functions of the tagparser library...
Definition: utils.cpp:46
void testAspectRatio()
Definition: utils.cpp:221
uint64 level() const
Returns the level.
Definition: tagtarget.h:81
void open(bool readOnly=false)
Opens a std::fstream for the current file.
const char * abbreviation() const
Returns an abbreviation for the current instance, eg.
Definition: size.cpp:9
void close()
A possibly opened std::fstream will be closed.
bool hasNotifications() const
Returns an indication whether there are notifications for the current object.
IoUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition: basicfileinfo.h:80
TAG_PARSER_EXPORT const char * containerFormatName(ContainerFormat containerFormat)
Returns the name of the specified container format as C-style string.
Definition: signature.cpp:322
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, int bufferSize)
Parses the signature read from the specified buffer.
Definition: signature.cpp:103
Contains utility classes helping to read and write streams.
const char * shortAbbreviation() const
Returns a short abbreviation of the media format as C-style string.
void setHeight(uint32 value)
Sets the height.
Definition: size.h:82
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
void tryToAbort()
Commands the object to abort the current operation.
std::string toString(const std::function< TagTargetLevel(uint64)> &tagTargetMapping) const
Returns the string representation of the current instance.
Definition: tagtarget.h:218
void testMediaFormat()
Definition: utils.cpp:235
const std::string & path() const
Returns the path of the current file.
Definition: basicfileinfo.h:98
CPPUNIT_TEST_SUITE_REGISTRATION(UtilitiesTests)
void invalidateNotifications()
Invalidates the object&#39;s notifications.
const char * abbreviation() const
Returns the abbreviation of the media format as C-style string.
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition: mediafileinfo.h:53
void testTagTarget()
Definition: utils.cpp:171
void setUp()
Definition: utils.cpp:78
const std::string & currentStatus() const
Returns a status information for the current object.
void testSize()
Definition: utils.cpp:86
void updateStatus(const std::string &status)
This method is meant to be called by the derived class to report updated status information.
The TestStatusProvider class helps testing the StatusProvider class.
Definition: utils.cpp:33
The TagTarget class specifies the target of a tag.
Definition: tagtarget.h:31
void testStatusProvider()
Definition: utils.cpp:100
Contains all classes and functions of the TagInfo library.
Definition: exceptions.h:9
void testSignature()
Definition: utils.cpp:193
The StatusProvider class acts as a base class for objects providing status information.
std::string toString() const
Returns the string representation of the current size.
Definition: size.h:123
void addNotifications(const StatusProvider &from)
This protected method is meant to be called by the derived class to add all notifications from anothe...
void forwardStatusUpdateCalls(StatusProvider *other=nullptr)
Forwards all status updates calls to the specified statusProvider.
TAG_PARSER_EXPORT std::string & backupDirectory()
Returns the directory used to store backup files.
TAG_PARSER_EXPORT void restoreOriginalFileFromBackupFile(const std::string &originalPath, const std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
The MediaFormat class specifies the format of media data.
Definition: mediaformat.h:257
The Margin class defines the four margins of a rectangle.
Definition: margin.h:16