diff --git a/cli/attachmentinfo.cpp b/cli/attachmentinfo.cpp index 3d13865..51b90ff 100644 --- a/cli/attachmentinfo.cpp +++ b/cli/attachmentinfo.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -37,7 +38,7 @@ void AttachmentInfo::parseDenotation(const char *denotation) } } -void AttachmentInfo::apply(AbstractContainer *container) +void AttachmentInfo::apply(AbstractContainer *container, Media::Diagnostics &diag) { static const string context("applying specified attachments"); AbstractAttachment *attachment = nullptr; @@ -48,30 +49,30 @@ void AttachmentInfo::apply(AbstractContainer *container) cerr << "Argument --update-argument specified but no name/path provided." << endl; return; } - apply(container->createAttachment()); + apply(container->createAttachment(), diag); break; case AttachmentAction::Update: if(hasId) { for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) { attachment = container->attachment(i); if(attachment->id() == id) { - apply(attachment); + apply(attachment, diag); attachmentFound = true; } } if(!attachmentFound) { - container->addNotification(NotificationType::Critical, "Attachment with the specified ID \"" + numberToString(id) + "\" does not exist and hence can't be updated.", context); + diag.emplace_back(DiagLevel::Critical, argsToString("Attachment with the specified ID \"", id, "\" does not exist and hence can't be updated."), context); } } else if(name) { for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) { attachment = container->attachment(i); if(attachment->name() == name) { - apply(attachment); + apply(attachment, diag); attachmentFound = true; } } if(!attachmentFound) { - container->addNotification(NotificationType::Critical, "Attachment with the specified name \"" + string(name) + "\" does not exist and hence can't be updated.", context); + diag.emplace_back(DiagLevel::Critical, argsToString("Attachment with the specified name \"", name, "\" does not exist and hence can't be updated."), context); } } else { cerr << "Argument --update-argument specified but no ID/name provided." << endl; @@ -87,7 +88,7 @@ void AttachmentInfo::apply(AbstractContainer *container) } } if(!attachmentFound) { - container->addNotification(NotificationType::Critical, "Attachment with the specified ID \"" + numberToString(id) + "\" does not exist and hence can't be removed.", context); + diag.emplace_back(DiagLevel::Critical, "Attachment with the specified ID \"" + numberToString(id) + "\" does not exist and hence can't be removed.", context); } } else if(name) { for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) { @@ -98,7 +99,7 @@ void AttachmentInfo::apply(AbstractContainer *container) } } if(!attachmentFound) { - container->addNotification(NotificationType::Critical, "Attachment with the specified name \"" + string(name) + "\" does not exist and hence can't be removed.", context); + diag.emplace_back(DiagLevel::Critical, "Attachment with the specified name \"" + string(name) + "\" does not exist and hence can't be removed.", context); } } else { cerr << "Argument --remove-argument specified but no ID/name provided." << endl; @@ -107,13 +108,13 @@ void AttachmentInfo::apply(AbstractContainer *container) } } -void AttachmentInfo::apply(AbstractAttachment *attachment) +void AttachmentInfo::apply(AbstractAttachment *attachment, Media::Diagnostics &diag) { if(hasId) { attachment->setId(id); } if(path) { - attachment->setFile(path); + attachment->setFile(path, diag); } if(name) { attachment->setName(name); @@ -134,13 +135,13 @@ void AttachmentInfo::reset() path = name = mime = desc = nullptr; } -bool AttachmentInfo::next(AbstractContainer *container) +bool AttachmentInfo::next(AbstractContainer *container, Media::Diagnostics &diag) { if(!id && !path && !name && !mime && !desc) { // skip empty attachment infos return false; } - apply(container); + apply(container, diag); reset(); return true; } diff --git a/cli/attachmentinfo.h b/cli/attachmentinfo.h index 281bef8..30a9a93 100644 --- a/cli/attachmentinfo.h +++ b/cli/attachmentinfo.h @@ -1,6 +1,8 @@ #ifndef CLI_ATTACHMENT_INFO #define CLI_ATTACHMENT_INFO +#include + #include namespace Media { @@ -21,10 +23,10 @@ class AttachmentInfo public: AttachmentInfo(); void parseDenotation(const char *denotation); - void apply(Media::AbstractContainer *container); - void apply(Media::AbstractAttachment *attachment); + void apply(Media::AbstractContainer *container, Media::Diagnostics &diag); + void apply(Media::AbstractAttachment *attachment, Media::Diagnostics &diag); void reset(); - bool next(Media::AbstractContainer *container); + bool next(Media::AbstractContainer *container, Media::Diagnostics &diag); AttachmentAction action; uint64 id; diff --git a/cli/helper.cpp b/cli/helper.cpp index e28f733..2aef21c 100644 --- a/cli/helper.cpp +++ b/cli/helper.cpp @@ -2,6 +2,8 @@ #include "./fieldmapping.h" #include +#include +#include #include #include #include @@ -118,16 +120,16 @@ string incremented(const string &str, unsigned int toIncrement) return res; } -void printNotifications(NotificationList ¬ifications, const char *head, bool beVerbose) +void printDiagMessages(const Diagnostics &diag, const char *head, bool beVerbose) { - if(notifications.empty()) { + if(diag.empty()) { return; } if(!beVerbose) { - for(const auto ¬ification : notifications) { - switch(notification.type()) { - case NotificationType::Debug: - case NotificationType::Information: + for(const auto &message : diag) { + switch(message.level()) { + case DiagLevel::Debug: + case DiagLevel::Information: break; default: goto printNotifications; @@ -140,45 +142,37 @@ printNotifications: if(head) { cout << " - " << head << endl; } - Notification::sortByTime(notifications); - for(const auto ¬ification : notifications) { - switch(notification.type()) { - case NotificationType::Debug: + for(const auto &message : diag) { + switch(message.level()) { + case DiagLevel::Debug: if(beVerbose) { cout << " Debug "; break; } else { continue; } - case NotificationType::Information: + case DiagLevel::Information: if(beVerbose) { cout << " Information "; break; } else { continue; } - case NotificationType::Warning: + case DiagLevel::Warning: cout << " Warning "; break; - case NotificationType::Critical: + case DiagLevel::Critical: cout << " Error "; break; default: ; } - cout << notification.creationTime().toString(DateTimeOutputFormat::TimeOnly) << " "; - cout << notification.context() << ": "; - cout << notification.message() << '\n'; + cout << message.creationTime().toString(DateTimeOutputFormat::TimeOnly) << " "; + cout << message.context() << ": "; + cout << message.message() << '\n'; } } -void printNotifications(const MediaFileInfo &fileInfo, const char *head, bool beVerbose) -{ - NotificationList notifications; - fileInfo.gatherRelatedNotifications(notifications); - printNotifications(notifications, head, beVerbose); -} - void printProperty(const char *propName, const char *value, const char *suffix, Indentation indentation) { if(!*value) { @@ -658,29 +652,23 @@ bool stringToBool(const string &str) } bool logLineFinalized = true; -static string lastLoggedStatus; -void logStatus(const StatusProvider &statusProvider) +static string lastStep; +void logNextStep(const AbortableProgressFeedback &progress) { - if(statusProvider.currentStatus() != lastLoggedStatus) { - // the ongoing operation ("status") has changed - // -> finalize previous line and make new line - if(!logLineFinalized) { - cout << "\r - [100%] " << lastLoggedStatus << endl; - logLineFinalized = true; - } - // -> update lastStatus - lastLoggedStatus = statusProvider.currentStatus(); + // finalize previous step + if(!logLineFinalized) { + cout << "\r - [100%] " << lastStep << endl; + logLineFinalized = true; } + // print line for next step + lastStep = progress.step(); + cout << "\r - [" << setw(3) << static_cast(progress.stepPercentage()) << "%] " << lastStep << flush; + logLineFinalized = false; +} - // update current line if an operation is ongoing (status is not empty) - if(!lastLoggedStatus.empty()) { - int percentage = static_cast(statusProvider.currentPercentage() * 100); - if(percentage < 0) { - percentage = 0; - } - cout << "\r - [" << setw(3) << percentage << "%] " << lastLoggedStatus << flush; - logLineFinalized = false; - } +void logStepPercentage(const Media::AbortableProgressFeedback &progress) +{ + cout << "\r - [" << setw(3) << static_cast(progress.stepPercentage()) << "%] " << lastStep << flush; } void finalizeLog() @@ -688,9 +676,9 @@ void finalizeLog() if(logLineFinalized) { return; } - cout << '\n'; + cout << "\r - [100%] " << lastStep << '\n'; logLineFinalized = true; - lastLoggedStatus.clear(); + lastStep.clear(); } } diff --git a/cli/helper.h b/cli/helper.h index 039c24f..16fa89e 100644 --- a/cli/helper.h +++ b/cli/helper.h @@ -22,6 +22,8 @@ class Argument; namespace Media { class MediaFileInfo; +class Diagnostics; +class AbortableProgressFeedback; enum class TagUsage; enum class ElementPosition; } @@ -263,8 +265,7 @@ constexpr bool isDigit(char c) std::string incremented(const std::string &str, unsigned int toIncrement = 1); -void printNotifications(NotificationList ¬ifications, const char *head = nullptr, bool beVerbose = false); -void printNotifications(const MediaFileInfo &fileInfo, const char *head = nullptr, bool beVerbose = false); +void printDiagMessages(const Media::Diagnostics &diag, const char *head = nullptr, bool beVerbose = false); void printProperty(const char *propName, const char *value, const char *suffix = nullptr, ApplicationUtilities::Indentation indentation = 4); void printProperty(const char *propName, ElementPosition elementPosition, const char *suffix = nullptr, ApplicationUtilities::Indentation indentation = 4); @@ -310,7 +311,8 @@ FieldDenotations parseFieldDenotations(const ApplicationUtilities::Argument &fie std::string tagName(const Tag *tag); bool stringToBool(const std::string &str); extern bool logLineFinalized; -void logStatus(const StatusProvider &statusProvider); +void logNextStep(const Media::AbortableProgressFeedback &progress); +void logStepPercentage(const Media::AbortableProgressFeedback &progress); void finalizeLog(); } diff --git a/cli/mainfeatures.cpp b/cli/mainfeatures.cpp index 2aed726..28e3866 100644 --- a/cli/mainfeatures.cpp +++ b/cli/mainfeatures.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #ifdef TAGEDITOR_JSON_EXPORT # include @@ -107,18 +109,21 @@ void generateFileInfo(const ArgumentOccurrence &, const Argument &inputFileArg, MediaFileInfo inputFileInfo(inputFileArg.values().front()); inputFileInfo.setForceFullParse(validateArg.isPresent()); inputFileInfo.open(true); - inputFileInfo.parseEverything(); + Diagnostics diag; + inputFileInfo.parseEverything(diag); + + // generate and save info + Diagnostics diagReparsing; (outputFileArg.isPresent() ? cout : cerr) << "Saving file info for \"" << inputFileArg.values().front() << "\" ..." << endl; - NotificationList origNotify; - if(outputFileArg.isPresent()) { - QFile file(fromNativeFileName(outputFileArg.values().front())); - if(file.open(QFile::WriteOnly) && file.write(HtmlInfo::generateInfo(inputFileInfo, origNotify)) && file.flush()) { - cout << "File information has been saved to \"" << outputFileArg.values().front() << "\"." << endl; - } else { - cerr << Phrases::Error << "An IO error occured when writing the file \"" << outputFileArg.values().front() << "\"." << Phrases::EndFlush; - } + if(!outputFileArg.isPresent()) { + cout << HtmlInfo::generateInfo(inputFileInfo, diag, diagReparsing).data() << endl; + return; + } + QFile file(fromNativeFileName(outputFileArg.values().front())); + if(file.open(QFile::WriteOnly) && file.write(HtmlInfo::generateInfo(inputFileInfo, diag, diagReparsing)) && file.flush()) { + cout << "File information has been saved to \"" << outputFileArg.values().front() << "\"." << endl; } else { - cout << HtmlInfo::generateInfo(inputFileInfo, origNotify).data() << endl; + cerr << Phrases::Error << "An IO error occured when writing the file \"" << outputFileArg.values().front() << "\"." << Phrases::EndFlush; } } catch(const Media::Failure &) { cerr << Phrases::Error << "A parsing failure occured when reading the file \"" << inputFileArg.values().front() << "\"." << Phrases::EndFlush; @@ -146,14 +151,15 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const MediaFileInfo fileInfo; for(const char *file : filesArg.values()) { + Diagnostics diag; try { // parse tags fileInfo.setPath(file); fileInfo.open(true); - fileInfo.parseContainerFormat(); - fileInfo.parseTracks(); - fileInfo.parseAttachments(); - fileInfo.parseChapters(); + fileInfo.parseContainerFormat(diag); + fileInfo.parseTracks(diag); + fileInfo.parseAttachments(diag); + fileInfo.parseChapters(diag); // print general/container-related info cout << "Technical information for \"" << file << "\":\n"; @@ -176,8 +182,8 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const printProperty("Duration", container->duration()); printProperty("Creation time", container->creationTime()); printProperty("Modification time", container->modificationTime()); - printProperty("Tag position", container->determineTagPosition()); - printProperty("Index position", container->determineIndexPosition()); + printProperty("Tag position", container->determineTagPosition(diag)); + printProperty("Index position", container->determineIndexPosition(diag)); } if(fileInfo.paddingSize()) { printProperty("Padding", dataSizeToString(fileInfo.paddingSize())); @@ -295,7 +301,7 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const cerr << Phrases::Error << "An IO failure occured when reading the file \"" << file << "\"." << Phrases::EndFlush; } - printNotifications(fileInfo, "Parsing notifications:", verboseArg.isPresent()); + printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent()); cout << endl; } } @@ -315,12 +321,13 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &filesArg, const A MediaFileInfo fileInfo; for(const char *file : filesArg.values()) { + Diagnostics diag; try { // parse tags fileInfo.setPath(file); fileInfo.open(true); - fileInfo.parseContainerFormat(); - fileInfo.parseTags(); + fileInfo.parseContainerFormat(diag); + fileInfo.parseTags(diag); cout << "Tag information for \"" << file << "\":\n"; const auto tags = fileInfo.tags(); if(!tags.empty()) { @@ -353,7 +360,7 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &filesArg, const A ::IoUtilities::catchIoFailure(); cerr << Phrases::Error << "An IO failure occured when reading the file \"" << file << "\"." << Phrases::EndFlush; } - printNotifications(fileInfo, "Parsing notifications:", verboseArg.isPresent()); + printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent()); cout << endl; } } @@ -454,17 +461,16 @@ void setTagInfo(const SetTagInfoArgs &args) // iterate through all specified files unsigned int fileIndex = 0; - static const string context("setting tags"); - NotificationList notifications; + static string context("setting tags"); for(const char *file : args.filesArg.values()) { + Diagnostics diag; try { // parse tags and tracks (tracks are relevent because track meta-data such as language can be changed as well) cout << TextAttribute::Bold << "Setting tag information for \"" << file << "\" ..." << Phrases::EndFlush; - notifications.clear(); fileInfo.setPath(file); - fileInfo.parseContainerFormat(); - fileInfo.parseTags(); - fileInfo.parseTracks(); + fileInfo.parseContainerFormat(diag); + fileInfo.parseTags(diag); + fileInfo.parseTracks(diag); vector tags; // remove tags with the specified targets @@ -517,7 +523,7 @@ void setTagInfo(const SetTagInfoArgs &args) // alter tags fileInfo.tags(tags); if(tags.empty()) { - fileInfo.addNotification(NotificationType::Critical, "Can not create appropriate tags for file.", context); + diag.emplace_back(DiagLevel::Critical, "Can not create appropriate tags for file.", context); } else { // iterate through all tags for(auto *tag : tags) { @@ -534,7 +540,7 @@ void setTagInfo(const SetTagInfoArgs &args) if(!tag->canEncodingBeUsed(denotedEncoding)) { usedEncoding = tag->proposedTextEncoding(); if(args.encodingArg.isPresent()) { - fileInfo.addNotification(NotificationType::Warning, argsToString("Can't use specified encoding \"", args.encodingArg.values().front(), "\" in ", tagName(tag), " because the tag format/version doesn't support it."), context); + diag.emplace_back(DiagLevel::Warning, argsToString("Can't use specified encoding \"", args.encodingArg.values().front(), "\" in ", tagName(tag), " because the tag format/version doesn't support it."), context); } } // iterate through all denoted field values @@ -553,8 +559,9 @@ void setTagInfo(const SetTagInfoArgs &args) try { // assume the file refers to a picture MediaFileInfo fileInfo(relevantDenotedValue->value); + Diagnostics diag; fileInfo.open(true); - fileInfo.parseContainerFormat(); + fileInfo.parseContainerFormat(diag); auto buff = make_unique(fileInfo.size()); fileInfo.stream().seekg(0); fileInfo.stream().read(buff.get(), fileInfo.size()); @@ -562,10 +569,10 @@ void setTagInfo(const SetTagInfoArgs &args) value.setMimeType(fileInfo.mimeType()); convertedValues.emplace_back(move(value)); } catch(const Media::Failure &) { - fileInfo.addNotification(NotificationType::Critical, "Unable to parse specified cover file.", context); + diag.emplace_back(DiagLevel::Critical, "Unable to parse specified cover file.", context); } catch(...) { ::IoUtilities::catchIoFailure(); - fileInfo.addNotification(NotificationType::Critical, "An IO error occured when parsing the specified cover file.", context); + diag.emplace_back(DiagLevel::Critical, "An IO error occured when parsing the specified cover file.", context); } } else { convertedValues.emplace_back(relevantDenotedValue->value, TagTextEncoding::Utf8, usedEncoding); @@ -579,7 +586,7 @@ void setTagInfo(const SetTagInfoArgs &args) try { denotedScope.field.setValues(tag, tagType, convertedValues); } catch(const ConversionException &e) { - fileInfo.addNotification(NotificationType::Critical, argsToString("Unable to parse denoted field ID \"", denotedScope.field.name(), "\": ", e.what()), context); + diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse denoted field ID \"", denotedScope.field.name(), "\": ", e.what()), context); } } } @@ -613,10 +620,10 @@ void setTagInfo(const SetTagInfoArgs &args) } else if(field.denotes("default")) { track->setDefault(stringToBool(value)); } else { - fileInfo.addNotification(NotificationType::Critical, argsToString("Denoted track property name \"", field.denotation(), "\" is invalid"), argsToString("setting meta-data of track ", track->id())); + diag.emplace_back(DiagLevel::Critical, argsToString("Denoted track property name \"", field.denotation(), "\" is invalid"), argsToString("setting meta-data of track ", track->id())); } } catch(const ConversionException &e) { - fileInfo.addNotification(NotificationType::Critical, argsToString("Unable to parse value for track property \"", field.denotation(), "\": ", e.what()), argsToString("setting meta-data of track ", track->id())); + diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse value for track property \"", field.denotation(), "\": ", e.what()), argsToString("setting meta-data of track ", track->id())); } } } @@ -635,7 +642,7 @@ void setTagInfo(const SetTagInfoArgs &args) bool attachmentsModified = false; if(args.addAttachmentArg.isPresent() || args.updateAttachmentArg.isPresent() || args.removeAttachmentArg.isPresent() || args.removeExistingAttachmentsArg.isPresent()) { static const string context("setting attachments"); - fileInfo.parseAttachments(); + fileInfo.parseAttachments(diag); if(fileInfo.attachmentsParsingStatus() == ParsingStatus::Ok) { if(container) { // ignore all existing attachments if argument is specified @@ -652,24 +659,24 @@ void setTagInfo(const SetTagInfoArgs &args) for(const char *value : args.addAttachmentArg.values(i)) { currentInfo.parseDenotation(value); } - attachmentsModified |= currentInfo.next(container); + attachmentsModified |= currentInfo.next(container, diag); } currentInfo.action = AttachmentAction::Update; for(size_t i = 0, occurrences = args.updateAttachmentArg.occurrences(); i != occurrences; ++i) { for(const char *value : args.updateAttachmentArg.values(i)) { currentInfo.parseDenotation(value); } - attachmentsModified |= currentInfo.next(container); + attachmentsModified |= currentInfo.next(container, diag); } currentInfo.action = AttachmentAction::Remove; for(size_t i = 0, occurrences = args.removeAttachmentArg.occurrences(); i != occurrences; ++i) { for(const char *value : args.removeAttachmentArg.values(i)) { currentInfo.parseDenotation(value); } - attachmentsModified |= currentInfo.next(container); + attachmentsModified |= currentInfo.next(container, diag); } } else { - fileInfo.addNotification(NotificationType::Critical, "Unable to assign attachments because the container object has not been initialized.", context); + diag.emplace_back(DiagLevel::Critical, "Unable to assign attachments because the container object has not been initialized.", context); } } else { // notification will be added by the file info automatically @@ -677,22 +684,14 @@ void setTagInfo(const SetTagInfoArgs &args) } // apply changes + fileInfo.setSaveFilePath(currentOutputFile != noMoreOutputFiles ? string(*currentOutputFile) : string()); try { - // save parsing notifications because notifications of sub objects like tags, tracks, ... will be gone after applying changes - fileInfo.setSaveFilePath(currentOutputFile != noMoreOutputFiles ? string(*currentOutputFile) : string()); - fileInfo.gatherRelatedNotifications(notifications); - fileInfo.invalidateNotifications(); + // create handler for progress updates and aborting + AbortableProgressFeedback progress(logNextStep, logStepPercentage); + const InterruptHandler handler(bind(&AbortableProgressFeedback::tryToAbort, ref(progress))); - // register handler for logging status - fileInfo.registerCallback(logStatus); - - // register interrupt handler - const InterruptHandler handler([&fileInfo] { - fileInfo.tryToAbort(); - }); - - // apply changes and gather notifications - fileInfo.applyChanges(); + // apply changes + fileInfo.applyChanges(diag, progress); // notify about completion finalizeLog(); @@ -714,8 +713,7 @@ void setTagInfo(const SetTagInfoArgs &args) cerr << " - " << Phrases::Error << "An IO failure occured when reading/writing the file \"" << file << "\"." << Phrases::EndFlush; } - fileInfo.gatherRelatedNotifications(notifications); - printNotifications(notifications, "Notifications:", args.verboseArg.isPresent()); + printDiagMessages(diag, "Diagnostic messages:", args.verboseArg.isPresent()); // continue with next file ++fileIndex; @@ -747,6 +745,7 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const MediaFileInfo inputFileInfo; for(const char *file : inputFilesArg.values()) { + Diagnostics diag; try { // setup media file info inputFileInfo.setPath(file); @@ -756,8 +755,8 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const if(!fieldDenotations.empty()) { // extract tag field (outputFileArg.isPresent() ? cout : cerr) << "Extracting field " << fieldArg.values().front() << " of \"" << file << "\" ..." << endl; - inputFileInfo.parseContainerFormat(); - inputFileInfo.parseTags(); + inputFileInfo.parseContainerFormat(diag); + inputFileInfo.parseTags(diag); auto tags = inputFileInfo.tags(); vector > values; // iterate through all tags @@ -769,7 +768,7 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const values.emplace_back(value, joinStrings({tag->typeName(), numberToString(values.size())}, "-", true)); } } catch(const ConversionException &e) { - inputFileInfo.addNotification(NotificationType::Critical, "Unable to parse denoted field ID \"" % string(fieldDenotation.first.field.name()) % "\": " + e.what(), "extracting field"); + diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse denoted field ID \"", fieldDenotation.first.field.name(), "\": ", e.what()), "extracting field"); } } } @@ -812,8 +811,8 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const } logStream << " of \"" << file << "\" ..." << endl; - inputFileInfo.parseContainerFormat(); - inputFileInfo.parseAttachments(); + inputFileInfo.parseContainerFormat(diag); + inputFileInfo.parseAttachments(diag); vector > attachments; // iterate through all attachments for(const AbstractAttachment *attachment : inputFileInfo.attachments()) { @@ -856,7 +855,7 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const ::IoUtilities::catchIoFailure(); cerr << Phrases::Error << "An IO failure occured when reading the file \"" << file << "\"." << Phrases::End; } - printNotifications(inputFileInfo, "Parsing notifications:", verboseArg.isPresent()); + printDiagMessages(diag, "Diagnostic messages:", verboseArg.isPresent()); } } @@ -877,13 +876,14 @@ void exportToJson(const ArgumentOccurrence &, const Argument &filesArg, const Ar // gather tags for each file for(const char *file : filesArg.values()) { + Diagnostics diag; try { // parse tags fileInfo.setPath(file); fileInfo.open(true); - fileInfo.parseContainerFormat(); - fileInfo.parseTags(); - fileInfo.parseTracks(); + fileInfo.parseContainerFormat(diag); + fileInfo.parseTags(diag); + fileInfo.parseTracks(diag); jsonData.emplace_back(fileInfo, document.GetAllocator()); } catch(const Media::Failure &) { cerr << Phrases::Error << "A parsing failure occured when reading the file \"" << file << "\"." << Phrases::EndFlush; @@ -893,6 +893,8 @@ void exportToJson(const ArgumentOccurrence &, const Argument &filesArg, const Ar } } + // TODO: serialize diag messages + // print the gathered data as JSON document ReflectiveRapidJSON::JsonReflector::push(jsonData, document, document.GetAllocator()); RAPIDJSON_NAMESPACE::OStreamWrapper osw(cout); diff --git a/gui/attachmentsedit.cpp b/gui/attachmentsedit.cpp index 88389ac..a3ff7e3 100644 --- a/gui/attachmentsedit.cpp +++ b/gui/attachmentsedit.cpp @@ -6,6 +6,7 @@ #include #include +#include #include @@ -91,16 +92,17 @@ void AttachmentsEdit::invalidate() void AttachmentsEdit::addFile(const QString &path) { - if(fileInfo() && fileInfo()->attachmentsParsingStatus() == ParsingStatus::Ok && fileInfo()->container()) { - // create and add attachment - auto *attachment = fileInfo()->container()->createAttachment(); - attachment->setIgnored(true); - attachment->setFile(toNativeFileName(path).data()); - m_addedAttachments << attachment; - m_model->addAttachment(-1, attachment, true, path); - } else { + if(!fileInfo() || fileInfo()->attachmentsParsingStatus() != ParsingStatus::Ok || !fileInfo()->container()) { throw Failure(); } + // create and add attachment + auto *const attachment = fileInfo()->container()->createAttachment(); + attachment->setIgnored(true); + Diagnostics diag; + attachment->setFile(toNativeFileName(path).data(), diag); + // TODO: show diag messages + m_addedAttachments << attachment; + m_model->addAttachment(-1, attachment, true, path); } void AttachmentsEdit::showFileSelection() diff --git a/gui/fileinfomodel.cpp b/gui/fileinfomodel.cpp index a9ac2ab..9a12206 100644 --- a/gui/fileinfomodel.cpp +++ b/gui/fileinfomodel.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include @@ -30,6 +30,7 @@ using namespace std; using namespace ChronoUtilities; using namespace ConversionUtilities; using namespace Media; +using namespace Utility; namespace QtGui { @@ -115,34 +116,33 @@ private: QStandardItem *m_item; }; -void addNotifications(Media::NotificationList *notifications, QStandardItem *parent) +void addDiagMessages(Media::Diagnostics *diag, QStandardItem *parent) { - Notification::sortByTime(*notifications); - for(Notification ¬ification : *notifications) { + for(const auto &msg : *diag) { QList notificationRow; notificationRow.reserve(3); - auto *firstItem = defaultItem(QString::fromUtf8(notification.creationTime().toString().data())); - switch(notification.type()) { - case NotificationType::None: - break; - case NotificationType::Debug: + auto *firstItem = defaultItem(QString::fromUtf8(msg.creationTime().toString().data())); + switch(msg.level()) { + case DiagLevel::None: + case DiagLevel::Debug: firstItem->setIcon(FileInfoModel::debugIcon()); break; - case NotificationType::Information: + case DiagLevel::Information: firstItem->setIcon(FileInfoModel::informationIcon()); break; - case NotificationType::Warning: + case DiagLevel::Warning: firstItem->setIcon(FileInfoModel::warningIcon()); break; - case NotificationType::Critical: + case DiagLevel::Critical: + case DiagLevel::Fatal: firstItem->setIcon(FileInfoModel::errorIcon()); break; } parent->appendRow(QList() << firstItem - << defaultItem(QString::fromUtf8(notification.message().data())) - << defaultItem(QString::fromUtf8(notification.context().data()))); + << defaultItem(QString::fromUtf8(msg.message().data())) + << defaultItem(QString::fromUtf8(msg.context().data()))); } } @@ -190,10 +190,9 @@ template void addElementNode(const /*! * \brief Constructs a new instance with the specified \a fileInfo which might be nullptr. */ -FileInfoModel::FileInfoModel(Media::MediaFileInfo *fileInfo, QObject *parent) : +FileInfoModel::FileInfoModel(QObject *parent) : QStandardItemModel(parent) { - setFileInfo(fileInfo); } QVariant FileInfoModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -231,10 +230,11 @@ const MediaFileInfo *FileInfoModel::fileInfo() const * \brief Assigns a Media::MediaFileInfo. * \remarks Causes updating the internal cache and resets the model. */ -void FileInfoModel::setFileInfo(MediaFileInfo *fileInfo, Media::NotificationList *originalNotifications) +void FileInfoModel::setFileInfo(MediaFileInfo &fileInfo, Diagnostics &diag, Diagnostics *diagReparsing) { - m_file = fileInfo; - m_originalNotifications = originalNotifications; + m_file = &fileInfo; + m_diag = &diag; + m_diagReparsing = diagReparsing; updateCache(); } @@ -271,6 +271,9 @@ void FileInfoModel::updateCache() beginResetModel(); clear(); if(m_file) { + // get diag + Diagnostics &diag = m_diagReparsing ? *m_diagReparsing : *m_diag; + // get container AbstractContainer *container = m_file->container(); @@ -327,8 +330,8 @@ void FileInfoModel::updateCache() containerHelper.appendRow(tr("Document type"), container->documentType()); containerHelper.appendRow(tr("Document version"), container->doctypeVersion()); containerHelper.appendRow(tr("Document read version"), container->doctypeReadVersion()); - containerHelper.appendRow(tr("Tag position"), Utility::elementPositionToQString(container->determineTagPosition())); - containerHelper.appendRow(tr("Index position"), Utility::elementPositionToQString(container->determineIndexPosition())); + containerHelper.appendRow(tr("Tag position"), Utility::elementPositionToQString(container->determineTagPosition(diag))); + containerHelper.appendRow(tr("Index position"), Utility::elementPositionToQString(container->determineIndexPosition(diag))); } containerHelper.appendRow(tr("Padding size"), m_file->paddingSize()); @@ -338,7 +341,7 @@ void FileInfoModel::updateCache() if(!tags.empty()) { auto *tagsItem = defaultItem(tr("Tags")); setItem(++currentRow, tagsItem); - setItem(currentRow, 1, defaultItem(tr("%1 tag(s) assigned", nullptr, static_cast(tags.size())).arg(tags.size()))); + setItem(currentRow, 1, defaultItem(tr("%1 tag(s) assigned", nullptr, trQuandity(tags.size())).arg(tags.size()))); for(const Tag *tag : tags) { auto *tagItem = defaultItem(tag->typeName()); @@ -360,9 +363,9 @@ void FileInfoModel::updateCache() setItem(++currentRow, tracksItem); const string summary(m_file->technicalSummary()); if(summary.empty()) { - setItem(currentRow, 1, defaultItem(tr("%1 track(s) contained", nullptr, static_cast(tracks.size())).arg(tracks.size()))); + setItem(currentRow, 1, defaultItem(tr("%1 track(s) contained", nullptr, trQuandity(tracks.size())).arg(tracks.size()))); } else { - setItem(currentRow, 1, defaultItem(tr("%1 track(s): ", nullptr, static_cast(tracks.size())).arg(tracks.size()) + QString::fromUtf8(summary.data(), summary.size()))); + setItem(currentRow, 1, defaultItem(tr("%1 track(s): ", nullptr, trQuandity(tracks.size())).arg(tracks.size()) + QString::fromUtf8(summary.data(), summary.size()))); } size_t number = 0; @@ -461,7 +464,7 @@ void FileInfoModel::updateCache() if(!attachments.empty()) { auto *attachmentsItem = defaultItem(tr("Attachments")); setItem(++currentRow, attachmentsItem); - setItem(currentRow, 1, defaultItem(tr("%1 attachment(s) present", nullptr, attachments.size()).arg(attachments.size()))); + setItem(currentRow, 1, defaultItem(tr("%1 attachment(s) present", nullptr, trQuandity(attachments.size())).arg(attachments.size()))); size_t number = 0; for(const AbstractAttachment *attachment : attachments) { @@ -524,7 +527,7 @@ void FileInfoModel::updateCache() if(!editionEntries.empty()) { auto *editionsItem = defaultItem(tr("Editions")); setItem(++currentRow, editionsItem); - setItem(currentRow, 1, defaultItem(tr("%1 edition(s) present", nullptr, editionEntries.size()).arg(editionEntries.size()))); + setItem(currentRow, 1, defaultItem(tr("%1 edition(s) present", nullptr, trQuandity(editionEntries.size())).arg(editionEntries.size()))); size_t editionNumber = 0; for(const auto &edition : editionEntries) { auto *editionItem = defaultItem(tr("Edition #%1").arg(++editionNumber)); @@ -554,7 +557,7 @@ void FileInfoModel::updateCache() if(!chapters.empty()) { auto *chaptersItem = defaultItem(tr("Chapters")); setItem(++currentRow, chaptersItem); - setItem(currentRow, 1, defaultItem(tr("%1 chapter(s) present", nullptr, chapters.size()).arg(chapters.size()))); + setItem(currentRow, 1, defaultItem(tr("%1 chapter(s) present", nullptr, trQuandity(chapters.size())).arg(chapters.size()))); for(const AbstractChapter *chapter : chapters) { addChapter(chapter, chaptersItem); } @@ -592,18 +595,14 @@ void FileInfoModel::updateCache() } // notifications - auto currentNotifications = m_file->gatherRelatedNotifications(); - if(!currentNotifications.empty()) { - auto *notificationsItem= defaultItem(m_originalNotifications ? tr("Notifications (reparsing after saving)") : tr("Notifications")); - addNotifications(¤tNotifications, notificationsItem); - setItem(++currentRow, notificationsItem); + auto *const diagItem = defaultItem(tr("Diagnostic messages")); + addDiagMessages(m_diag, diagItem); + setItem(++currentRow, diagItem); + if (m_diagReparsing) { + auto *diagReparsingItem = defaultItem(tr("Diagnostic messages from reparsing")); + addDiagMessages(m_diagReparsing, diagReparsingItem); + setItem(++currentRow, diagReparsingItem); } - if(m_originalNotifications && !m_originalNotifications->empty()) { - auto *notificationsItem = defaultItem(tr("Notifications")); - addNotifications(m_originalNotifications, notificationsItem); - setItem(++currentRow, notificationsItem); - } - } endResetModel(); } diff --git a/gui/fileinfomodel.h b/gui/fileinfomodel.h index 4bbf224..c4089ec 100644 --- a/gui/fileinfomodel.h +++ b/gui/fileinfomodel.h @@ -7,8 +7,7 @@ namespace Media { class MediaFileInfo; -class Notification; -typedef std::list NotificationList; +class Diagnostics; } namespace QtGui { @@ -17,12 +16,12 @@ class FileInfoModel : public QStandardItemModel { Q_OBJECT public: - explicit FileInfoModel(Media::MediaFileInfo *fileInfo = nullptr, QObject *parent = nullptr); + explicit FileInfoModel(QObject *parent = nullptr); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; const Media::MediaFileInfo *fileInfo() const; - void setFileInfo(Media::MediaFileInfo *fileInfo, Media::NotificationList *originalNotifications = nullptr); + void setFileInfo(Media::MediaFileInfo &fileInfo, Media::Diagnostics &diag, Media::Diagnostics *diagReparsing = nullptr); #if defined(GUI_QTWIDGETS) static const QIcon &informationIcon(); @@ -36,7 +35,8 @@ private: private: Media::MediaFileInfo *m_file; - Media::NotificationList *m_originalNotifications; + Media::Diagnostics *m_diag; + Media::Diagnostics *m_diagReparsing; }; } diff --git a/gui/notificationmodel.cpp b/gui/notificationmodel.cpp index 9b96e4a..8bd59e6 100644 --- a/gui/notificationmodel.cpp +++ b/gui/notificationmodel.cpp @@ -1,5 +1,7 @@ #include "./notificationmodel.h" +#include "../misc/utility.h" + #include #include @@ -9,14 +11,15 @@ using namespace std; using namespace ChronoUtilities; using namespace Media; +using namespace Utility; namespace QtGui { -NotificationModel::NotificationModel(QObject *parent) : +DiagModel::DiagModel(QObject *parent) : QAbstractListModel(parent) {} -QVariant NotificationModel::headerData(int section, Qt::Orientation orientation, int role) const +QVariant DiagModel::headerData(int section, Qt::Orientation orientation, int role) const { switch(orientation) { case Qt::Horizontal: @@ -41,7 +44,7 @@ QVariant NotificationModel::headerData(int section, Qt::Orientation orientation, return QVariant(); } -int NotificationModel::columnCount(const QModelIndex &parent) const +int DiagModel::columnCount(const QModelIndex &parent) const { if(!parent.isValid()) { return 3; @@ -49,27 +52,27 @@ int NotificationModel::columnCount(const QModelIndex &parent) const return 0; } -int NotificationModel::rowCount(const QModelIndex &parent) const +int DiagModel::rowCount(const QModelIndex &parent) const { if(!parent.isValid()) { - return m_notifications.size(); + return sizeToInt(m_diag.size()); } return 0; } -Qt::ItemFlags NotificationModel::flags(const QModelIndex &index) const +Qt::ItemFlags DiagModel::flags(const QModelIndex &index) const { return QAbstractListModel::flags(index); } -QVariant NotificationModel::data(const QModelIndex &index, int role) const +QVariant DiagModel::data(const QModelIndex &index, int role) const { - if(index.isValid() && index.row() < m_notifications.size()) { + if(index.isValid() && index.row() >= 0 && static_cast(index.row()) < m_diag.size()) { switch(role) { case Qt::DisplayRole: switch(index.column()) { case 0: { - const string &context = m_notifications.at(index.row()).context(); + const string &context = m_diag[static_cast(index.row())].context(); if(context.empty()) { return tr("unspecified"); } else { @@ -77,9 +80,9 @@ QVariant NotificationModel::data(const QModelIndex &index, int role) const } } case 1: - return QString::fromUtf8(m_notifications.at(index.row()).message().c_str()); + return QString::fromUtf8(m_diag[static_cast(index.row())].message().c_str()); case 2: - return QString::fromUtf8(m_notifications.at(index.row()).creationTime().toString(DateTimeOutputFormat::DateAndTime, true).c_str()); + return QString::fromUtf8(m_diag[static_cast(index.row())].creationTime().toString(DateTimeOutputFormat::DateAndTime, true).c_str()); default: ; } @@ -87,18 +90,19 @@ QVariant NotificationModel::data(const QModelIndex &index, int role) const case Qt::DecorationRole: switch(index.column()) { case 0: - switch(m_notifications.at(index.row()).type()) { - case NotificationType::Information: - return informationIcon(); - case NotificationType::Warning: - return warningIcon(); - case NotificationType::Critical: - return errorIcon(); - case NotificationType::Debug: + switch(m_diag[static_cast(index.row())].level()) { + case DiagLevel::None: + case DiagLevel::Debug: return debugIcon(); - default: - ; + case DiagLevel::Information: + return informationIcon(); + case DiagLevel::Warning: + return warningIcon(); + case DiagLevel::Critical: + case DiagLevel::Fatal: + return errorIcon(); } + break; default: ; } @@ -110,42 +114,37 @@ QVariant NotificationModel::data(const QModelIndex &index, int role) const return QVariant(); } -const QList &NotificationModel::notifications() const +const Diagnostics &DiagModel::diagnostics() const { - return m_notifications; + return m_diag; } -void NotificationModel::setNotifications(const QList ¬ifications) +void DiagModel::setDiagnostics(const Media::Diagnostics ¬ifications) { beginResetModel(); - m_notifications = notifications; + m_diag = notifications; endResetModel(); } -void NotificationModel::setNotifications(const NotificationList ¬ifications) -{ - setNotifications(QList::fromStdList(notifications)); -} - -const QIcon &NotificationModel::informationIcon() +const QIcon &DiagModel::informationIcon() { static const QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); return icon; } -const QIcon &NotificationModel::warningIcon() +const QIcon &DiagModel::warningIcon() { static const QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning); return icon; } -const QIcon &NotificationModel::errorIcon() +const QIcon &DiagModel::errorIcon() { static const QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); return icon; } -const QIcon &NotificationModel::debugIcon() +const QIcon &DiagModel::debugIcon() { static const QIcon icon = QIcon(QStringLiteral("/images/bug")); return icon; diff --git a/gui/notificationmodel.h b/gui/notificationmodel.h index 2caecf0..05d4b54 100644 --- a/gui/notificationmodel.h +++ b/gui/notificationmodel.h @@ -1,17 +1,17 @@ #ifndef NOTIFICATIONMODEL_H #define NOTIFICATIONMODEL_H -#include +#include #include namespace QtGui { -class NotificationModel : public QAbstractListModel +class DiagModel : public QAbstractListModel { Q_OBJECT public: - explicit NotificationModel(QObject *parent = nullptr); + explicit DiagModel(QObject *parent = nullptr); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; int columnCount(const QModelIndex &parent) const; @@ -19,9 +19,8 @@ public: Qt::ItemFlags flags(const QModelIndex &index) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - const QList ¬ifications() const; - void setNotifications(const QList ¬ifications); - void setNotifications(const Media::NotificationList ¬ifications); + const Media::Diagnostics &diagnostics() const; + void setDiagnostics(const Media::Diagnostics &diagnostics); static const QIcon &informationIcon(); static const QIcon &warningIcon(); @@ -33,7 +32,7 @@ signals: public slots: private: - QList m_notifications; + Media::Diagnostics m_diag; }; diff --git a/gui/picturepreviewselection.cpp b/gui/picturepreviewselection.cpp index 68de3ab..7f5892e 100644 --- a/gui/picturepreviewselection.cpp +++ b/gui/picturepreviewselection.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -318,8 +319,10 @@ void PicturePreviewSelection::addOfSelectedType(const QString &path) TagValue &selectedCover = m_values[m_currentTypeIndex]; try { MediaFileInfo fileInfo(toNativeFileName(path).constData()); + Diagnostics diag; fileInfo.open(true); - fileInfo.parseContainerFormat(); + fileInfo.parseContainerFormat(diag); + // TODO: show diagnostic messages auto mimeType = QString::fromUtf8(fileInfo.mimeType()); bool ok; mimeType = QInputDialog::getText(this, tr("Enter/confirm mime type"), tr("Confirm or enter the mime type of the selected file."), QLineEdit::Normal, mimeType, &ok); diff --git a/gui/tageditorwidget.cpp b/gui/tageditorwidget.cpp index 968309c..bb416bb 100644 --- a/gui/tageditorwidget.cpp +++ b/gui/tageditorwidget.cpp @@ -164,7 +164,7 @@ TagEditorWidget::~TagEditorWidget() const QByteArray &TagEditorWidget::generateFileInfoHtml() { if(m_fileInfoHtml.isEmpty()) { - m_fileInfoHtml = HtmlInfo::generateInfo(m_fileInfo, m_originalNotifications); + m_fileInfoHtml = HtmlInfo::generateInfo(m_fileInfo, m_diag, m_diagReparsing); } return m_fileInfoHtml; } @@ -627,7 +627,8 @@ void TagEditorWidget::initInfoView() m_ui->tagSplitter->addWidget(m_infoTreeView); } if(!m_infoModel) { - m_infoModel = new FileInfoModel(m_fileInfo.isOpen() ? &m_fileInfo : nullptr, this); + m_infoModel = new FileInfoModel(this); + m_infoModel->setFileInfo(m_fileInfo, m_diag, m_makingResultsAvailable ? &m_diagReparsing : nullptr); m_infoTreeView->setModel(m_infoModel); m_infoTreeView->setHeaderHidden(true); m_infoTreeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); @@ -658,7 +659,7 @@ void TagEditorWidget::updateInfoView() // update info model if present if(m_infoModel) { - m_infoModel->setFileInfo(m_fileInfo.isOpen() ? &m_fileInfo : nullptr); // resets the model + m_infoModel->setFileInfo(m_fileInfo, m_diag, m_makingResultsAvailable ? &m_diagReparsing : nullptr); // resets the model } } @@ -758,8 +759,6 @@ bool TagEditorWidget::startParsing(const QString &path, bool forceRefresh) // clear previous results and status m_tags.clear(); m_fileInfo.clearParsingResults(); - m_fileInfo.invalidateStatus(); - m_fileInfo.invalidateNotifications(); if(!sameFile) { // close last file if possibly open m_fileInfo.close(); @@ -768,25 +767,26 @@ bool TagEditorWidget::startParsing(const QString &path, bool forceRefresh) m_fileInfo.setSaveFilePath(string()); m_fileInfo.setPath(toNativeFileName(path).data()); // update file name and directory - QFileInfo fileInfo(path); + const QFileInfo fileInfo(path); m_lastDir = m_currentDir; m_currentDir = fileInfo.absolutePath(); m_fileName = fileInfo.fileName(); } - // update availability of making results + // write diagnostics to m_diagReparsing if making results are avalable m_makingResultsAvailable &= sameFile; - if(!m_makingResultsAvailable) { - m_originalNotifications.clear(); - } + Diagnostics &diag = m_makingResultsAvailable ? m_diagReparsing : m_diag; + // clear diagnostics + diag.clear(); + m_diagReparsing.clear(); // show filename m_ui->fileNameLabel->setText(m_fileName); // define function to parse the file - auto startThread = [this] { + const auto startThread = [this, &diag] { char result; try { // credits for this nesting go to GCC regression 66145 try { + // try to open with write access try { - // try to open with write access m_fileInfo.reopen(false); } catch(...) { ::IoUtilities::catchIoFailure(); @@ -794,7 +794,7 @@ bool TagEditorWidget::startParsing(const QString &path, bool forceRefresh) m_fileInfo.reopen(true); } m_fileInfo.setForceFullParse(Settings::values().editor.forceFullParse); - m_fileInfo.parseEverything(); + m_fileInfo.parseEverything(diag); result = ParsingSuccessful; } catch(const Failure &) { // the file has been opened; parsing notifications will be shown in the info box @@ -806,21 +806,19 @@ bool TagEditorWidget::startParsing(const QString &path, bool forceRefresh) result = IoError; } } catch(const exception &e) { - m_fileInfo.addNotification(Media::NotificationType::Critical, string("Something completely unexpected happened: ") + e.what(), "parsing"); + diag.emplace_back(Media::DiagLevel::Critical, argsToString("Something completely unexpected happened: ", + e.what()), "parsing"); result = FatalParsingError; } catch(...) { - m_fileInfo.addNotification(Media::NotificationType::Critical, "Something completely unexpected happened", "parsing"); + diag.emplace_back(Media::DiagLevel::Critical, "Something completely unexpected happened", "parsing"); result = FatalParsingError; } - m_fileInfo.unregisterAllCallbacks(); QMetaObject::invokeMethod(this, "showFile", Qt::QueuedConnection, Q_ARG(char, result)); }; - m_fileInfo.unregisterAllCallbacks(); m_fileOperationOngoing = true; // perform the operation concurrently QtConcurrent::run(startThread); // inform user - static const QString statusMsg(tr("The file is beeing parsed ...")); + static const auto statusMsg(tr("The file is beeing parsed ...")); m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Progress); m_ui->parsingNotificationWidget->setText(statusMsg); m_ui->parsingNotificationWidget->setVisible(true); // ensure widget is visible! @@ -899,10 +897,13 @@ void TagEditorWidget::showFile(char result) } // show parsing status/result using parsing notification widget - auto worstNotificationType = m_fileInfo.worstNotificationTypeIncludingRelatedObjects(); - if(worstNotificationType >= Media::NotificationType::Critical) { - // we catched no exception, but there are critical notifications - // -> treat critical notifications as fatal parsing errors + auto diagLevel = m_diag.level(); + if (diagLevel < worstDiagLevel) { + diagLevel |= m_diagReparsing.level(); + } + if(diagLevel >= DiagLevel::Critical) { + // we catched no exception, but there are critical diag messages + // -> treat those as fatal parsing errors result = LoadingResult::FatalParsingError; } switch(result) { @@ -915,12 +916,12 @@ void TagEditorWidget::showFile(char result) m_ui->parsingNotificationWidget->setText(tr("File couldn't be parsed correctly.")); } bool multipleSegmentsNotTested = m_fileInfo.containerFormat() == ContainerFormat::Matroska && m_fileInfo.container()->segmentCount() > 1; - if(worstNotificationType >= Media::NotificationType::Critical) { + if(diagLevel >= Media::DiagLevel::Critical) { m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Critical); - m_ui->parsingNotificationWidget->appendLine(tr("There are critical parsing notifications.")); - } else if(worstNotificationType == Media::NotificationType::Warning || m_fileInfo.isReadOnly() || !m_fileInfo.areTagsSupported() || multipleSegmentsNotTested) { + m_ui->parsingNotificationWidget->appendLine(tr("Errors occured.")); + } else if(diagLevel == Media::DiagLevel::Warning || m_fileInfo.isReadOnly() || !m_fileInfo.areTagsSupported() || multipleSegmentsNotTested) { m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Warning); - if(worstNotificationType == Media::NotificationType::Warning) { + if(diagLevel == Media::DiagLevel::Warning) { m_ui->parsingNotificationWidget->appendLine(tr("There are warnings.")); } } @@ -1099,21 +1100,26 @@ bool TagEditorWidget::startSaving() m_fileInfo.setMinPadding(settings.minPadding); m_fileInfo.setMaxPadding(settings.maxPadding); m_fileInfo.setPreferredPadding(settings.preferredPadding); - // define functions to show the saving progress and to actually applying the changes - auto showProgress = [this] (StatusProvider &sender) -> void { - QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setPercentage", Qt::QueuedConnection, Q_ARG(int, static_cast(sender.currentPercentage() * 100.0))); - if(m_abortClicked) { - QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setText", Qt::QueuedConnection, Q_ARG(QString, tr("Cancelling ..."))); - m_fileInfo.tryToAbort(); - } else { - QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(sender.currentStatus()))); - } - }; - auto startThread = [this] { + const auto startThread = [this] { + // define functions to show the saving progress and to actually applying the changes + const auto showPercentage([this] (const AbortableProgressFeedback &progress) { + QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setPercentage", Qt::QueuedConnection, Q_ARG(int, progress.stepPercentage())); + }); + const auto showStep([this] (AbortableProgressFeedback &progress) { + QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setPercentage", Qt::QueuedConnection, Q_ARG(int, progress.stepPercentage())); + if(m_abortClicked) { + progress.tryToAbort(); + QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setText", Qt::QueuedConnection, Q_ARG(QString, tr("Cancelling ..."))); + } else { + QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(progress.step()))); + } + }); + AbortableProgressFeedback progress(std::move(showStep), std::move(showPercentage)); + bool processingError = false, ioError = false; try { try { - m_fileInfo.applyChanges(); + m_fileInfo.applyChanges(m_diag, progress); } catch(const Failure &) { processingError = true; } catch(...) { @@ -1121,17 +1127,14 @@ bool TagEditorWidget::startSaving() ioError = true; } } catch(const exception &e) { - m_fileInfo.addNotification(Media::NotificationType::Critical, string("Something completely unexpected happened: ") + e.what(), "making"); + m_diag.emplace_back(Media::DiagLevel::Critical, argsToString("Something completely unexpected happened: ", e.what()), "making"); processingError = true; } catch(...) { - m_fileInfo.addNotification(Media::NotificationType::Critical, "Something completely unexpected happened", "making"); + m_diag.emplace_back(Media::DiagLevel::Critical, "Something completely unexpected happened", "making"); processingError = true; } - m_fileInfo.unregisterAllCallbacks(); QMetaObject::invokeMethod(this, "showSavingResult", Qt::QueuedConnection, Q_ARG(bool, processingError), Q_ARG(bool, ioError)); }; - m_fileInfo.unregisterAllCallbacks(); - m_fileInfo.registerCallback(showProgress); m_fileOperationOngoing = true; // use another thread to perform the operation QtConcurrent::run(startThread); @@ -1155,17 +1158,17 @@ void TagEditorWidget::showSavingResult(bool processingError, bool ioError) m_ui->makingNotificationWidget->setPercentage(-1); m_ui->makingNotificationWidget->setHidden(false); m_makingResultsAvailable = true; - m_originalNotifications = m_fileInfo.gatherRelatedNotifications(); if(!processingError && !ioError) { // display status messages QString statusMsg; size_t critical = 0, warnings = 0; - for(const Notification ¬ification : m_originalNotifications) { - switch(notification.type()) { - case Media::NotificationType::Critical: + for(const auto &msg : m_diag) { + switch(msg.level()) { + case Media::DiagLevel::Fatal: + case Media::DiagLevel::Critical: ++critical; break; - case Media::NotificationType::Warning: + case Media::DiagLevel::Warning: ++warnings; break; default: @@ -1174,12 +1177,12 @@ void TagEditorWidget::showSavingResult(bool processingError, bool ioError) } if(warnings || critical) { if(critical) { - statusMsg = tr("The tags have been saved, but there is/are %1 warning(s) ", 0, warnings).arg(warnings); - statusMsg.append(tr("and %1 error(s).", 0, critical).arg(critical)); + statusMsg = tr("The tags have been saved, but there is/are %1 warning(s) ", nullptr, trQuandity(warnings)).arg(warnings); + statusMsg.append(tr("and %1 error(s).", nullptr, trQuandity(critical)).arg(critical)); } else { - statusMsg = tr("The tags have been saved, but there is/are %1 warning(s).", 0, warnings).arg(warnings); + statusMsg = tr("The tags have been saved, but there is/are %1 warning(s).", nullptr, trQuandity(warnings)).arg(warnings); } - m_ui->makingNotificationWidget->setNotificationType(critical > 0 ? NotificationType::Critical : NotificationType::Warning); + m_ui->makingNotificationWidget->setNotificationType(critical ? NotificationType::Critical : NotificationType::Warning); } else { statusMsg = tr("The tags have been saved."); diff --git a/gui/tageditorwidget.h b/gui/tageditorwidget.h index dae9a06..423e097 100644 --- a/gui/tageditorwidget.h +++ b/gui/tageditorwidget.h @@ -5,6 +5,7 @@ #include "./webviewdefs.h" #include +#include #include #include @@ -49,7 +50,7 @@ public: const QString ¤tPath() const; const QString ¤tDir() const; Media::MediaFileInfo &fileInfo(); - Media::NotificationList &originalNotifications(); + const Media::Diagnostics &diagnostics() const; bool isTagEditShown() const; const QByteArray &fileInfoHtml() const; const QByteArray &generateFileInfoHtml(); @@ -150,9 +151,10 @@ private: QString m_lastDir; QString m_saveFilePath; // status + Media::Diagnostics m_diag; + Media::Diagnostics m_diagReparsing; bool m_nextFileAfterSaving; bool m_makingResultsAvailable; - Media::NotificationList m_originalNotifications; bool m_abortClicked; bool m_fileOperationOngoing; }; @@ -191,11 +193,11 @@ inline Media::MediaFileInfo &TagEditorWidget::fileInfo() } /*! - * \brief Returns the original notifications. + * \brief Returns the diagnostic messages. */ -inline Media::NotificationList &TagEditorWidget::originalNotifications() +inline const Media::Diagnostics &TagEditorWidget::diagnostics() const { - return m_originalNotifications; + return m_diag; } /*! diff --git a/misc/htmlinfo.cpp b/misc/htmlinfo.cpp index c153891..182896f 100644 --- a/misc/htmlinfo.cpp +++ b/misc/htmlinfo.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include @@ -56,6 +55,7 @@ using namespace std; using namespace ConversionUtilities; using namespace ChronoUtilities; using namespace Media; +using namespace Utility; namespace HtmlInfo { @@ -308,11 +308,12 @@ class Generator { public: - Generator(const MediaFileInfo &file, NotificationList &originalNotifications) : + Generator(const MediaFileInfo &file, Diagnostics &diag, Diagnostics &diagReparsing) : m_writer(&m_res), m_rowMaker(m_writer), m_file(file), - originalNotifications(originalNotifications) + m_diag(diag), + m_diagReparsing(diagReparsing) {} QString mkStyle() @@ -808,33 +809,33 @@ public: } } - void mkNotifications(NotificationList ¬ifications, bool reparsing = false) + void mkNotifications(Diagnostics &diag, bool reparsing = false) { - if(notifications.size()) { - startTableSection(); - const QString moreId(reparsing ? QStringLiteral("notificationsReparsingMore") : QStringLiteral("notificationsMore")); - m_rowMaker.startRow(reparsing ? QCoreApplication::translate("HtmlInfo", "Notifications (reparsing after saving)") : QCoreApplication::translate("HtmlInfo", "Notifications")); - m_writer.writeCharacters(QCoreApplication::translate("HtmlInfo", "%1 notification(s) available", 0, static_cast(notifications.size())).arg(notifications.size())); - mkSpace(); - mkDetailsLink(moreId, QCoreApplication::translate("HtmlInfo", "show notifications")); - m_rowMaker.endRow(); - m_writer.writeEndElement(); + if (diag.empty()) { + return; + } + startTableSection(); + const QString moreId(reparsing ? QStringLiteral("notificationsReparsingMore") : QStringLiteral("notificationsMore")); + m_rowMaker.startRow(reparsing ? QCoreApplication::translate("HtmlInfo", "Notifications (reparsing after saving)") : QCoreApplication::translate("HtmlInfo", "Notifications")); + m_writer.writeCharacters(QCoreApplication::translate("HtmlInfo", "%1 notification(s) available", nullptr, trQuandity(diag.size())).arg(diag.size())); + mkSpace(); + mkDetailsLink(moreId, QCoreApplication::translate("HtmlInfo", "show notifications")); + m_rowMaker.endRow(); + m_writer.writeEndElement(); - startExtendedTableSection(moreId); - m_rowMaker.startHorizontalSubTab(QString(), QStringList() << QString() << QCoreApplication::translate("HtmlInfo", "Context") << QCoreApplication::translate("HtmlInfo", "Message") << QCoreApplication::translate("HtmlInfo", "Time")); - Notification::sortByTime(notifications); - for(const Notification ¬ification : notifications) { - m_writer.writeStartElement(QStringLiteral("tr")); - m_writer.writeEmptyElement(QStringLiteral("td")); - m_writer.writeAttribute(QStringLiteral("class"), qstr(notification.typeName())); - m_writer.writeTextElement(QStringLiteral("td"), qstr(notification.context())); - m_writer.writeTextElement(QStringLiteral("td"), qstr(notification.message())); - m_writer.writeTextElement(QStringLiteral("td"), qstr(notification.creationTime().toString(DateTimeOutputFormat::DateAndTime, false))); - m_writer.writeEndElement(); - } - m_rowMaker.endSubTab(); + startExtendedTableSection(moreId); + m_rowMaker.startHorizontalSubTab(QString(), QStringList({QString(), QCoreApplication::translate("HtmlInfo", "Context"), QCoreApplication::translate("HtmlInfo", "Message"), QCoreApplication::translate("HtmlInfo", "Time")})); + for(const auto &msg : diag) { + m_writer.writeStartElement(QStringLiteral("tr")); + m_writer.writeEmptyElement(QStringLiteral("td")); + m_writer.writeAttribute(QStringLiteral("class"), qstr(msg.levelName())); + m_writer.writeTextElement(QStringLiteral("td"), qstr(msg.context())); + m_writer.writeTextElement(QStringLiteral("td"), qstr(msg.message())); + m_writer.writeTextElement(QStringLiteral("td"), qstr(msg.creationTime().toString(DateTimeOutputFormat::DateAndTime, false))); m_writer.writeEndElement(); } + m_rowMaker.endSubTab(); + m_writer.writeEndElement(); } void mkDoc() @@ -932,8 +933,8 @@ public: if(m_file.paddingSize()) { rowMaker.mkRow(QCoreApplication::translate("HtmlInfo", "Padding size"), QStringLiteral("%1 (%2 %)").arg(qstr(dataSizeToString(m_file.paddingSize(), true))).arg(static_cast(m_file.paddingSize()) / m_file.size() * 100.0, 0, 'g', 2)); } - rowMaker.mkRow(QCoreApplication::translate("HtmlInfo", "Tag position"), container->determineTagPosition()); - rowMaker.mkRow(QCoreApplication::translate("HtmlInfo", "Index position"), container->determineIndexPosition()); + rowMaker.mkRow(QCoreApplication::translate("HtmlInfo", "Tag position"), container->determineTagPosition(m_diagReparsing)); + rowMaker.mkRow(QCoreApplication::translate("HtmlInfo", "Index position"), container->determineIndexPosition(m_diagReparsing)); m_writer.writeEndElement(); } @@ -1110,11 +1111,8 @@ public: } // notifications - auto currentNotifications = m_file.gatherRelatedNotifications(); - mkNotifications(currentNotifications, !originalNotifications.empty()); - if(!originalNotifications.empty()) { - mkNotifications(originalNotifications); - } + mkNotifications(m_diag); + mkNotifications(m_diagReparsing, true); // @@ -1132,7 +1130,8 @@ private: QByteArray m_res; RowMaker m_rowMaker; const MediaFileInfo &m_file; - NotificationList &originalNotifications; + Diagnostics &m_diag; + Diagnostics &m_diagReparsing; }; /*! @@ -1143,9 +1142,9 @@ private: * A QGuiApplication instance should be available for setting fonts. * A QApplication instance should be available for standard icons. */ -QByteArray generateInfo(const MediaFileInfo &file, NotificationList &originalNotifications) +QByteArray generateInfo(const MediaFileInfo &file, Diagnostics &diag, Diagnostics &diagReparsing) { - Generator gen(file, originalNotifications); + Generator gen(file, diag, diagReparsing); gen.mkDoc(); #ifdef QT_DEBUG QFile test(QStringLiteral("/tmp/test.xhtml")); diff --git a/misc/htmlinfo.h b/misc/htmlinfo.h index 0daffb4..e972c89 100644 --- a/misc/htmlinfo.h +++ b/misc/htmlinfo.h @@ -7,13 +7,12 @@ namespace Media { class MediaFileInfo; -class Notification; -typedef std::list NotificationList; +class Diagnostics; } namespace HtmlInfo { -QByteArray generateInfo(const Media::MediaFileInfo &file, Media::NotificationList &originalNotifications); +QByteArray generateInfo(const Media::MediaFileInfo &file, Media::Diagnostics &diag, Media::Diagnostics &diagReparsing); } diff --git a/misc/utility.h b/misc/utility.h index d906dd7..aed01e4 100644 --- a/misc/utility.h +++ b/misc/utility.h @@ -30,6 +30,16 @@ void parseFileName(const QString &fileName, QString &title, int &trackNumber); QString printModel(QAbstractItemModel *model); void printModelIndex(const QModelIndex &index, QString &res, int level); +constexpr int sizeToInt(std::size_t size) +{ + return size > std::numeric_limits::max() ? std::numeric_limits::max() : static_cast(size); +} + +constexpr int trQuandity(quint64 quandity) +{ + return quandity > std::numeric_limits::max() ? std::numeric_limits::max() : static_cast(quandity); +} + } #endif // UTILITYFEATURES_H diff --git a/renamingutility/tageditorobject.cpp b/renamingutility/tageditorobject.cpp index 9fa1720..50535d3 100644 --- a/renamingutility/tageditorobject.cpp +++ b/renamingutility/tageditorobject.cpp @@ -29,17 +29,17 @@ using namespace std; namespace RenamingUtility { /// \brief Adds notifications from \a statusProvider to \a notificationsObject. -TAGEDITOR_JS_VALUE &operator <<(TAGEDITOR_JS_VALUE ¬ificationsObject, const StatusProvider &statusProvider) +TAGEDITOR_JS_VALUE &operator <<(TAGEDITOR_JS_VALUE &diagObject, const Diagnostics &diag) { quint32 counter = 0; - for(const auto ¬ification : statusProvider.notifications()) { + for(const auto &msg : diag) { TAGEDITOR_JS_VALUE val; - val.setProperty("msg", QString::fromUtf8(notification.message().data()) TAGEDITOR_JS_READONLY); - val.setProperty("critical", notification.type() == NotificationType::Critical TAGEDITOR_JS_READONLY); - notificationsObject.setProperty(counter, val); + val.setProperty("msg", QString::fromUtf8(msg.message().data()) TAGEDITOR_JS_READONLY); + val.setProperty("critical", msg.level() >= DiagLevel::Critical TAGEDITOR_JS_READONLY); + diagObject.setProperty(counter, val); ++counter; } - return notificationsObject; + return diagObject; } /// \brief Add fields and notifications from \a tag to \a tagObject. @@ -79,8 +79,6 @@ TAGEDITOR_JS_VALUE &operator <<(TAGEDITOR_JS_VALUE &tagObject, const Tag &tag) tagObject.setProperty("diskPos", pos.position() TAGEDITOR_JS_READONLY); tagObject.setProperty("diskTotal", pos.total() TAGEDITOR_JS_READONLY); - // add notifications - tagObject.setProperty("hasCriticalNotifications", tag.hasCriticalNotifications() TAGEDITOR_JS_READONLY); return tagObject; } @@ -138,6 +136,7 @@ const QString &TagEditorObject::newRelativeDirectory() const TAGEDITOR_JS_VALUE TagEditorObject::parseFileInfo(const QString &fileName) { + Diagnostics diag; MediaFileInfo fileInfo(toNativeFileName(fileName).data()); // add basic file information @@ -153,7 +152,7 @@ TAGEDITOR_JS_VALUE TagEditorObject::parseFileInfo(const QString &fileName) // parse further file information bool criticalParseingErrorOccured = false; try { - fileInfo.parseEverything(); + fileInfo.parseEverything(diag); } catch(const Failure &) { // parsing notifications will be addded anyways criticalParseingErrorOccured = true; @@ -163,11 +162,11 @@ TAGEDITOR_JS_VALUE TagEditorObject::parseFileInfo(const QString &fileName) } // gather notifications - auto mainNotificationObject = m_engine->newArray(static_cast(fileInfo.notifications().size())); - mainNotificationObject << fileInfo; - criticalParseingErrorOccured |= fileInfo.hasCriticalNotifications(); - fileInfoObject.setProperty(QStringLiteral("hasCriticalNotifications"), criticalParseingErrorOccured); - fileInfoObject.setProperty(QStringLiteral("notifications"), mainNotificationObject); + auto diagObj = m_engine->newArray(static_cast(diag.size())); + diagObj << diag; + criticalParseingErrorOccured |= diag.level() >= DiagLevel::Critical; + fileInfoObject.setProperty(QStringLiteral("hasCriticalMessages"), criticalParseingErrorOccured); + fileInfoObject.setProperty(QStringLiteral("diagMessages"), diagObj); // add MIME-type, suitable suffix and technical summary fileInfoObject.setProperty(QStringLiteral("mimeType"), QString::fromUtf8(fileInfo.mimeType()) TAGEDITOR_JS_READONLY); @@ -186,9 +185,6 @@ TAGEDITOR_JS_VALUE TagEditorObject::parseFileInfo(const QString &fileName) combinedTagObject << tag; combinedTagNotifications << tag; tagObject << tag; - auto tagNotificationsObject = m_engine->newArray(static_cast(tag.notifications().size())); - tagNotificationsObject << tag; - tagObject.setProperty(QStringLiteral("notifications"), tagNotificationsObject TAGEDITOR_JS_READONLY); tagsObject.setProperty(tagIndex, tagObject TAGEDITOR_JS_READONLY); } combinedTagObject.setProperty(QStringLiteral("notifications"), combinedTagNotifications TAGEDITOR_JS_READONLY); @@ -206,9 +202,6 @@ TAGEDITOR_JS_VALUE TagEditorObject::parseFileInfo(const QString &fileName) trackObject.setProperty(QStringLiteral("format"), QString::fromUtf8(track.formatName())); trackObject.setProperty(QStringLiteral("formatAbbreviation"), QString::fromUtf8(track.formatAbbreviation())); trackObject.setProperty(QStringLiteral("description"), QString::fromUtf8(track.description().data())); - auto trackNotificationsObject = m_engine->newArray(static_cast(track.notifications().size())); - trackNotificationsObject << track; - trackObject.setProperty(QStringLiteral("notifications"), trackNotificationsObject TAGEDITOR_JS_READONLY); tracksObject.setProperty(trackIndex, trackObject TAGEDITOR_JS_READONLY); } @@ -228,12 +221,12 @@ TAGEDITOR_JS_VALUE TagEditorObject::parseFileName(const QString &fileName) TAGEDITOR_JS_VALUE TagEditorObject::allFiles(const QString &dirName) { - QDir dir(dirName); + const QDir dir(dirName); if(dir.exists()) { - QStringList files = dir.entryList(QDir::Files); - auto entriesObj = m_engine->newArray(files.length()); + const auto files(dir.entryList(QDir::Files)); + auto entriesObj = m_engine->newArray(static_cast(files.size())); quint32 counter = 0; - for(const QString &file : files) { + for(const auto &file : files) { entriesObj.setProperty(counter, file TAGEDITOR_JS_READONLY); ++counter; } @@ -245,9 +238,9 @@ TAGEDITOR_JS_VALUE TagEditorObject::allFiles(const QString &dirName) TAGEDITOR_JS_VALUE TagEditorObject::firstFile(const QString &dirName) { - QDir dir(dirName); + const QDir dir(dirName); if(dir.exists()) { - QStringList files = dir.entryList(QDir::Files); + const auto files(dir.entryList(QDir::Files)); if(!files.empty()) { return TAGEDITOR_JS_VALUE(files.first()); } diff --git a/tests/cli.cpp b/tests/cli.cpp index fa05654..8ead2be 100644 --- a/tests/cli.cpp +++ b/tests/cli.cpp @@ -2,9 +2,27 @@ #include #include #include -#include + +// order of includes and definition of operator << matters for C++ to resolve the correct overload #include +#include + +namespace TestUtilities { + +/*! + * \brief Prints a DiagMessage to enable using it in CPPUNIT_ASSERT_EQUAL. + */ +inline std::ostream &operator <<(std::ostream &os, const Media::DiagMessage &diagMessage) +{ + return os << diagMessage.levelName() << ':' << ' ' << diagMessage.message() << ' ' << '(' << diagMessage.context() << ')'; +} + +} + +using namespace TestUtilities; + +#include #include #include @@ -804,9 +822,10 @@ void CliTests::testExtraction() // test extraction of cover const char *const args1[] = {"tageditor", "extract", "cover", "-f", mp4File1.data(), "-o", "/tmp/extracted.jpeg", nullptr}; TESTUTILS_ASSERT_EXEC(args1); + Diagnostics diag; MediaFileInfo extractedInfo("/tmp/extracted.jpeg"); extractedInfo.open(true); - extractedInfo.parseContainerFormat(); + extractedInfo.parseContainerFormat(diag); CPPUNIT_ASSERT_EQUAL(22771_st, extractedInfo.size()); CPPUNIT_ASSERT(ContainerFormat::Jpeg == extractedInfo.containerFormat()); extractedInfo.invalidate(); @@ -819,12 +838,13 @@ void CliTests::testExtraction() CPPUNIT_ASSERT_EQUAL(0, remove("/tmp/extracted.jpeg")); TESTUTILS_ASSERT_EXEC(args3); extractedInfo.open(true); - extractedInfo.parseContainerFormat(); + extractedInfo.parseContainerFormat(diag); CPPUNIT_ASSERT_EQUAL(22771_st, extractedInfo.size()); CPPUNIT_ASSERT(ContainerFormat::Jpeg == extractedInfo.containerFormat()); CPPUNIT_ASSERT_EQUAL(0, remove("/tmp/extracted.jpeg")); CPPUNIT_ASSERT_EQUAL(0, remove(mp4File2.data())); CPPUNIT_ASSERT_EQUAL(0, remove((mp4File2 + ".bak").data())); + CPPUNIT_ASSERT_EQUAL(Diagnostics(), diag); } /*!