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