Adapt to new notification handling

This commit is contained in:
Martchus 2018-03-06 02:04:35 +01:00
parent 254b9b7661
commit c2b2e4ac44
18 changed files with 368 additions and 345 deletions

View File

@ -5,6 +5,7 @@
#include <c++utilities/conversion/conversionexception.h> #include <c++utilities/conversion/conversionexception.h>
#include <c++utilities/conversion/stringconversion.h> #include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/conversion/stringbuilder.h>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
@ -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"); static const string context("applying specified attachments");
AbstractAttachment *attachment = nullptr; AbstractAttachment *attachment = nullptr;
@ -48,30 +49,30 @@ void AttachmentInfo::apply(AbstractContainer *container)
cerr << "Argument --update-argument specified but no name/path provided." << endl; cerr << "Argument --update-argument specified but no name/path provided." << endl;
return; return;
} }
apply(container->createAttachment()); apply(container->createAttachment(), diag);
break; break;
case AttachmentAction::Update: case AttachmentAction::Update:
if(hasId) { if(hasId) {
for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) { for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) {
attachment = container->attachment(i); attachment = container->attachment(i);
if(attachment->id() == id) { if(attachment->id() == id) {
apply(attachment); apply(attachment, diag);
attachmentFound = true; attachmentFound = true;
} }
} }
if(!attachmentFound) { 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) { } else if(name) {
for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) { for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) {
attachment = container->attachment(i); attachment = container->attachment(i);
if(attachment->name() == name) { if(attachment->name() == name) {
apply(attachment); apply(attachment, diag);
attachmentFound = true; attachmentFound = true;
} }
} }
if(!attachmentFound) { 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 { } else {
cerr << "Argument --update-argument specified but no ID/name provided." << endl; cerr << "Argument --update-argument specified but no ID/name provided." << endl;
@ -87,7 +88,7 @@ void AttachmentInfo::apply(AbstractContainer *container)
} }
} }
if(!attachmentFound) { 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) { } else if(name) {
for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) { for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) {
@ -98,7 +99,7 @@ void AttachmentInfo::apply(AbstractContainer *container)
} }
} }
if(!attachmentFound) { 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 { } else {
cerr << "Argument --remove-argument specified but no ID/name provided." << endl; 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) { if(hasId) {
attachment->setId(id); attachment->setId(id);
} }
if(path) { if(path) {
attachment->setFile(path); attachment->setFile(path, diag);
} }
if(name) { if(name) {
attachment->setName(name); attachment->setName(name);
@ -134,13 +135,13 @@ void AttachmentInfo::reset()
path = name = mime = desc = nullptr; path = name = mime = desc = nullptr;
} }
bool AttachmentInfo::next(AbstractContainer *container) bool AttachmentInfo::next(AbstractContainer *container, Media::Diagnostics &diag)
{ {
if(!id && !path && !name && !mime && !desc) { if(!id && !path && !name && !mime && !desc) {
// skip empty attachment infos // skip empty attachment infos
return false; return false;
} }
apply(container); apply(container, diag);
reset(); reset();
return true; return true;
} }

View File

@ -1,6 +1,8 @@
#ifndef CLI_ATTACHMENT_INFO #ifndef CLI_ATTACHMENT_INFO
#define CLI_ATTACHMENT_INFO #define CLI_ATTACHMENT_INFO
#include <tagparser/diagnostics.h>
#include <c++utilities/conversion/types.h> #include <c++utilities/conversion/types.h>
namespace Media { namespace Media {
@ -21,10 +23,10 @@ class AttachmentInfo
public: public:
AttachmentInfo(); AttachmentInfo();
void parseDenotation(const char *denotation); void parseDenotation(const char *denotation);
void apply(Media::AbstractContainer *container); void apply(Media::AbstractContainer *container, Media::Diagnostics &diag);
void apply(Media::AbstractAttachment *attachment); void apply(Media::AbstractAttachment *attachment, Media::Diagnostics &diag);
void reset(); void reset();
bool next(Media::AbstractContainer *container); bool next(Media::AbstractContainer *container, Media::Diagnostics &diag);
AttachmentAction action; AttachmentAction action;
uint64 id; uint64 id;

View File

@ -2,6 +2,8 @@
#include "./fieldmapping.h" #include "./fieldmapping.h"
#include <tagparser/mediafileinfo.h> #include <tagparser/mediafileinfo.h>
#include <tagparser/diagnostics.h>
#include <tagparser/progressfeedback.h>
#include <tagparser/matroska/matroskatag.h> #include <tagparser/matroska/matroskatag.h>
#include <tagparser/mp4/mp4tag.h> #include <tagparser/mp4/mp4tag.h>
#include <tagparser/vorbis/vorbiscomment.h> #include <tagparser/vorbis/vorbiscomment.h>
@ -118,16 +120,16 @@ string incremented(const string &str, unsigned int toIncrement)
return res; return res;
} }
void printNotifications(NotificationList &notifications, const char *head, bool beVerbose) void printDiagMessages(const Diagnostics &diag, const char *head, bool beVerbose)
{ {
if(notifications.empty()) { if(diag.empty()) {
return; return;
} }
if(!beVerbose) { if(!beVerbose) {
for(const auto &notification : notifications) { for(const auto &message : diag) {
switch(notification.type()) { switch(message.level()) {
case NotificationType::Debug: case DiagLevel::Debug:
case NotificationType::Information: case DiagLevel::Information:
break; break;
default: default:
goto printNotifications; goto printNotifications;
@ -140,45 +142,37 @@ printNotifications:
if(head) { if(head) {
cout << " - " << head << endl; cout << " - " << head << endl;
} }
Notification::sortByTime(notifications); for(const auto &message : diag) {
for(const auto &notification : notifications) { switch(message.level()) {
switch(notification.type()) { case DiagLevel::Debug:
case NotificationType::Debug:
if(beVerbose) { if(beVerbose) {
cout << " Debug "; cout << " Debug ";
break; break;
} else { } else {
continue; continue;
} }
case NotificationType::Information: case DiagLevel::Information:
if(beVerbose) { if(beVerbose) {
cout << " Information "; cout << " Information ";
break; break;
} else { } else {
continue; continue;
} }
case NotificationType::Warning: case DiagLevel::Warning:
cout << " Warning "; cout << " Warning ";
break; break;
case NotificationType::Critical: case DiagLevel::Critical:
cout << " Error "; cout << " Error ";
break; break;
default: default:
; ;
} }
cout << notification.creationTime().toString(DateTimeOutputFormat::TimeOnly) << " "; cout << message.creationTime().toString(DateTimeOutputFormat::TimeOnly) << " ";
cout << notification.context() << ": "; cout << message.context() << ": ";
cout << notification.message() << '\n'; 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) void printProperty(const char *propName, const char *value, const char *suffix, Indentation indentation)
{ {
if(!*value) { if(!*value) {
@ -658,29 +652,23 @@ bool stringToBool(const string &str)
} }
bool logLineFinalized = true; bool logLineFinalized = true;
static string lastLoggedStatus; static string lastStep;
void logStatus(const StatusProvider &statusProvider) void logNextStep(const AbortableProgressFeedback &progress)
{ {
if(statusProvider.currentStatus() != lastLoggedStatus) { // finalize previous step
// the ongoing operation ("status") has changed if(!logLineFinalized) {
// -> finalize previous line and make new line cout << "\r - [100%] " << lastStep << endl;
if(!logLineFinalized) { logLineFinalized = true;
cout << "\r - [100%] " << lastLoggedStatus << endl;
logLineFinalized = true;
}
// -> update lastStatus
lastLoggedStatus = statusProvider.currentStatus();
} }
// print line for next step
lastStep = progress.step();
cout << "\r - [" << setw(3) << static_cast<unsigned int>(progress.stepPercentage()) << "%] " << lastStep << flush;
logLineFinalized = false;
}
// update current line if an operation is ongoing (status is not empty) void logStepPercentage(const Media::AbortableProgressFeedback &progress)
if(!lastLoggedStatus.empty()) { {
int percentage = static_cast<int>(statusProvider.currentPercentage() * 100); cout << "\r - [" << setw(3) << static_cast<unsigned int>(progress.stepPercentage()) << "%] " << lastStep << flush;
if(percentage < 0) {
percentage = 0;
}
cout << "\r - [" << setw(3) << percentage << "%] " << lastLoggedStatus << flush;
logLineFinalized = false;
}
} }
void finalizeLog() void finalizeLog()
@ -688,9 +676,9 @@ void finalizeLog()
if(logLineFinalized) { if(logLineFinalized) {
return; return;
} }
cout << '\n'; cout << "\r - [100%] " << lastStep << '\n';
logLineFinalized = true; logLineFinalized = true;
lastLoggedStatus.clear(); lastStep.clear();
} }
} }

View File

@ -22,6 +22,8 @@ class Argument;
namespace Media { namespace Media {
class MediaFileInfo; class MediaFileInfo;
class Diagnostics;
class AbortableProgressFeedback;
enum class TagUsage; enum class TagUsage;
enum class ElementPosition; enum class ElementPosition;
} }
@ -263,8 +265,7 @@ constexpr bool isDigit(char c)
std::string incremented(const std::string &str, unsigned int toIncrement = 1); std::string incremented(const std::string &str, unsigned int toIncrement = 1);
void printNotifications(NotificationList &notifications, const char *head = nullptr, bool beVerbose = false); void printDiagMessages(const Media::Diagnostics &diag, const char *head = nullptr, bool beVerbose = false);
void printNotifications(const MediaFileInfo &fileInfo, 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, 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); 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); std::string tagName(const Tag *tag);
bool stringToBool(const std::string &str); bool stringToBool(const std::string &str);
extern bool logLineFinalized; extern bool logLineFinalized;
void logStatus(const StatusProvider &statusProvider); void logNextStep(const Media::AbortableProgressFeedback &progress);
void logStepPercentage(const Media::AbortableProgressFeedback &progress);
void finalizeLog(); void finalizeLog();
} }

View File

@ -18,6 +18,8 @@
#include <tagparser/abstractattachment.h> #include <tagparser/abstractattachment.h>
#include <tagparser/abstractchapter.h> #include <tagparser/abstractchapter.h>
#include <tagparser/backuphelper.h> #include <tagparser/backuphelper.h>
#include <tagparser/diagnostics.h>
#include <tagparser/progressfeedback.h>
#ifdef TAGEDITOR_JSON_EXPORT #ifdef TAGEDITOR_JSON_EXPORT
# include <reflective_rapidjson/json/reflector.h> # include <reflective_rapidjson/json/reflector.h>
@ -107,18 +109,21 @@ void generateFileInfo(const ArgumentOccurrence &, const Argument &inputFileArg,
MediaFileInfo inputFileInfo(inputFileArg.values().front()); MediaFileInfo inputFileInfo(inputFileArg.values().front());
inputFileInfo.setForceFullParse(validateArg.isPresent()); inputFileInfo.setForceFullParse(validateArg.isPresent());
inputFileInfo.open(true); 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; (outputFileArg.isPresent() ? cout : cerr) << "Saving file info for \"" << inputFileArg.values().front() << "\" ..." << endl;
NotificationList origNotify; if(!outputFileArg.isPresent()) {
if(outputFileArg.isPresent()) { cout << HtmlInfo::generateInfo(inputFileInfo, diag, diagReparsing).data() << endl;
QFile file(fromNativeFileName(outputFileArg.values().front())); return;
if(file.open(QFile::WriteOnly) && file.write(HtmlInfo::generateInfo(inputFileInfo, origNotify)) && file.flush()) { }
cout << "File information has been saved to \"" << outputFileArg.values().front() << "\"." << endl; QFile file(fromNativeFileName(outputFileArg.values().front()));
} else { if(file.open(QFile::WriteOnly) && file.write(HtmlInfo::generateInfo(inputFileInfo, diag, diagReparsing)) && file.flush()) {
cerr << Phrases::Error << "An IO error occured when writing the file \"" << outputFileArg.values().front() << "\"." << Phrases::EndFlush; cout << "File information has been saved to \"" << outputFileArg.values().front() << "\"." << endl;
}
} else { } 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 &) { } catch(const Media::Failure &) {
cerr << Phrases::Error << "A parsing failure occured when reading the file \"" << inputFileArg.values().front() << "\"." << Phrases::EndFlush; 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; MediaFileInfo fileInfo;
for(const char *file : filesArg.values()) { for(const char *file : filesArg.values()) {
Diagnostics diag;
try { try {
// parse tags // parse tags
fileInfo.setPath(file); fileInfo.setPath(file);
fileInfo.open(true); fileInfo.open(true);
fileInfo.parseContainerFormat(); fileInfo.parseContainerFormat(diag);
fileInfo.parseTracks(); fileInfo.parseTracks(diag);
fileInfo.parseAttachments(); fileInfo.parseAttachments(diag);
fileInfo.parseChapters(); fileInfo.parseChapters(diag);
// print general/container-related info // print general/container-related info
cout << "Technical information for \"" << file << "\":\n"; cout << "Technical information for \"" << file << "\":\n";
@ -176,8 +182,8 @@ void displayFileInfo(const ArgumentOccurrence &, const Argument &filesArg, const
printProperty("Duration", container->duration()); printProperty("Duration", container->duration());
printProperty("Creation time", container->creationTime()); printProperty("Creation time", container->creationTime());
printProperty("Modification time", container->modificationTime()); printProperty("Modification time", container->modificationTime());
printProperty("Tag position", container->determineTagPosition()); printProperty("Tag position", container->determineTagPosition(diag));
printProperty("Index position", container->determineIndexPosition()); printProperty("Index position", container->determineIndexPosition(diag));
} }
if(fileInfo.paddingSize()) { if(fileInfo.paddingSize()) {
printProperty("Padding", dataSizeToString(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; 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; cout << endl;
} }
} }
@ -315,12 +321,13 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &filesArg, const A
MediaFileInfo fileInfo; MediaFileInfo fileInfo;
for(const char *file : filesArg.values()) { for(const char *file : filesArg.values()) {
Diagnostics diag;
try { try {
// parse tags // parse tags
fileInfo.setPath(file); fileInfo.setPath(file);
fileInfo.open(true); fileInfo.open(true);
fileInfo.parseContainerFormat(); fileInfo.parseContainerFormat(diag);
fileInfo.parseTags(); fileInfo.parseTags(diag);
cout << "Tag information for \"" << file << "\":\n"; cout << "Tag information for \"" << file << "\":\n";
const auto tags = fileInfo.tags(); const auto tags = fileInfo.tags();
if(!tags.empty()) { if(!tags.empty()) {
@ -353,7 +360,7 @@ void displayTagInfo(const Argument &fieldsArg, const Argument &filesArg, const A
::IoUtilities::catchIoFailure(); ::IoUtilities::catchIoFailure();
cerr << Phrases::Error << "An IO failure occured when reading the file \"" << file << "\"." << Phrases::EndFlush; 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; cout << endl;
} }
} }
@ -454,17 +461,16 @@ void setTagInfo(const SetTagInfoArgs &args)
// iterate through all specified files // iterate through all specified files
unsigned int fileIndex = 0; unsigned int fileIndex = 0;
static const string context("setting tags"); static string context("setting tags");
NotificationList notifications;
for(const char *file : args.filesArg.values()) { for(const char *file : args.filesArg.values()) {
Diagnostics diag;
try { try {
// parse tags and tracks (tracks are relevent because track meta-data such as language can be changed as well) // 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; cout << TextAttribute::Bold << "Setting tag information for \"" << file << "\" ..." << Phrases::EndFlush;
notifications.clear();
fileInfo.setPath(file); fileInfo.setPath(file);
fileInfo.parseContainerFormat(); fileInfo.parseContainerFormat(diag);
fileInfo.parseTags(); fileInfo.parseTags(diag);
fileInfo.parseTracks(); fileInfo.parseTracks(diag);
vector<Tag *> tags; vector<Tag *> tags;
// remove tags with the specified targets // remove tags with the specified targets
@ -517,7 +523,7 @@ void setTagInfo(const SetTagInfoArgs &args)
// alter tags // alter tags
fileInfo.tags(tags); fileInfo.tags(tags);
if(tags.empty()) { 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 { } else {
// iterate through all tags // iterate through all tags
for(auto *tag : tags) { for(auto *tag : tags) {
@ -534,7 +540,7 @@ void setTagInfo(const SetTagInfoArgs &args)
if(!tag->canEncodingBeUsed(denotedEncoding)) { if(!tag->canEncodingBeUsed(denotedEncoding)) {
usedEncoding = tag->proposedTextEncoding(); usedEncoding = tag->proposedTextEncoding();
if(args.encodingArg.isPresent()) { 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 // iterate through all denoted field values
@ -553,8 +559,9 @@ void setTagInfo(const SetTagInfoArgs &args)
try { try {
// assume the file refers to a picture // assume the file refers to a picture
MediaFileInfo fileInfo(relevantDenotedValue->value); MediaFileInfo fileInfo(relevantDenotedValue->value);
Diagnostics diag;
fileInfo.open(true); fileInfo.open(true);
fileInfo.parseContainerFormat(); fileInfo.parseContainerFormat(diag);
auto buff = make_unique<char []>(fileInfo.size()); auto buff = make_unique<char []>(fileInfo.size());
fileInfo.stream().seekg(0); fileInfo.stream().seekg(0);
fileInfo.stream().read(buff.get(), fileInfo.size()); fileInfo.stream().read(buff.get(), fileInfo.size());
@ -562,10 +569,10 @@ void setTagInfo(const SetTagInfoArgs &args)
value.setMimeType(fileInfo.mimeType()); value.setMimeType(fileInfo.mimeType());
convertedValues.emplace_back(move(value)); convertedValues.emplace_back(move(value));
} catch(const Media::Failure &) { } 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(...) { } catch(...) {
::IoUtilities::catchIoFailure(); ::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 { } else {
convertedValues.emplace_back(relevantDenotedValue->value, TagTextEncoding::Utf8, usedEncoding); convertedValues.emplace_back(relevantDenotedValue->value, TagTextEncoding::Utf8, usedEncoding);
@ -579,7 +586,7 @@ void setTagInfo(const SetTagInfoArgs &args)
try { try {
denotedScope.field.setValues(tag, tagType, convertedValues); denotedScope.field.setValues(tag, tagType, convertedValues);
} catch(const ConversionException &e) { } 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")) { } else if(field.denotes("default")) {
track->setDefault(stringToBool(value)); track->setDefault(stringToBool(value));
} else { } 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) { } 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; bool attachmentsModified = false;
if(args.addAttachmentArg.isPresent() || args.updateAttachmentArg.isPresent() || args.removeAttachmentArg.isPresent() || args.removeExistingAttachmentsArg.isPresent()) { if(args.addAttachmentArg.isPresent() || args.updateAttachmentArg.isPresent() || args.removeAttachmentArg.isPresent() || args.removeExistingAttachmentsArg.isPresent()) {
static const string context("setting attachments"); static const string context("setting attachments");
fileInfo.parseAttachments(); fileInfo.parseAttachments(diag);
if(fileInfo.attachmentsParsingStatus() == ParsingStatus::Ok) { if(fileInfo.attachmentsParsingStatus() == ParsingStatus::Ok) {
if(container) { if(container) {
// ignore all existing attachments if argument is specified // 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)) { for(const char *value : args.addAttachmentArg.values(i)) {
currentInfo.parseDenotation(value); currentInfo.parseDenotation(value);
} }
attachmentsModified |= currentInfo.next(container); attachmentsModified |= currentInfo.next(container, diag);
} }
currentInfo.action = AttachmentAction::Update; currentInfo.action = AttachmentAction::Update;
for(size_t i = 0, occurrences = args.updateAttachmentArg.occurrences(); i != occurrences; ++i) { for(size_t i = 0, occurrences = args.updateAttachmentArg.occurrences(); i != occurrences; ++i) {
for(const char *value : args.updateAttachmentArg.values(i)) { for(const char *value : args.updateAttachmentArg.values(i)) {
currentInfo.parseDenotation(value); currentInfo.parseDenotation(value);
} }
attachmentsModified |= currentInfo.next(container); attachmentsModified |= currentInfo.next(container, diag);
} }
currentInfo.action = AttachmentAction::Remove; currentInfo.action = AttachmentAction::Remove;
for(size_t i = 0, occurrences = args.removeAttachmentArg.occurrences(); i != occurrences; ++i) { for(size_t i = 0, occurrences = args.removeAttachmentArg.occurrences(); i != occurrences; ++i) {
for(const char *value : args.removeAttachmentArg.values(i)) { for(const char *value : args.removeAttachmentArg.values(i)) {
currentInfo.parseDenotation(value); currentInfo.parseDenotation(value);
} }
attachmentsModified |= currentInfo.next(container); attachmentsModified |= currentInfo.next(container, diag);
} }
} else { } 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 { } else {
// notification will be added by the file info automatically // notification will be added by the file info automatically
@ -677,22 +684,14 @@ void setTagInfo(const SetTagInfoArgs &args)
} }
// apply changes // apply changes
fileInfo.setSaveFilePath(currentOutputFile != noMoreOutputFiles ? string(*currentOutputFile) : string());
try { try {
// save parsing notifications because notifications of sub objects like tags, tracks, ... will be gone after applying changes // create handler for progress updates and aborting
fileInfo.setSaveFilePath(currentOutputFile != noMoreOutputFiles ? string(*currentOutputFile) : string()); AbortableProgressFeedback progress(logNextStep, logStepPercentage);
fileInfo.gatherRelatedNotifications(notifications); const InterruptHandler handler(bind(&AbortableProgressFeedback::tryToAbort, ref(progress)));
fileInfo.invalidateNotifications();
// register handler for logging status // apply changes
fileInfo.registerCallback(logStatus); fileInfo.applyChanges(diag, progress);
// register interrupt handler
const InterruptHandler handler([&fileInfo] {
fileInfo.tryToAbort();
});
// apply changes and gather notifications
fileInfo.applyChanges();
// notify about completion // notify about completion
finalizeLog(); 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; cerr << " - " << Phrases::Error << "An IO failure occured when reading/writing the file \"" << file << "\"." << Phrases::EndFlush;
} }
fileInfo.gatherRelatedNotifications(notifications); printDiagMessages(diag, "Diagnostic messages:", args.verboseArg.isPresent());
printNotifications(notifications, "Notifications:", args.verboseArg.isPresent());
// continue with next file // continue with next file
++fileIndex; ++fileIndex;
@ -747,6 +745,7 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const
MediaFileInfo inputFileInfo; MediaFileInfo inputFileInfo;
for(const char *file : inputFilesArg.values()) { for(const char *file : inputFilesArg.values()) {
Diagnostics diag;
try { try {
// setup media file info // setup media file info
inputFileInfo.setPath(file); inputFileInfo.setPath(file);
@ -756,8 +755,8 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const
if(!fieldDenotations.empty()) { if(!fieldDenotations.empty()) {
// extract tag field // extract tag field
(outputFileArg.isPresent() ? cout : cerr) << "Extracting field " << fieldArg.values().front() << " of \"" << file << "\" ..." << endl; (outputFileArg.isPresent() ? cout : cerr) << "Extracting field " << fieldArg.values().front() << " of \"" << file << "\" ..." << endl;
inputFileInfo.parseContainerFormat(); inputFileInfo.parseContainerFormat(diag);
inputFileInfo.parseTags(); inputFileInfo.parseTags(diag);
auto tags = inputFileInfo.tags(); auto tags = inputFileInfo.tags();
vector<pair<const TagValue *, string> > values; vector<pair<const TagValue *, string> > values;
// iterate through all tags // 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)); values.emplace_back(value, joinStrings({tag->typeName(), numberToString(values.size())}, "-", true));
} }
} catch(const ConversionException &e) { } 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; logStream << " of \"" << file << "\" ..." << endl;
inputFileInfo.parseContainerFormat(); inputFileInfo.parseContainerFormat(diag);
inputFileInfo.parseAttachments(); inputFileInfo.parseAttachments(diag);
vector<pair<const AbstractAttachment *, string> > attachments; vector<pair<const AbstractAttachment *, string> > attachments;
// iterate through all attachments // iterate through all attachments
for(const AbstractAttachment *attachment : inputFileInfo.attachments()) { for(const AbstractAttachment *attachment : inputFileInfo.attachments()) {
@ -856,7 +855,7 @@ void extractField(const Argument &fieldArg, const Argument &attachmentArg, const
::IoUtilities::catchIoFailure(); ::IoUtilities::catchIoFailure();
cerr << Phrases::Error << "An IO failure occured when reading the file \"" << file << "\"." << Phrases::End; 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 // gather tags for each file
for(const char *file : filesArg.values()) { for(const char *file : filesArg.values()) {
Diagnostics diag;
try { try {
// parse tags // parse tags
fileInfo.setPath(file); fileInfo.setPath(file);
fileInfo.open(true); fileInfo.open(true);
fileInfo.parseContainerFormat(); fileInfo.parseContainerFormat(diag);
fileInfo.parseTags(); fileInfo.parseTags(diag);
fileInfo.parseTracks(); fileInfo.parseTracks(diag);
jsonData.emplace_back(fileInfo, document.GetAllocator()); jsonData.emplace_back(fileInfo, document.GetAllocator());
} catch(const Media::Failure &) { } catch(const Media::Failure &) {
cerr << Phrases::Error << "A parsing failure occured when reading the file \"" << file << "\"." << Phrases::EndFlush; 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 // print the gathered data as JSON document
ReflectiveRapidJSON::JsonReflector::push(jsonData, document, document.GetAllocator()); ReflectiveRapidJSON::JsonReflector::push(jsonData, document, document.GetAllocator());
RAPIDJSON_NAMESPACE::OStreamWrapper osw(cout); RAPIDJSON_NAMESPACE::OStreamWrapper osw(cout);

View File

@ -6,6 +6,7 @@
#include <tagparser/mediafileinfo.h> #include <tagparser/mediafileinfo.h>
#include <tagparser/abstractattachment.h> #include <tagparser/abstractattachment.h>
#include <tagparser/diagnostics.h>
#include <qtutilities/misc/conversion.h> #include <qtutilities/misc/conversion.h>
@ -91,16 +92,17 @@ void AttachmentsEdit::invalidate()
void AttachmentsEdit::addFile(const QString &path) void AttachmentsEdit::addFile(const QString &path)
{ {
if(fileInfo() && fileInfo()->attachmentsParsingStatus() == ParsingStatus::Ok && fileInfo()->container()) { 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 {
throw Failure(); 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() void AttachmentsEdit::showFileSelection()

View File

@ -11,7 +11,7 @@
#include <tagparser/mp4/mp4container.h> #include <tagparser/mp4/mp4container.h>
#include <tagparser/abstracttrack.h> #include <tagparser/abstracttrack.h>
#include <tagparser/abstractattachment.h> #include <tagparser/abstractattachment.h>
#include <tagparser/notification.h> #include <tagparser/diagnostics.h>
#include <c++utilities/chrono/datetime.h> #include <c++utilities/chrono/datetime.h>
#include <c++utilities/conversion/stringconversion.h> #include <c++utilities/conversion/stringconversion.h>
@ -30,6 +30,7 @@ using namespace std;
using namespace ChronoUtilities; using namespace ChronoUtilities;
using namespace ConversionUtilities; using namespace ConversionUtilities;
using namespace Media; using namespace Media;
using namespace Utility;
namespace QtGui { namespace QtGui {
@ -115,34 +116,33 @@ private:
QStandardItem *m_item; QStandardItem *m_item;
}; };
void addNotifications(Media::NotificationList *notifications, QStandardItem *parent) void addDiagMessages(Media::Diagnostics *diag, QStandardItem *parent)
{ {
Notification::sortByTime(*notifications); for(const auto &msg : *diag) {
for(Notification &notification : *notifications) {
QList<QStandardItem *> notificationRow; QList<QStandardItem *> notificationRow;
notificationRow.reserve(3); notificationRow.reserve(3);
auto *firstItem = defaultItem(QString::fromUtf8(notification.creationTime().toString().data())); auto *firstItem = defaultItem(QString::fromUtf8(msg.creationTime().toString().data()));
switch(notification.type()) { switch(msg.level()) {
case NotificationType::None: case DiagLevel::None:
break; case DiagLevel::Debug:
case NotificationType::Debug:
firstItem->setIcon(FileInfoModel::debugIcon()); firstItem->setIcon(FileInfoModel::debugIcon());
break; break;
case NotificationType::Information: case DiagLevel::Information:
firstItem->setIcon(FileInfoModel::informationIcon()); firstItem->setIcon(FileInfoModel::informationIcon());
break; break;
case NotificationType::Warning: case DiagLevel::Warning:
firstItem->setIcon(FileInfoModel::warningIcon()); firstItem->setIcon(FileInfoModel::warningIcon());
break; break;
case NotificationType::Critical: case DiagLevel::Critical:
case DiagLevel::Fatal:
firstItem->setIcon(FileInfoModel::errorIcon()); firstItem->setIcon(FileInfoModel::errorIcon());
break; break;
} }
parent->appendRow(QList<QStandardItem *>() parent->appendRow(QList<QStandardItem *>()
<< firstItem << firstItem
<< defaultItem(QString::fromUtf8(notification.message().data())) << defaultItem(QString::fromUtf8(msg.message().data()))
<< defaultItem(QString::fromUtf8(notification.context().data()))); << defaultItem(QString::fromUtf8(msg.context().data())));
} }
} }
@ -190,10 +190,9 @@ template<class ElementType, bool isAdditional = false> void addElementNode(const
/*! /*!
* \brief Constructs a new instance with the specified \a fileInfo which might be nullptr. * \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) QStandardItemModel(parent)
{ {
setFileInfo(fileInfo);
} }
QVariant FileInfoModel::headerData(int section, Qt::Orientation orientation, int role) const 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. * \brief Assigns a Media::MediaFileInfo.
* \remarks Causes updating the internal cache and resets the model. * \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_file = &fileInfo;
m_originalNotifications = originalNotifications; m_diag = &diag;
m_diagReparsing = diagReparsing;
updateCache(); updateCache();
} }
@ -271,6 +271,9 @@ void FileInfoModel::updateCache()
beginResetModel(); beginResetModel();
clear(); clear();
if(m_file) { if(m_file) {
// get diag
Diagnostics &diag = m_diagReparsing ? *m_diagReparsing : *m_diag;
// get container // get container
AbstractContainer *container = m_file->container(); AbstractContainer *container = m_file->container();
@ -327,8 +330,8 @@ void FileInfoModel::updateCache()
containerHelper.appendRow(tr("Document type"), container->documentType()); containerHelper.appendRow(tr("Document type"), container->documentType());
containerHelper.appendRow(tr("Document version"), container->doctypeVersion()); containerHelper.appendRow(tr("Document version"), container->doctypeVersion());
containerHelper.appendRow(tr("Document read version"), container->doctypeReadVersion()); containerHelper.appendRow(tr("Document read version"), container->doctypeReadVersion());
containerHelper.appendRow(tr("Tag position"), Utility::elementPositionToQString(container->determineTagPosition())); containerHelper.appendRow(tr("Tag position"), Utility::elementPositionToQString(container->determineTagPosition(diag)));
containerHelper.appendRow(tr("Index position"), Utility::elementPositionToQString(container->determineIndexPosition())); containerHelper.appendRow(tr("Index position"), Utility::elementPositionToQString(container->determineIndexPosition(diag)));
} }
containerHelper.appendRow(tr("Padding size"), m_file->paddingSize()); containerHelper.appendRow(tr("Padding size"), m_file->paddingSize());
@ -338,7 +341,7 @@ void FileInfoModel::updateCache()
if(!tags.empty()) { if(!tags.empty()) {
auto *tagsItem = defaultItem(tr("Tags")); auto *tagsItem = defaultItem(tr("Tags"));
setItem(++currentRow, tagsItem); setItem(++currentRow, tagsItem);
setItem(currentRow, 1, defaultItem(tr("%1 tag(s) assigned", nullptr, static_cast<int>(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) { for(const Tag *tag : tags) {
auto *tagItem = defaultItem(tag->typeName()); auto *tagItem = defaultItem(tag->typeName());
@ -360,9 +363,9 @@ void FileInfoModel::updateCache()
setItem(++currentRow, tracksItem); setItem(++currentRow, tracksItem);
const string summary(m_file->technicalSummary()); const string summary(m_file->technicalSummary());
if(summary.empty()) { if(summary.empty()) {
setItem(currentRow, 1, defaultItem(tr("%1 track(s) contained", nullptr, static_cast<int>(tracks.size())).arg(tracks.size()))); setItem(currentRow, 1, defaultItem(tr("%1 track(s) contained", nullptr, trQuandity(tracks.size())).arg(tracks.size())));
} else { } else {
setItem(currentRow, 1, defaultItem(tr("%1 track(s): ", nullptr, static_cast<int>(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; size_t number = 0;
@ -461,7 +464,7 @@ void FileInfoModel::updateCache()
if(!attachments.empty()) { if(!attachments.empty()) {
auto *attachmentsItem = defaultItem(tr("Attachments")); auto *attachmentsItem = defaultItem(tr("Attachments"));
setItem(++currentRow, attachmentsItem); 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; size_t number = 0;
for(const AbstractAttachment *attachment : attachments) { for(const AbstractAttachment *attachment : attachments) {
@ -524,7 +527,7 @@ void FileInfoModel::updateCache()
if(!editionEntries.empty()) { if(!editionEntries.empty()) {
auto *editionsItem = defaultItem(tr("Editions")); auto *editionsItem = defaultItem(tr("Editions"));
setItem(++currentRow, editionsItem); 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; size_t editionNumber = 0;
for(const auto &edition : editionEntries) { for(const auto &edition : editionEntries) {
auto *editionItem = defaultItem(tr("Edition #%1").arg(++editionNumber)); auto *editionItem = defaultItem(tr("Edition #%1").arg(++editionNumber));
@ -554,7 +557,7 @@ void FileInfoModel::updateCache()
if(!chapters.empty()) { if(!chapters.empty()) {
auto *chaptersItem = defaultItem(tr("Chapters")); auto *chaptersItem = defaultItem(tr("Chapters"));
setItem(++currentRow, chaptersItem); 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) { for(const AbstractChapter *chapter : chapters) {
addChapter(chapter, chaptersItem); addChapter(chapter, chaptersItem);
} }
@ -592,18 +595,14 @@ void FileInfoModel::updateCache()
} }
// notifications // notifications
auto currentNotifications = m_file->gatherRelatedNotifications(); auto *const diagItem = defaultItem(tr("Diagnostic messages"));
if(!currentNotifications.empty()) { addDiagMessages(m_diag, diagItem);
auto *notificationsItem= defaultItem(m_originalNotifications ? tr("Notifications (reparsing after saving)") : tr("Notifications")); setItem(++currentRow, diagItem);
addNotifications(&currentNotifications, notificationsItem); if (m_diagReparsing) {
setItem(++currentRow, notificationsItem); 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(); endResetModel();
} }

View File

@ -7,8 +7,7 @@
namespace Media { namespace Media {
class MediaFileInfo; class MediaFileInfo;
class Notification; class Diagnostics;
typedef std::list<Notification> NotificationList;
} }
namespace QtGui { namespace QtGui {
@ -17,12 +16,12 @@ class FileInfoModel : public QStandardItemModel
{ {
Q_OBJECT Q_OBJECT
public: 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; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
const Media::MediaFileInfo *fileInfo() 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) #if defined(GUI_QTWIDGETS)
static const QIcon &informationIcon(); static const QIcon &informationIcon();
@ -36,7 +35,8 @@ private:
private: private:
Media::MediaFileInfo *m_file; Media::MediaFileInfo *m_file;
Media::NotificationList *m_originalNotifications; Media::Diagnostics *m_diag;
Media::Diagnostics *m_diagReparsing;
}; };
} }

View File

@ -1,5 +1,7 @@
#include "./notificationmodel.h" #include "./notificationmodel.h"
#include "../misc/utility.h"
#include <c++utilities/chrono/datetime.h> #include <c++utilities/chrono/datetime.h>
#include <QApplication> #include <QApplication>
@ -9,14 +11,15 @@
using namespace std; using namespace std;
using namespace ChronoUtilities; using namespace ChronoUtilities;
using namespace Media; using namespace Media;
using namespace Utility;
namespace QtGui { namespace QtGui {
NotificationModel::NotificationModel(QObject *parent) : DiagModel::DiagModel(QObject *parent) :
QAbstractListModel(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) { switch(orientation) {
case Qt::Horizontal: case Qt::Horizontal:
@ -41,7 +44,7 @@ QVariant NotificationModel::headerData(int section, Qt::Orientation orientation,
return QVariant(); return QVariant();
} }
int NotificationModel::columnCount(const QModelIndex &parent) const int DiagModel::columnCount(const QModelIndex &parent) const
{ {
if(!parent.isValid()) { if(!parent.isValid()) {
return 3; return 3;
@ -49,27 +52,27 @@ int NotificationModel::columnCount(const QModelIndex &parent) const
return 0; return 0;
} }
int NotificationModel::rowCount(const QModelIndex &parent) const int DiagModel::rowCount(const QModelIndex &parent) const
{ {
if(!parent.isValid()) { if(!parent.isValid()) {
return m_notifications.size(); return sizeToInt(m_diag.size());
} }
return 0; return 0;
} }
Qt::ItemFlags NotificationModel::flags(const QModelIndex &index) const Qt::ItemFlags DiagModel::flags(const QModelIndex &index) const
{ {
return QAbstractListModel::flags(index); 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<std::size_t>(index.row()) < m_diag.size()) {
switch(role) { switch(role) {
case Qt::DisplayRole: case Qt::DisplayRole:
switch(index.column()) { switch(index.column()) {
case 0: { case 0: {
const string &context = m_notifications.at(index.row()).context(); const string &context = m_diag[static_cast<std::size_t>(index.row())].context();
if(context.empty()) { if(context.empty()) {
return tr("unspecified"); return tr("unspecified");
} else { } else {
@ -77,9 +80,9 @@ QVariant NotificationModel::data(const QModelIndex &index, int role) const
} }
} }
case 1: case 1:
return QString::fromUtf8(m_notifications.at(index.row()).message().c_str()); return QString::fromUtf8(m_diag[static_cast<std::size_t>(index.row())].message().c_str());
case 2: case 2:
return QString::fromUtf8(m_notifications.at(index.row()).creationTime().toString(DateTimeOutputFormat::DateAndTime, true).c_str()); return QString::fromUtf8(m_diag[static_cast<std::size_t>(index.row())].creationTime().toString(DateTimeOutputFormat::DateAndTime, true).c_str());
default: default:
; ;
} }
@ -87,18 +90,19 @@ QVariant NotificationModel::data(const QModelIndex &index, int role) const
case Qt::DecorationRole: case Qt::DecorationRole:
switch(index.column()) { switch(index.column()) {
case 0: case 0:
switch(m_notifications.at(index.row()).type()) { switch(m_diag[static_cast<std::size_t>(index.row())].level()) {
case NotificationType::Information: case DiagLevel::None:
return informationIcon(); case DiagLevel::Debug:
case NotificationType::Warning:
return warningIcon();
case NotificationType::Critical:
return errorIcon();
case NotificationType::Debug:
return debugIcon(); return debugIcon();
default: case DiagLevel::Information:
; return informationIcon();
case DiagLevel::Warning:
return warningIcon();
case DiagLevel::Critical:
case DiagLevel::Fatal:
return errorIcon();
} }
break;
default: default:
; ;
} }
@ -110,42 +114,37 @@ QVariant NotificationModel::data(const QModelIndex &index, int role) const
return QVariant(); return QVariant();
} }
const QList<Notification> &NotificationModel::notifications() const const Diagnostics &DiagModel::diagnostics() const
{ {
return m_notifications; return m_diag;
} }
void NotificationModel::setNotifications(const QList<Notification> &notifications) void DiagModel::setDiagnostics(const Media::Diagnostics &notifications)
{ {
beginResetModel(); beginResetModel();
m_notifications = notifications; m_diag = notifications;
endResetModel(); endResetModel();
} }
void NotificationModel::setNotifications(const NotificationList &notifications) const QIcon &DiagModel::informationIcon()
{
setNotifications(QList<Notification>::fromStdList(notifications));
}
const QIcon &NotificationModel::informationIcon()
{ {
static const QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); static const QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation);
return icon; return icon;
} }
const QIcon &NotificationModel::warningIcon() const QIcon &DiagModel::warningIcon()
{ {
static const QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning); static const QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning);
return icon; return icon;
} }
const QIcon &NotificationModel::errorIcon() const QIcon &DiagModel::errorIcon()
{ {
static const QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); static const QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical);
return icon; return icon;
} }
const QIcon &NotificationModel::debugIcon() const QIcon &DiagModel::debugIcon()
{ {
static const QIcon icon = QIcon(QStringLiteral("/images/bug")); static const QIcon icon = QIcon(QStringLiteral("/images/bug"));
return icon; return icon;

View File

@ -1,17 +1,17 @@
#ifndef NOTIFICATIONMODEL_H #ifndef NOTIFICATIONMODEL_H
#define NOTIFICATIONMODEL_H #define NOTIFICATIONMODEL_H
#include <tagparser/notification.h> #include <tagparser/diagnostics.h>
#include <QAbstractListModel> #include <QAbstractListModel>
namespace QtGui { namespace QtGui {
class NotificationModel : public QAbstractListModel class DiagModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit NotificationModel(QObject *parent = nullptr); explicit DiagModel(QObject *parent = nullptr);
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
int columnCount(const QModelIndex &parent) const; int columnCount(const QModelIndex &parent) const;
@ -19,9 +19,8 @@ public:
Qt::ItemFlags flags(const QModelIndex &index) const; Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
const QList<Media::Notification> &notifications() const; const Media::Diagnostics &diagnostics() const;
void setNotifications(const QList<Media::Notification> &notifications); void setDiagnostics(const Media::Diagnostics &diagnostics);
void setNotifications(const Media::NotificationList &notifications);
static const QIcon &informationIcon(); static const QIcon &informationIcon();
static const QIcon &warningIcon(); static const QIcon &warningIcon();
@ -33,7 +32,7 @@ signals:
public slots: public slots:
private: private:
QList<Media::Notification> m_notifications; Media::Diagnostics m_diag;
}; };

View File

@ -11,6 +11,7 @@
#include <tagparser/id3/id3v2frame.h> #include <tagparser/id3/id3v2frame.h>
#include <tagparser/vorbis/vorbiscomment.h> #include <tagparser/vorbis/vorbiscomment.h>
#include <tagparser/vorbis/vorbiscommentfield.h> #include <tagparser/vorbis/vorbiscommentfield.h>
#include <tagparser/diagnostics.h>
#include <qtutilities/misc/conversion.h> #include <qtutilities/misc/conversion.h>
@ -318,8 +319,10 @@ void PicturePreviewSelection::addOfSelectedType(const QString &path)
TagValue &selectedCover = m_values[m_currentTypeIndex]; TagValue &selectedCover = m_values[m_currentTypeIndex];
try { try {
MediaFileInfo fileInfo(toNativeFileName(path).constData()); MediaFileInfo fileInfo(toNativeFileName(path).constData());
Diagnostics diag;
fileInfo.open(true); fileInfo.open(true);
fileInfo.parseContainerFormat(); fileInfo.parseContainerFormat(diag);
// TODO: show diagnostic messages
auto mimeType = QString::fromUtf8(fileInfo.mimeType()); auto mimeType = QString::fromUtf8(fileInfo.mimeType());
bool ok; 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); mimeType = QInputDialog::getText(this, tr("Enter/confirm mime type"), tr("Confirm or enter the mime type of the selected file."), QLineEdit::Normal, mimeType, &ok);

View File

@ -164,7 +164,7 @@ TagEditorWidget::~TagEditorWidget()
const QByteArray &TagEditorWidget::generateFileInfoHtml() const QByteArray &TagEditorWidget::generateFileInfoHtml()
{ {
if(m_fileInfoHtml.isEmpty()) { 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; return m_fileInfoHtml;
} }
@ -627,7 +627,8 @@ void TagEditorWidget::initInfoView()
m_ui->tagSplitter->addWidget(m_infoTreeView); m_ui->tagSplitter->addWidget(m_infoTreeView);
} }
if(!m_infoModel) { 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->setModel(m_infoModel);
m_infoTreeView->setHeaderHidden(true); m_infoTreeView->setHeaderHidden(true);
m_infoTreeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); m_infoTreeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
@ -658,7 +659,7 @@ void TagEditorWidget::updateInfoView()
// update info model if present // update info model if present
if(m_infoModel) { 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 // clear previous results and status
m_tags.clear(); m_tags.clear();
m_fileInfo.clearParsingResults(); m_fileInfo.clearParsingResults();
m_fileInfo.invalidateStatus();
m_fileInfo.invalidateNotifications();
if(!sameFile) { if(!sameFile) {
// close last file if possibly open // close last file if possibly open
m_fileInfo.close(); m_fileInfo.close();
@ -768,25 +767,26 @@ bool TagEditorWidget::startParsing(const QString &path, bool forceRefresh)
m_fileInfo.setSaveFilePath(string()); m_fileInfo.setSaveFilePath(string());
m_fileInfo.setPath(toNativeFileName(path).data()); m_fileInfo.setPath(toNativeFileName(path).data());
// update file name and directory // update file name and directory
QFileInfo fileInfo(path); const QFileInfo fileInfo(path);
m_lastDir = m_currentDir; m_lastDir = m_currentDir;
m_currentDir = fileInfo.absolutePath(); m_currentDir = fileInfo.absolutePath();
m_fileName = fileInfo.fileName(); m_fileName = fileInfo.fileName();
} }
// update availability of making results // write diagnostics to m_diagReparsing if making results are avalable
m_makingResultsAvailable &= sameFile; m_makingResultsAvailable &= sameFile;
if(!m_makingResultsAvailable) { Diagnostics &diag = m_makingResultsAvailable ? m_diagReparsing : m_diag;
m_originalNotifications.clear(); // clear diagnostics
} diag.clear();
m_diagReparsing.clear();
// show filename // show filename
m_ui->fileNameLabel->setText(m_fileName); m_ui->fileNameLabel->setText(m_fileName);
// define function to parse the file // define function to parse the file
auto startThread = [this] { const auto startThread = [this, &diag] {
char result; char result;
try { // credits for this nesting go to GCC regression 66145 try { // credits for this nesting go to GCC regression 66145
try { try {
// try to open with write access
try { try {
// try to open with write access
m_fileInfo.reopen(false); m_fileInfo.reopen(false);
} catch(...) { } catch(...) {
::IoUtilities::catchIoFailure(); ::IoUtilities::catchIoFailure();
@ -794,7 +794,7 @@ bool TagEditorWidget::startParsing(const QString &path, bool forceRefresh)
m_fileInfo.reopen(true); m_fileInfo.reopen(true);
} }
m_fileInfo.setForceFullParse(Settings::values().editor.forceFullParse); m_fileInfo.setForceFullParse(Settings::values().editor.forceFullParse);
m_fileInfo.parseEverything(); m_fileInfo.parseEverything(diag);
result = ParsingSuccessful; result = ParsingSuccessful;
} catch(const Failure &) { } catch(const Failure &) {
// the file has been opened; parsing notifications will be shown in the info box // 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; result = IoError;
} }
} catch(const exception &e) { } 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; result = FatalParsingError;
} catch(...) { } 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; result = FatalParsingError;
} }
m_fileInfo.unregisterAllCallbacks();
QMetaObject::invokeMethod(this, "showFile", Qt::QueuedConnection, Q_ARG(char, result)); QMetaObject::invokeMethod(this, "showFile", Qt::QueuedConnection, Q_ARG(char, result));
}; };
m_fileInfo.unregisterAllCallbacks();
m_fileOperationOngoing = true; m_fileOperationOngoing = true;
// perform the operation concurrently // perform the operation concurrently
QtConcurrent::run(startThread); QtConcurrent::run(startThread);
// inform user // 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->setNotificationType(NotificationType::Progress);
m_ui->parsingNotificationWidget->setText(statusMsg); m_ui->parsingNotificationWidget->setText(statusMsg);
m_ui->parsingNotificationWidget->setVisible(true); // ensure widget is visible! 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 // show parsing status/result using parsing notification widget
auto worstNotificationType = m_fileInfo.worstNotificationTypeIncludingRelatedObjects(); auto diagLevel = m_diag.level();
if(worstNotificationType >= Media::NotificationType::Critical) { if (diagLevel < worstDiagLevel) {
// we catched no exception, but there are critical notifications diagLevel |= m_diagReparsing.level();
// -> treat critical notifications as fatal parsing errors }
if(diagLevel >= DiagLevel::Critical) {
// we catched no exception, but there are critical diag messages
// -> treat those as fatal parsing errors
result = LoadingResult::FatalParsingError; result = LoadingResult::FatalParsingError;
} }
switch(result) { switch(result) {
@ -915,12 +916,12 @@ void TagEditorWidget::showFile(char result)
m_ui->parsingNotificationWidget->setText(tr("File couldn't be parsed correctly.")); m_ui->parsingNotificationWidget->setText(tr("File couldn't be parsed correctly."));
} }
bool multipleSegmentsNotTested = m_fileInfo.containerFormat() == ContainerFormat::Matroska && m_fileInfo.container()->segmentCount() > 1; 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->setNotificationType(NotificationType::Critical);
m_ui->parsingNotificationWidget->appendLine(tr("There are critical parsing notifications.")); m_ui->parsingNotificationWidget->appendLine(tr("Errors occured."));
} else if(worstNotificationType == Media::NotificationType::Warning || m_fileInfo.isReadOnly() || !m_fileInfo.areTagsSupported() || multipleSegmentsNotTested) { } else if(diagLevel == Media::DiagLevel::Warning || m_fileInfo.isReadOnly() || !m_fileInfo.areTagsSupported() || multipleSegmentsNotTested) {
m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Warning); m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Warning);
if(worstNotificationType == Media::NotificationType::Warning) { if(diagLevel == Media::DiagLevel::Warning) {
m_ui->parsingNotificationWidget->appendLine(tr("There are warnings.")); m_ui->parsingNotificationWidget->appendLine(tr("There are warnings."));
} }
} }
@ -1099,21 +1100,26 @@ bool TagEditorWidget::startSaving()
m_fileInfo.setMinPadding(settings.minPadding); m_fileInfo.setMinPadding(settings.minPadding);
m_fileInfo.setMaxPadding(settings.maxPadding); m_fileInfo.setMaxPadding(settings.maxPadding);
m_fileInfo.setPreferredPadding(settings.preferredPadding); m_fileInfo.setPreferredPadding(settings.preferredPadding);
// define functions to show the saving progress and to actually applying the changes const auto startThread = [this] {
auto showProgress = [this] (StatusProvider &sender) -> void { // define functions to show the saving progress and to actually applying the changes
QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setPercentage", Qt::QueuedConnection, Q_ARG(int, static_cast<int>(sender.currentPercentage() * 100.0))); const auto showPercentage([this] (const AbortableProgressFeedback &progress) {
if(m_abortClicked) { QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setPercentage", Qt::QueuedConnection, Q_ARG(int, progress.stepPercentage()));
QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setText", Qt::QueuedConnection, Q_ARG(QString, tr("Cancelling ..."))); });
m_fileInfo.tryToAbort(); const auto showStep([this] (AbortableProgressFeedback &progress) {
} else { QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setPercentage", Qt::QueuedConnection, Q_ARG(int, progress.stepPercentage()));
QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(sender.currentStatus()))); if(m_abortClicked) {
} progress.tryToAbort();
}; QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setText", Qt::QueuedConnection, Q_ARG(QString, tr("Cancelling ...")));
auto startThread = [this] { } 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; bool processingError = false, ioError = false;
try { try {
try { try {
m_fileInfo.applyChanges(); m_fileInfo.applyChanges(m_diag, progress);
} catch(const Failure &) { } catch(const Failure &) {
processingError = true; processingError = true;
} catch(...) { } catch(...) {
@ -1121,17 +1127,14 @@ bool TagEditorWidget::startSaving()
ioError = true; ioError = true;
} }
} catch(const exception &e) { } 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; processingError = true;
} catch(...) { } 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; processingError = true;
} }
m_fileInfo.unregisterAllCallbacks();
QMetaObject::invokeMethod(this, "showSavingResult", Qt::QueuedConnection, Q_ARG(bool, processingError), Q_ARG(bool, ioError)); QMetaObject::invokeMethod(this, "showSavingResult", Qt::QueuedConnection, Q_ARG(bool, processingError), Q_ARG(bool, ioError));
}; };
m_fileInfo.unregisterAllCallbacks();
m_fileInfo.registerCallback(showProgress);
m_fileOperationOngoing = true; m_fileOperationOngoing = true;
// use another thread to perform the operation // use another thread to perform the operation
QtConcurrent::run(startThread); QtConcurrent::run(startThread);
@ -1155,17 +1158,17 @@ void TagEditorWidget::showSavingResult(bool processingError, bool ioError)
m_ui->makingNotificationWidget->setPercentage(-1); m_ui->makingNotificationWidget->setPercentage(-1);
m_ui->makingNotificationWidget->setHidden(false); m_ui->makingNotificationWidget->setHidden(false);
m_makingResultsAvailable = true; m_makingResultsAvailable = true;
m_originalNotifications = m_fileInfo.gatherRelatedNotifications();
if(!processingError && !ioError) { if(!processingError && !ioError) {
// display status messages // display status messages
QString statusMsg; QString statusMsg;
size_t critical = 0, warnings = 0; size_t critical = 0, warnings = 0;
for(const Notification &notification : m_originalNotifications) { for(const auto &msg : m_diag) {
switch(notification.type()) { switch(msg.level()) {
case Media::NotificationType::Critical: case Media::DiagLevel::Fatal:
case Media::DiagLevel::Critical:
++critical; ++critical;
break; break;
case Media::NotificationType::Warning: case Media::DiagLevel::Warning:
++warnings; ++warnings;
break; break;
default: default:
@ -1174,12 +1177,12 @@ void TagEditorWidget::showSavingResult(bool processingError, bool ioError)
} }
if(warnings || critical) { if(warnings || critical) {
if(critical) { if(critical) {
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);
statusMsg.append(tr("and %1 error(s).", 0, critical).arg(critical)); statusMsg.append(tr("and %1 error(s).", nullptr, trQuandity(critical)).arg(critical));
} else { } 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 { } else {
statusMsg = tr("The tags have been saved."); statusMsg = tr("The tags have been saved.");

View File

@ -5,6 +5,7 @@
#include "./webviewdefs.h" #include "./webviewdefs.h"
#include <tagparser/mediafileinfo.h> #include <tagparser/mediafileinfo.h>
#include <tagparser/diagnostics.h>
#include <QWidget> #include <QWidget>
#include <QByteArray> #include <QByteArray>
@ -49,7 +50,7 @@ public:
const QString &currentPath() const; const QString &currentPath() const;
const QString &currentDir() const; const QString &currentDir() const;
Media::MediaFileInfo &fileInfo(); Media::MediaFileInfo &fileInfo();
Media::NotificationList &originalNotifications(); const Media::Diagnostics &diagnostics() const;
bool isTagEditShown() const; bool isTagEditShown() const;
const QByteArray &fileInfoHtml() const; const QByteArray &fileInfoHtml() const;
const QByteArray &generateFileInfoHtml(); const QByteArray &generateFileInfoHtml();
@ -150,9 +151,10 @@ private:
QString m_lastDir; QString m_lastDir;
QString m_saveFilePath; QString m_saveFilePath;
// status // status
Media::Diagnostics m_diag;
Media::Diagnostics m_diagReparsing;
bool m_nextFileAfterSaving; bool m_nextFileAfterSaving;
bool m_makingResultsAvailable; bool m_makingResultsAvailable;
Media::NotificationList m_originalNotifications;
bool m_abortClicked; bool m_abortClicked;
bool m_fileOperationOngoing; 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;
} }
/*! /*!

View File

@ -9,7 +9,6 @@
#include <tagparser/mp4/mp4container.h> #include <tagparser/mp4/mp4container.h>
#include <tagparser/abstracttrack.h> #include <tagparser/abstracttrack.h>
#include <tagparser/abstractattachment.h> #include <tagparser/abstractattachment.h>
#include <tagparser/notification.h>
#include <qtutilities/resources/resources.h> #include <qtutilities/resources/resources.h>
@ -56,6 +55,7 @@ using namespace std;
using namespace ConversionUtilities; using namespace ConversionUtilities;
using namespace ChronoUtilities; using namespace ChronoUtilities;
using namespace Media; using namespace Media;
using namespace Utility;
namespace HtmlInfo { namespace HtmlInfo {
@ -308,11 +308,12 @@ class Generator
{ {
public: public:
Generator(const MediaFileInfo &file, NotificationList &originalNotifications) : Generator(const MediaFileInfo &file, Diagnostics &diag, Diagnostics &diagReparsing) :
m_writer(&m_res), m_writer(&m_res),
m_rowMaker(m_writer), m_rowMaker(m_writer),
m_file(file), m_file(file),
originalNotifications(originalNotifications) m_diag(diag),
m_diagReparsing(diagReparsing)
{} {}
QString mkStyle() QString mkStyle()
@ -808,33 +809,33 @@ public:
} }
} }
void mkNotifications(NotificationList &notifications, bool reparsing = false) void mkNotifications(Diagnostics &diag, bool reparsing = false)
{ {
if(notifications.size()) { if (diag.empty()) {
startTableSection(); return;
const QString moreId(reparsing ? QStringLiteral("notificationsReparsingMore") : QStringLiteral("notificationsMore")); }
m_rowMaker.startRow(reparsing ? QCoreApplication::translate("HtmlInfo", "Notifications (reparsing after saving)") : QCoreApplication::translate("HtmlInfo", "Notifications")); startTableSection();
m_writer.writeCharacters(QCoreApplication::translate("HtmlInfo", "%1 notification(s) available", 0, static_cast<int>(notifications.size())).arg(notifications.size())); const QString moreId(reparsing ? QStringLiteral("notificationsReparsingMore") : QStringLiteral("notificationsMore"));
mkSpace(); m_rowMaker.startRow(reparsing ? QCoreApplication::translate("HtmlInfo", "Notifications (reparsing after saving)") : QCoreApplication::translate("HtmlInfo", "Notifications"));
mkDetailsLink(moreId, QCoreApplication::translate("HtmlInfo", "show notifications")); m_writer.writeCharacters(QCoreApplication::translate("HtmlInfo", "%1 notification(s) available", nullptr, trQuandity(diag.size())).arg(diag.size()));
m_rowMaker.endRow(); mkSpace();
m_writer.writeEndElement(); mkDetailsLink(moreId, QCoreApplication::translate("HtmlInfo", "show notifications"));
m_rowMaker.endRow();
m_writer.writeEndElement();
startExtendedTableSection(moreId); startExtendedTableSection(moreId);
m_rowMaker.startHorizontalSubTab(QString(), QStringList() << QString() << QCoreApplication::translate("HtmlInfo", "Context") << QCoreApplication::translate("HtmlInfo", "Message") << QCoreApplication::translate("HtmlInfo", "Time")); m_rowMaker.startHorizontalSubTab(QString(), QStringList({QString(), QCoreApplication::translate("HtmlInfo", "Context"), QCoreApplication::translate("HtmlInfo", "Message"), QCoreApplication::translate("HtmlInfo", "Time")}));
Notification::sortByTime(notifications); for(const auto &msg : diag) {
for(const Notification &notification : notifications) { m_writer.writeStartElement(QStringLiteral("tr"));
m_writer.writeStartElement(QStringLiteral("tr")); m_writer.writeEmptyElement(QStringLiteral("td"));
m_writer.writeEmptyElement(QStringLiteral("td")); m_writer.writeAttribute(QStringLiteral("class"), qstr(msg.levelName()));
m_writer.writeAttribute(QStringLiteral("class"), qstr(notification.typeName())); m_writer.writeTextElement(QStringLiteral("td"), qstr(msg.context()));
m_writer.writeTextElement(QStringLiteral("td"), qstr(notification.context())); m_writer.writeTextElement(QStringLiteral("td"), qstr(msg.message()));
m_writer.writeTextElement(QStringLiteral("td"), qstr(notification.message())); m_writer.writeTextElement(QStringLiteral("td"), qstr(msg.creationTime().toString(DateTimeOutputFormat::DateAndTime, false)));
m_writer.writeTextElement(QStringLiteral("td"), qstr(notification.creationTime().toString(DateTimeOutputFormat::DateAndTime, false)));
m_writer.writeEndElement();
}
m_rowMaker.endSubTab();
m_writer.writeEndElement(); m_writer.writeEndElement();
} }
m_rowMaker.endSubTab();
m_writer.writeEndElement();
} }
void mkDoc() void mkDoc()
@ -932,8 +933,8 @@ public:
if(m_file.paddingSize()) { if(m_file.paddingSize()) {
rowMaker.mkRow(QCoreApplication::translate("HtmlInfo", "Padding size"), QStringLiteral("%1 (%2 %)").arg(qstr(dataSizeToString(m_file.paddingSize(), true))).arg(static_cast<double>(m_file.paddingSize()) / m_file.size() * 100.0, 0, 'g', 2)); rowMaker.mkRow(QCoreApplication::translate("HtmlInfo", "Padding size"), QStringLiteral("%1 (%2 %)").arg(qstr(dataSizeToString(m_file.paddingSize(), true))).arg(static_cast<double>(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", "Tag position"), container->determineTagPosition(m_diagReparsing));
rowMaker.mkRow(QCoreApplication::translate("HtmlInfo", "Index position"), container->determineIndexPosition()); rowMaker.mkRow(QCoreApplication::translate("HtmlInfo", "Index position"), container->determineIndexPosition(m_diagReparsing));
m_writer.writeEndElement(); m_writer.writeEndElement();
} }
@ -1110,11 +1111,8 @@ public:
} }
// notifications // notifications
auto currentNotifications = m_file.gatherRelatedNotifications(); mkNotifications(m_diag);
mkNotifications(currentNotifications, !originalNotifications.empty()); mkNotifications(m_diagReparsing, true);
if(!originalNotifications.empty()) {
mkNotifications(originalNotifications);
}
// </table> </body> </html> // </table> </body> </html>
@ -1132,7 +1130,8 @@ private:
QByteArray m_res; QByteArray m_res;
RowMaker m_rowMaker; RowMaker m_rowMaker;
const MediaFileInfo &m_file; 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 QGuiApplication instance should be available for setting fonts.
* A QApplication instance should be available for standard icons. * 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(); gen.mkDoc();
#ifdef QT_DEBUG #ifdef QT_DEBUG
QFile test(QStringLiteral("/tmp/test.xhtml")); QFile test(QStringLiteral("/tmp/test.xhtml"));

View File

@ -7,13 +7,12 @@
namespace Media { namespace Media {
class MediaFileInfo; class MediaFileInfo;
class Notification; class Diagnostics;
typedef std::list<Notification> NotificationList;
} }
namespace HtmlInfo { namespace HtmlInfo {
QByteArray generateInfo(const Media::MediaFileInfo &file, Media::NotificationList &originalNotifications); QByteArray generateInfo(const Media::MediaFileInfo &file, Media::Diagnostics &diag, Media::Diagnostics &diagReparsing);
} }

View File

@ -30,6 +30,16 @@ void parseFileName(const QString &fileName, QString &title, int &trackNumber);
QString printModel(QAbstractItemModel *model); QString printModel(QAbstractItemModel *model);
void printModelIndex(const QModelIndex &index, QString &res, int level); void printModelIndex(const QModelIndex &index, QString &res, int level);
constexpr int sizeToInt(std::size_t size)
{
return size > std::numeric_limits<int>::max() ? std::numeric_limits<int>::max() : static_cast<int>(size);
}
constexpr int trQuandity(quint64 quandity)
{
return quandity > std::numeric_limits<int>::max() ? std::numeric_limits<int>::max() : static_cast<int>(quandity);
}
} }
#endif // UTILITYFEATURES_H #endif // UTILITYFEATURES_H

View File

@ -29,17 +29,17 @@ using namespace std;
namespace RenamingUtility { namespace RenamingUtility {
/// \brief Adds notifications from \a statusProvider to \a notificationsObject. /// \brief Adds notifications from \a statusProvider to \a notificationsObject.
TAGEDITOR_JS_VALUE &operator <<(TAGEDITOR_JS_VALUE &notificationsObject, const StatusProvider &statusProvider) TAGEDITOR_JS_VALUE &operator <<(TAGEDITOR_JS_VALUE &diagObject, const Diagnostics &diag)
{ {
quint32 counter = 0; quint32 counter = 0;
for(const auto &notification : statusProvider.notifications()) { for(const auto &msg : diag) {
TAGEDITOR_JS_VALUE val; TAGEDITOR_JS_VALUE val;
val.setProperty("msg", QString::fromUtf8(notification.message().data()) TAGEDITOR_JS_READONLY); val.setProperty("msg", QString::fromUtf8(msg.message().data()) TAGEDITOR_JS_READONLY);
val.setProperty("critical", notification.type() == NotificationType::Critical TAGEDITOR_JS_READONLY); val.setProperty("critical", msg.level() >= DiagLevel::Critical TAGEDITOR_JS_READONLY);
notificationsObject.setProperty(counter, val); diagObject.setProperty(counter, val);
++counter; ++counter;
} }
return notificationsObject; return diagObject;
} }
/// \brief Add fields and notifications from \a tag to \a tagObject. /// \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("diskPos", pos.position() TAGEDITOR_JS_READONLY);
tagObject.setProperty("diskTotal", pos.total() TAGEDITOR_JS_READONLY); tagObject.setProperty("diskTotal", pos.total() TAGEDITOR_JS_READONLY);
// add notifications
tagObject.setProperty("hasCriticalNotifications", tag.hasCriticalNotifications() TAGEDITOR_JS_READONLY);
return tagObject; return tagObject;
} }
@ -138,6 +136,7 @@ const QString &TagEditorObject::newRelativeDirectory() const
TAGEDITOR_JS_VALUE TagEditorObject::parseFileInfo(const QString &fileName) TAGEDITOR_JS_VALUE TagEditorObject::parseFileInfo(const QString &fileName)
{ {
Diagnostics diag;
MediaFileInfo fileInfo(toNativeFileName(fileName).data()); MediaFileInfo fileInfo(toNativeFileName(fileName).data());
// add basic file information // add basic file information
@ -153,7 +152,7 @@ TAGEDITOR_JS_VALUE TagEditorObject::parseFileInfo(const QString &fileName)
// parse further file information // parse further file information
bool criticalParseingErrorOccured = false; bool criticalParseingErrorOccured = false;
try { try {
fileInfo.parseEverything(); fileInfo.parseEverything(diag);
} catch(const Failure &) { } catch(const Failure &) {
// parsing notifications will be addded anyways // parsing notifications will be addded anyways
criticalParseingErrorOccured = true; criticalParseingErrorOccured = true;
@ -163,11 +162,11 @@ TAGEDITOR_JS_VALUE TagEditorObject::parseFileInfo(const QString &fileName)
} }
// gather notifications // gather notifications
auto mainNotificationObject = m_engine->newArray(static_cast<uint>(fileInfo.notifications().size())); auto diagObj = m_engine->newArray(static_cast<uint>(diag.size()));
mainNotificationObject << fileInfo; diagObj << diag;
criticalParseingErrorOccured |= fileInfo.hasCriticalNotifications(); criticalParseingErrorOccured |= diag.level() >= DiagLevel::Critical;
fileInfoObject.setProperty(QStringLiteral("hasCriticalNotifications"), criticalParseingErrorOccured); fileInfoObject.setProperty(QStringLiteral("hasCriticalMessages"), criticalParseingErrorOccured);
fileInfoObject.setProperty(QStringLiteral("notifications"), mainNotificationObject); fileInfoObject.setProperty(QStringLiteral("diagMessages"), diagObj);
// add MIME-type, suitable suffix and technical summary // add MIME-type, suitable suffix and technical summary
fileInfoObject.setProperty(QStringLiteral("mimeType"), QString::fromUtf8(fileInfo.mimeType()) TAGEDITOR_JS_READONLY); 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; combinedTagObject << tag;
combinedTagNotifications << tag; combinedTagNotifications << tag;
tagObject << tag; tagObject << tag;
auto tagNotificationsObject = m_engine->newArray(static_cast<uint>(tag.notifications().size()));
tagNotificationsObject << tag;
tagObject.setProperty(QStringLiteral("notifications"), tagNotificationsObject TAGEDITOR_JS_READONLY);
tagsObject.setProperty(tagIndex, tagObject TAGEDITOR_JS_READONLY); tagsObject.setProperty(tagIndex, tagObject TAGEDITOR_JS_READONLY);
} }
combinedTagObject.setProperty(QStringLiteral("notifications"), combinedTagNotifications 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("format"), QString::fromUtf8(track.formatName()));
trackObject.setProperty(QStringLiteral("formatAbbreviation"), QString::fromUtf8(track.formatAbbreviation())); trackObject.setProperty(QStringLiteral("formatAbbreviation"), QString::fromUtf8(track.formatAbbreviation()));
trackObject.setProperty(QStringLiteral("description"), QString::fromUtf8(track.description().data())); trackObject.setProperty(QStringLiteral("description"), QString::fromUtf8(track.description().data()));
auto trackNotificationsObject = m_engine->newArray(static_cast<uint>(track.notifications().size()));
trackNotificationsObject << track;
trackObject.setProperty(QStringLiteral("notifications"), trackNotificationsObject TAGEDITOR_JS_READONLY);
tracksObject.setProperty(trackIndex, trackObject 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) TAGEDITOR_JS_VALUE TagEditorObject::allFiles(const QString &dirName)
{ {
QDir dir(dirName); const QDir dir(dirName);
if(dir.exists()) { if(dir.exists()) {
QStringList files = dir.entryList(QDir::Files); const auto files(dir.entryList(QDir::Files));
auto entriesObj = m_engine->newArray(files.length()); auto entriesObj = m_engine->newArray(static_cast<uint>(files.size()));
quint32 counter = 0; quint32 counter = 0;
for(const QString &file : files) { for(const auto &file : files) {
entriesObj.setProperty(counter, file TAGEDITOR_JS_READONLY); entriesObj.setProperty(counter, file TAGEDITOR_JS_READONLY);
++counter; ++counter;
} }
@ -245,9 +238,9 @@ TAGEDITOR_JS_VALUE TagEditorObject::allFiles(const QString &dirName)
TAGEDITOR_JS_VALUE TagEditorObject::firstFile(const QString &dirName) TAGEDITOR_JS_VALUE TagEditorObject::firstFile(const QString &dirName)
{ {
QDir dir(dirName); const QDir dir(dirName);
if(dir.exists()) { if(dir.exists()) {
QStringList files = dir.entryList(QDir::Files); const auto files(dir.entryList(QDir::Files));
if(!files.empty()) { if(!files.empty()) {
return TAGEDITOR_JS_VALUE(files.first()); return TAGEDITOR_JS_VALUE(files.first());
} }

View File

@ -2,9 +2,27 @@
#include <c++utilities/io/catchiofailure.h> #include <c++utilities/io/catchiofailure.h>
#include <c++utilities/io/misc.h> #include <c++utilities/io/misc.h>
#include <c++utilities/io/path.h> #include <c++utilities/io/path.h>
#include <c++utilities/tests/testutils.h>
// order of includes and definition of operator << matters for C++ to resolve the correct overload
#include <tagparser/mediafileinfo.h> #include <tagparser/mediafileinfo.h>
#include <tagparser/diagnostics.h>
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 <c++utilities/tests/testutils.h>
#include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h> #include <cppunit/TestFixture.h>
@ -804,9 +822,10 @@ void CliTests::testExtraction()
// test extraction of cover // test extraction of cover
const char *const args1[] = {"tageditor", "extract", "cover", "-f", mp4File1.data(), "-o", "/tmp/extracted.jpeg", nullptr}; const char *const args1[] = {"tageditor", "extract", "cover", "-f", mp4File1.data(), "-o", "/tmp/extracted.jpeg", nullptr};
TESTUTILS_ASSERT_EXEC(args1); TESTUTILS_ASSERT_EXEC(args1);
Diagnostics diag;
MediaFileInfo extractedInfo("/tmp/extracted.jpeg"); MediaFileInfo extractedInfo("/tmp/extracted.jpeg");
extractedInfo.open(true); extractedInfo.open(true);
extractedInfo.parseContainerFormat(); extractedInfo.parseContainerFormat(diag);
CPPUNIT_ASSERT_EQUAL(22771_st, extractedInfo.size()); CPPUNIT_ASSERT_EQUAL(22771_st, extractedInfo.size());
CPPUNIT_ASSERT(ContainerFormat::Jpeg == extractedInfo.containerFormat()); CPPUNIT_ASSERT(ContainerFormat::Jpeg == extractedInfo.containerFormat());
extractedInfo.invalidate(); extractedInfo.invalidate();
@ -819,12 +838,13 @@ void CliTests::testExtraction()
CPPUNIT_ASSERT_EQUAL(0, remove("/tmp/extracted.jpeg")); CPPUNIT_ASSERT_EQUAL(0, remove("/tmp/extracted.jpeg"));
TESTUTILS_ASSERT_EXEC(args3); TESTUTILS_ASSERT_EXEC(args3);
extractedInfo.open(true); extractedInfo.open(true);
extractedInfo.parseContainerFormat(); extractedInfo.parseContainerFormat(diag);
CPPUNIT_ASSERT_EQUAL(22771_st, extractedInfo.size()); CPPUNIT_ASSERT_EQUAL(22771_st, extractedInfo.size());
CPPUNIT_ASSERT(ContainerFormat::Jpeg == extractedInfo.containerFormat()); CPPUNIT_ASSERT(ContainerFormat::Jpeg == extractedInfo.containerFormat());
CPPUNIT_ASSERT_EQUAL(0, remove("/tmp/extracted.jpeg")); CPPUNIT_ASSERT_EQUAL(0, remove("/tmp/extracted.jpeg"));
CPPUNIT_ASSERT_EQUAL(0, remove(mp4File2.data())); CPPUNIT_ASSERT_EQUAL(0, remove(mp4File2.data()));
CPPUNIT_ASSERT_EQUAL(0, remove((mp4File2 + ".bak").data())); CPPUNIT_ASSERT_EQUAL(0, remove((mp4File2 + ".bak").data()));
CPPUNIT_ASSERT_EQUAL(Diagnostics(), diag);
} }
/*! /*!