diff --git a/ImportExport/ImportDialog.cpp b/ImportExport/ImportDialog.cpp index a68e9b6f..498b53db 100644 --- a/ImportExport/ImportDialog.cpp +++ b/ImportExport/ImportDialog.cpp @@ -1,404 +1,404 @@ -/* Copyright (C) 2003-2018 Jesper K. Pedersen +/* Copyright (C) 2003-2019 The KPhotoAlbum Development Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ImportDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ImageRow.h" #include "ImportMatcher.h" #include "KimFileReader.h" #include "MD5CheckPage.h" using Utilities::StringSet; class QPushButton; using namespace ImportExport; ImportDialog::ImportDialog( QWidget* parent ) :KAssistantDialog( parent ), m_hasFilled( false ), m_md5CheckPage(nullptr) { } bool ImportDialog::exec( KimFileReader* kimFileReader, const QUrl &kimFileURL ) { m_kimFileReader = kimFileReader; if (kimFileURL.isLocalFile()) { QDir cwd; // convert relative local path to absolute m_kimFile = QUrl::fromLocalFile(cwd.absoluteFilePath( kimFileURL.toLocalFile() ) ) .adjusted(QUrl::NormalizePathSegments); } else { m_kimFile = kimFileURL; } QByteArray indexXML = m_kimFileReader->indexXML(); if ( indexXML.isNull() ) return false; bool ok = readFile(indexXML); if ( !ok ) return false; setupPages(); return KAssistantDialog::exec() ; } bool ImportDialog::readFile(const QByteArray& data) { - XMLDB::ReaderPtr reader = XMLDB::ReaderPtr(new XMLDB::XmlReader); + XMLDB::ReaderPtr reader = XMLDB::ReaderPtr(new XMLDB::XmlReader(DB::ImageDB::instance()->uiDelegate())); reader->addData(data); XMLDB::ElementInfo info = reader->readNextStartOrStopElement(QString::fromUtf8("KimDaBa-export")); if ( !info.isStartToken ) reader->complainStartElementExpected(QString::fromUtf8("KimDaBa-export")); // Read source QString source = reader->attribute( QString::fromUtf8( "location" ) ).toLower(); if ( source != QString::fromLatin1( "inline" ) && source != QString::fromLatin1( "external" ) ) { KMessageBox::error( this, i18n("

XML file did not specify the source of the images, " "this is a strong indication that the file is corrupted

" ) ); return false; } m_externalSource = ( source == QString::fromLatin1( "external" ) ); // Read base url m_baseUrl = QUrl::fromUserInput( reader->attribute( QString::fromLatin1( "baseurl" ) )); while ( reader->readNextStartOrStopElement(QString::fromUtf8("image")).isStartToken) { const DB::FileName fileName = DB::FileName::fromRelativePath(reader->attribute(QString::fromUtf8( "file" ))); DB::ImageInfoPtr info = XMLDB::Database::createImageInfo( fileName, reader ); m_images.append( info ); } // the while loop already read the end element, so we tell readEndElement to not read the next token: reader->readEndElement(false); return true; } void ImportDialog::setupPages() { createIntroduction(); createImagesPage(); createDestination(); createCategoryPages(); connect(this, &ImportDialog::currentPageChanged, this, &ImportDialog::updateNextButtonState); QPushButton *helpButton = buttonBox()->button(QDialogButtonBox::Help); connect(helpButton, &QPushButton::clicked, this, &ImportDialog::slotHelp); } void ImportDialog::createIntroduction() { QString txt = i18n( "

Welcome to KPhotoAlbum Import

" "This wizard will take you through the steps of an import operation. The steps are: " "
  1. First you must select which images you want to import from the export file. " "You do so by selecting the checkbox next to the image.
  2. " "
  3. Next you must tell KPhotoAlbum in which directory to put the images. This directory must " "of course be below the directory root KPhotoAlbum uses for images. " "KPhotoAlbum will take care to avoid name clashes
  4. " "
  5. The next step is to specify which categories you want to import (People, Places, ... ) " "and also tell KPhotoAlbum how to match the categories from the file to your categories. " "Imagine you load from a file, where a category is called Blomst (which is the " "Danish word for flower), then you would likely want to match this with your category, which might be " "called Blume (which is the German word for flower) - of course given you are German.
  6. " "
  7. The final steps, is matching the individual tokens from the categories. I may call myself Jesper " "in my image database, while you want to call me by my full name, namely Jesper K. Pedersen. " "In this step non matches will be highlighted in red, so you can see which tokens was not found in your " "database, or which tokens was only a partial match.
"); QLabel* intro = new QLabel( txt, this ); intro->setWordWrap(true); addPage( intro, i18nc("@title:tab introduction page","Introduction") ); } void ImportDialog::createImagesPage() { QScrollArea* top = new QScrollArea; top->setWidgetResizable(true); QWidget* container = new QWidget; QVBoxLayout* lay1 = new QVBoxLayout( container ); top->setWidget( container ); // Select all and Deselect All buttons QHBoxLayout* lay2 = new QHBoxLayout; lay1->addLayout(lay2); QPushButton* selectAll = new QPushButton( i18n("Select All"), container ); lay2->addWidget( selectAll ); QPushButton* selectNone = new QPushButton( i18n("Deselect All"), container ); lay2->addWidget( selectNone ); lay2->addStretch( 1 ); connect(selectAll, &QPushButton::clicked, this, &ImportDialog::slotSelectAll); connect(selectNone, &QPushButton::clicked, this, &ImportDialog::slotSelectNone); QGridLayout* lay3 = new QGridLayout; lay1->addLayout( lay3 ); lay3->setColumnStretch( 2, 1 ); int row = 0; for( DB::ImageInfoListConstIterator it = m_images.constBegin(); it != m_images.constEnd(); ++it, ++row ) { DB::ImageInfoPtr info = *it; ImageRow* ir = new ImageRow( info, this, m_kimFileReader, container ); lay3->addWidget( ir->m_checkbox, row, 0 ); QPixmap pixmap = m_kimFileReader->loadThumbnail( info->fileName().relative() ); if ( !pixmap.isNull() ) { QPushButton* but = new QPushButton( container ); but->setIcon( pixmap ); but->setIconSize( pixmap.size() ); lay3->addWidget( but, row, 1 ); connect(but, &QPushButton::clicked, ir, &ImageRow::showImage); } else { QLabel* label = new QLabel( info->label() ); lay3->addWidget( label, row, 1 ); } QLabel* label = new QLabel( QString::fromLatin1("

%1

").arg(info->description()) ); lay3->addWidget( label, row, 2 ); m_imagesSelect.append( ir ); } addPage( top, i18n("Select Which Images to Import") ); } void ImportDialog::createDestination() { QWidget* top = new QWidget( this ); QVBoxLayout* topLay = new QVBoxLayout( top ); QHBoxLayout* lay = new QHBoxLayout; topLay->addLayout(lay); topLay->addStretch( 1 ); QLabel* label = new QLabel( i18n( "Destination of images: " ), top ); lay->addWidget( label ); m_destinationEdit = new QLineEdit( top ); lay->addWidget( m_destinationEdit, 1 ); QPushButton* but = new QPushButton( QString::fromLatin1("..." ), top ); but->setFixedWidth( 30 ); lay->addWidget( but ); m_destinationEdit->setText( Settings::SettingsData::instance()->imageDirectory()); connect(but, &QPushButton::clicked, this, &ImportDialog::slotEditDestination); connect(m_destinationEdit, &QLineEdit::textChanged, this, &ImportDialog::updateNextButtonState); m_destinationPage = addPage( top, i18n("Destination of Images" ) ); } void ImportDialog::slotEditDestination() { QString file = QFileDialog::getExistingDirectory(this , QString(), m_destinationEdit->text()); if ( !file.isNull() ) { if ( ! QFileInfo(file).absoluteFilePath().startsWith( QFileInfo(Settings::SettingsData::instance()->imageDirectory()).absoluteFilePath()) ) { KMessageBox::error( this, i18n("The directory must be a subdirectory of %1", Settings::SettingsData::instance()->imageDirectory() ) ); } else if ( QFileInfo(file).absoluteFilePath().startsWith( QFileInfo(Settings::SettingsData::instance()->imageDirectory()).absoluteFilePath() + QString::fromLatin1("CategoryImages")) ) { KMessageBox::error( this, i18n("This directory is reserved for category images." ) ); } else { m_destinationEdit->setText( file ); updateNextButtonState(); } } } void ImportDialog::updateNextButtonState() { bool enabled = true; if ( currentPage() == m_destinationPage ) { QString dest = m_destinationEdit->text(); if ( QFileInfo( dest ).isFile() ) enabled = false; else if ( ! QFileInfo(dest).absoluteFilePath().startsWith( QFileInfo(Settings::SettingsData::instance()->imageDirectory()).absoluteFilePath()) ) enabled = false; } setValid( currentPage(), enabled ); } void ImportDialog::createCategoryPages() { QStringList categories; DB::ImageInfoList images = selectedImages(); for( DB::ImageInfoListConstIterator it = images.constBegin(); it != images.constEnd(); ++it ) { DB::ImageInfoPtr info = *it; QStringList categoriesForImage = info->availableCategories(); Q_FOREACH( const QString &category, categoriesForImage ) { if ( !categories.contains( category ) && category != i18n( "Folder" ) && category != i18n( "Tokens" ) && category != i18n( "Media Type" )) categories.append( category ); } } if ( !categories.isEmpty() ) { m_categoryMatcher = new ImportMatcher( QString(), QString(), categories, DB::ImageDB::instance()->categoryCollection()->categoryNames(), false, this ); m_categoryMatcherPage = addPage( m_categoryMatcher, i18n("Match Categories") ); QWidget* dummy = new QWidget; m_dummy = addPage( dummy, QString() ); } else { m_categoryMatcherPage = nullptr; possiblyAddMD5CheckPage(); } } ImportMatcher* ImportDialog::createCategoryPage( const QString& myCategory, const QString& otherCategory ) { StringSet otherItems; DB::ImageInfoList images = selectedImages(); for( DB::ImageInfoListConstIterator it = images.constBegin(); it != images.constEnd(); ++it ) { otherItems += (*it)->itemsOfCategory( otherCategory ); } QStringList myItems = DB::ImageDB::instance()->categoryCollection()->categoryForName( myCategory )->itemsInclCategories(); myItems.sort(); ImportMatcher* matcher = new ImportMatcher( otherCategory, myCategory, otherItems.toList(), myItems, true, this ); addPage( matcher, myCategory ); return matcher; } void ImportDialog::next() { if ( currentPage() == m_destinationPage ) { QString dir = m_destinationEdit->text(); if ( !QFileInfo( dir ).exists() ) { int answer = KMessageBox::questionYesNo( this, i18n("Directory %1 does not exist. Should it be created?", dir ) ); if ( answer == KMessageBox::Yes ) { bool ok = QDir().mkpath(dir); if ( !ok ) { KMessageBox::error( this, i18n("Error creating directory %1", dir ) ); return; } } else return; } } if ( !m_hasFilled && currentPage() == m_categoryMatcherPage ) { m_hasFilled = true; m_categoryMatcher->setEnabled( false ); removePage(m_dummy); ImportMatcher* matcher = nullptr; Q_FOREACH( const CategoryMatch *match, m_categoryMatcher->m_matchers ) { if ( match->m_checkbox->isChecked() ) { matcher = createCategoryPage( match->m_combobox->currentText(), match->m_text ); m_matchers.append( matcher ); } } possiblyAddMD5CheckPage(); } KAssistantDialog::next(); } void ImportDialog::slotSelectAll() { selectImage( true ); } void ImportDialog::slotSelectNone() { selectImage( false ); } void ImportDialog::selectImage( bool on ) { Q_FOREACH( ImageRow* row, m_imagesSelect ) { row->m_checkbox->setChecked( on ); } } DB::ImageInfoList ImportDialog::selectedImages() const { DB::ImageInfoList res; for( QList::ConstIterator it = m_imagesSelect.begin(); it != m_imagesSelect.end(); ++it ) { if ( (*it)->m_checkbox->isChecked() ) res.append( (*it)->m_info ); } return res; } void ImportDialog::slotHelp() { KHelpClient::invokeHelp( QString::fromLatin1( "chp-importExport" ) ); } ImportSettings ImportExport::ImportDialog::settings() { ImportSettings settings; settings.setSelectedImages( selectedImages() ); settings.setDestination( m_destinationEdit->text() ); settings.setExternalSource( m_externalSource ); settings.setKimFile( m_kimFile ); settings.setBaseURL( m_baseUrl ); if ( m_md5CheckPage ) { settings.setImportActions( m_md5CheckPage->settings() ); } for ( ImportMatcher* match : m_matchers ) settings.addCategoryMatchSetting( match->settings() ); return settings; } void ImportExport::ImportDialog::possiblyAddMD5CheckPage() { if ( MD5CheckPage::pageNeeded( settings() ) ) { m_md5CheckPage = new MD5CheckPage( settings() ); addPage(m_md5CheckPage, i18n("How to resolve clashes") ); } } // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/XMLDB/FileReader.cpp b/XMLDB/FileReader.cpp index f841a702..41d1303d 100644 --- a/XMLDB/FileReader.cpp +++ b/XMLDB/FileReader.cpp @@ -1,572 +1,572 @@ /* Copyright (C) 2003-2019 The KPhotoAlbum Development Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Local includes #include "CompressFileInfo.h" #include "Database.h" #include "FileReader.h" #include "Logging.h" #include "XMLCategory.h" #include #include // KDE includes #include // Qt includes #include #include #include #include #include #include #include void XMLDB::FileReader::read( const QString& configFile ) { static QString versionString = QString::fromUtf8("version"); static QString compressedString = QString::fromUtf8("compressed"); ReaderPtr reader = readConfigFile( configFile ); ElementInfo info = reader->readNextStartOrStopElement(QString::fromUtf8("KPhotoAlbum")); if (!info.isStartToken) reader->complainStartElementExpected(QString::fromUtf8("KPhotoAlbum")); m_fileVersion = reader->attribute( versionString, QString::fromLatin1( "1" ) ).toInt(); if ( m_fileVersion > Database::fileVersion() ) { DB::UserFeedback ret = m_db->uiDelegate().warningContinueCancel( QString::fromLatin1("index.xml version %1 is newer than %2!").arg(m_fileVersion).arg(Database::fileVersion()) , i18n("

The database file (index.xml) is from a newer version of KPhotoAlbum!

" "

Chances are you will be able to read this file, but when writing it back, " "information saved in the newer version will be lost

") , i18n("index.xml version mismatch") , QString::fromLatin1( "checkDatabaseFileVersion" ) ); if (ret != DB::UserFeedback::Confirm) exit(-1); } setUseCompressedFileFormat( reader->attribute(compressedString).toInt() ); m_db->m_members.setLoading( true ); loadCategories( reader ); loadImages( reader ); loadBlockList( reader ); loadMemberGroups( reader ); //loadSettings(reader); m_db->m_members.setLoading( false ); checkIfImagesAreSorted(); checkIfAllImagesHaveSizeAttributes(); } void XMLDB::FileReader::createSpecialCategories() { // Setup the "Folder" category m_folderCategory = new XMLCategory(i18n("Folder"), QString::fromLatin1("folder"), DB::Category::TreeView, 32, false ); m_folderCategory->setType( DB::Category::FolderCategory ); // The folder category is not stored in the index.xml file, // but older versions of KPhotoAlbum stored a stub entry, which we need to remove first: if ( m_db->m_categoryCollection.categoryForName(m_folderCategory->name()) ) m_db->m_categoryCollection.removeCategory(m_folderCategory->name()); m_db->m_categoryCollection.addCategory( m_folderCategory ); dynamic_cast( m_folderCategory.data() )->setShouldSave( false ); // Setup the "Tokens" category DB::CategoryPtr tokenCat; if (m_fileVersion >= 7) { tokenCat = m_db->m_categoryCollection.categoryForSpecial( DB::Category::TokensCategory ); } else { // Before version 7, the "Tokens" category name wasn't stored to the settings. So ... // look for a literal "Tokens" category ... tokenCat = m_db->m_categoryCollection.categoryForName(QString::fromUtf8("Tokens")); if (!tokenCat) { // ... and a translated "Tokens" category if we don't have the literal one. tokenCat = m_db->m_categoryCollection.categoryForName(i18n("Tokens")); } if (tokenCat) { // in this case we need to give the tokens category its special meaning: m_db->m_categoryCollection.removeCategory(tokenCat->name()); tokenCat->setType(DB::Category::TokensCategory); m_db->m_categoryCollection.addCategory(tokenCat); } } if (! tokenCat) { // Create a new "Tokens" category tokenCat = new XMLCategory(i18n("Tokens"), QString::fromUtf8("tag"), DB::Category::TreeView, 32, true); tokenCat->setType(DB::Category::TokensCategory); m_db->m_categoryCollection.addCategory(tokenCat); } // KPhotoAlbum 2.2 did not write the tokens to the category section, // so unless we do this small trick they will not show up when importing. for (char ch = 'A'; ch <= 'Z'; ++ch) { tokenCat->addItem(QString::fromUtf8("%1").arg(QChar::fromLatin1(ch))); } // Setup the "Media Type" category DB::CategoryPtr mediaCat; mediaCat = new XMLCategory(i18n("Media Type"), QString::fromLatin1("view-categories"), DB::Category::TreeView, 32, false); mediaCat->addItem( i18n( "Image" ) ); mediaCat->addItem( i18n( "Video" ) ); mediaCat->setType( DB::Category::MediaTypeCategory ); dynamic_cast( mediaCat.data() )->setShouldSave( false ); // The media type is not stored in the media category, // but older versions of KPhotoAlbum stored a stub entry, which we need to remove first: if ( m_db->m_categoryCollection.categoryForName(mediaCat->name()) ) m_db->m_categoryCollection.removeCategory( mediaCat->name() ); m_db->m_categoryCollection.addCategory( mediaCat ); } void XMLDB::FileReader::loadCategories( ReaderPtr reader ) { static QString nameString = QString::fromUtf8("name"); static QString iconString = QString::fromUtf8("icon"); static QString viewTypeString = QString::fromUtf8("viewtype"); static QString showString = QString::fromUtf8("show"); static QString thumbnailSizeString = QString::fromUtf8("thumbnailsize"); static QString positionableString = QString::fromUtf8("positionable"); static QString metaString = QString::fromUtf8("meta"); static QString tokensString = QString::fromUtf8("tokens"); static QString valueString = QString::fromUtf8("value"); static QString idString = QString::fromUtf8("id"); static QString birthDateString = QString::fromUtf8("birthDate"); static QString categoriesString = QString::fromUtf8("Categories"); static QString categoryString = QString::fromUtf8("Category"); ElementInfo info = reader->readNextStartOrStopElement(categoriesString); if (!info.isStartToken) reader->complainStartElementExpected(categoriesString); while ( reader->readNextStartOrStopElement(categoryString).isStartToken) { const QString categoryName = unescape(reader->attribute(nameString)); if ( !categoryName.isNull() ) { // Read Category info QString icon = reader->attribute(iconString); DB::Category::ViewType type = (DB::Category::ViewType) reader->attribute( viewTypeString, QString::fromLatin1( "0" ) ).toInt(); int thumbnailSize = reader->attribute( thumbnailSizeString, QString::fromLatin1( "32" ) ).toInt(); bool show = (bool) reader->attribute( showString, QString::fromLatin1( "1" ) ).toInt(); bool positionable = (bool) reader->attribute( positionableString, QString::fromLatin1( "0" ) ).toInt(); bool tokensCat = reader->attribute(metaString) == tokensString; DB::CategoryPtr cat = m_db->m_categoryCollection.categoryForName( categoryName ); bool repairMode = false; if (cat) { DB::UserFeedback choice = m_db->uiDelegate().warningContinueCancel( QString::fromUtf8("Line %1, column %2: duplicate category '%3'") .arg(reader->lineNumber()).arg(reader->columnNumber()).arg(categoryName) , i18n( "

Line %1, column %2: duplicate category '%3'

" "

Choose continue to ignore the duplicate category and try an automatic repair, " "or choose cancel to quit.

", reader->lineNumber(), reader->columnNumber(), categoryName ) , i18n("Error in database file")); if ( choice == DB::UserFeedback::Confirm ) repairMode = true; else exit(-1); } else { cat = new XMLCategory( categoryName, icon, type, thumbnailSize, show, positionable ); if (tokensCat) cat->setType(DB::Category::TokensCategory); m_db->m_categoryCollection.addCategory( cat ); } // Read values QStringList items; while( reader->readNextStartOrStopElement(valueString).isStartToken) { QString value = reader->attribute(valueString); if ( reader->hasAttribute(idString) ) { int id = reader->attribute(idString).toInt(); static_cast(cat.data())->setIdMapping( value, id ); } if (reader->hasAttribute(birthDateString)) cat->setBirthDate(value,QDate::fromString(reader->attribute(birthDateString), Qt::ISODate)); items.append( value ); reader->readEndElement(); } if ( repairMode ) { // merge with duplicate category qCInfo(XMLDBLog) << "Repairing category " << categoryName << ": merging items " << cat->items() << " with " << items; items.append(cat->items()); items.removeDuplicates(); } cat->setItems( items ); } } createSpecialCategories(); if (m_fileVersion < 7) { m_db->uiDelegate().information( QString::fromLatin1("Standard category names are no longer used since index.xml " "version 7. Standard categories will be left untranslated from now on.") , i18nc("Leave \"Folder\" and \"Media Type\" untranslated below, those will show up with " "these exact names. Thanks :-)", "

This version of KPhotoAlbum does not translate \"standard\" categories " "any more.

" "

This may mean that – if you use a locale other than English – some of your " "categories are now displayed in English.

" "

You can manually rename your categories any time and then save your database." "

" "

In some cases, you may get two additional empty categories, \"Folder\" and " "\"Media Type\". You can delete those.

") , i18n("Changed standard category names") ); } } void XMLDB::FileReader::loadImages( ReaderPtr reader ) { static QString fileString = QString::fromUtf8("file"); static QString imagesString = QString::fromUtf8("images"); static QString imageString = QString::fromUtf8("image"); ElementInfo info = reader->readNextStartOrStopElement(imagesString); if (!info.isStartToken) reader->complainStartElementExpected(imagesString); while (reader->readNextStartOrStopElement(imageString).isStartToken) { const QString fileNameStr = reader->attribute(fileString); if ( fileNameStr.isNull() ) { qCWarning(XMLDBLog, "Element did not contain a file attribute" ); return; } const DB::FileName dbFileName = DB::FileName::fromRelativePath(fileNameStr); DB::ImageInfoPtr info = load( dbFileName, reader ); if ( m_db->md5Map()->containsFile(dbFileName)) { if (m_db->md5Map()->contains(info->MD5Sum())) { qCWarning(XMLDBLog) << "Merging duplicate entry for file" << dbFileName.relative(); DB::ImageInfoPtr existingInfo = m_db->info(dbFileName); existingInfo->merge(*info); } else { m_db->uiDelegate().error( QString::fromUtf8("Conflicting information for file '%1': duplicate entry with different MD5 sum! Bailing out...") .arg(dbFileName.relative()) , i18n( "

Line %1, column %2: duplicate entry for file '%3' with different MD5 sum.

" "

Manual repair required!

", reader->lineNumber(), reader->columnNumber(), dbFileName.relative() ) , i18n("Error in database file")); exit(-1); } } else { m_db->m_images.append(info); m_db->m_md5map.insert( info->MD5Sum(), dbFileName ); } } } void XMLDB::FileReader::loadBlockList( ReaderPtr reader ) { static QString fileString = QString::fromUtf8("file"); static QString blockListString = QString::fromUtf8("blocklist"); static QString blockString = QString::fromUtf8("block"); ElementInfo info = reader->peekNext(); if ( info.isStartToken && info.tokenName == blockListString ) { reader->readNextStartOrStopElement(blockListString); while (reader->readNextStartOrStopElement(blockString).isStartToken) { QString fileName = reader->attribute(fileString); if ( !fileName.isEmpty() ) m_db->m_blockList.insert(DB::FileName::fromRelativePath(fileName)); reader->readEndElement(); } } } void XMLDB::FileReader::loadMemberGroups( ReaderPtr reader ) { static QString categoryString = QString::fromUtf8("category"); static QString groupNameString = QString::fromUtf8("group-name"); static QString memberString = QString::fromUtf8("member"); static QString membersString = QString::fromUtf8("members"); static QString memberGroupsString = QString::fromUtf8("member-groups"); ElementInfo info = reader->peekNext(); if ( info.isStartToken && info.tokenName == memberGroupsString) { reader->readNextStartOrStopElement(memberGroupsString); while(reader->readNextStartOrStopElement(memberString).isStartToken) { QString category = reader->attribute(categoryString); QString group = reader->attribute(groupNameString); if ( reader->hasAttribute(memberString) ) { QString member = reader->attribute(memberString); m_db->m_members.addMemberToGroup( category, group, member ); } else { QStringList members = reader->attribute(membersString).split( QString::fromLatin1( "," ), QString::SkipEmptyParts ); Q_FOREACH( const QString &memberItem, members ) { DB::CategoryPtr catPtr = m_db->m_categoryCollection.categoryForName( category ); if (!catPtr) { // category was not declared in "Categories" qCWarning(XMLDBLog) << "File corruption in index.xml. Inserting missing category: " << category; catPtr = new XMLCategory(category, QString::fromUtf8("dialog-warning"), DB::Category::TreeView, 32, false); m_db->m_categoryCollection.addCategory( catPtr ); } XMLCategory* cat = static_cast( catPtr.data() ); QString member = cat->nameForId( memberItem.toInt() ); if (member.isNull()) continue; m_db->m_members.addMemberToGroup( category, group, member ); } if(members.size() == 0) { // Groups are stored even if they are empty, so we also have to read them. // With no members, the above for loop will not be executed. m_db->m_members.addGroup(category, group); } } reader->readEndElement(); } } } /* void XMLDB::FileReader::loadSettings(ReaderPtr reader) { static QString settingsString = QString::fromUtf8("settings"); static QString settingString = QString::fromUtf8("setting"); static QString keyString = QString::fromUtf8("key"); static QString valueString = QString::fromUtf8("value"); ElementInfo info = reader->peekNext(); if (info.isStartToken && info.tokenName == settingsString) { reader->readNextStartOrStopElement(settingString); while(reader->readNextStartOrStopElement(settingString).isStartToken) { if (reader->hasAttribute(keyString) && reader->hasAttribute(valueString)) { m_db->m_settings.insert(unescape(reader->attribute(keyString)), unescape(reader->attribute(valueString))); } else { qWarning() << "File corruption in index.xml. Setting either lacking a key or a " << "value attribute. Ignoring this entry."; } reader->readEndElement(); } } } */ void XMLDB::FileReader::checkIfImagesAreSorted() { if ( m_db->uiDelegate().isDialogDisabled( QString::fromLatin1( "checkWhetherImagesAreSorted" ) ) ) return; QDateTime last( QDate( 1900, 1, 1 ) ); bool wrongOrder = false; for( DB::ImageInfoListIterator it = m_db->m_images.begin(); !wrongOrder && it != m_db->m_images.end(); ++it ) { if ( last > (*it)->date().start() && (*it)->date().start().isValid() ) wrongOrder = true; last = (*it)->date().start(); } if ( wrongOrder ) { m_db->uiDelegate().information( QString::fromLatin1("Database is not sorted by date.") , i18n("

Your images/videos are not sorted, which means that navigating using the date bar " "will only work suboptimally.

" "

In the Maintenance menu, you can find Display Images with Incomplete Dates " "which you can use to find the images that are missing date information.

" "

You can then select the images that you have reason to believe have a correct date " "in either their Exif data or on the file, and execute Maintenance->Read Exif Info " "to reread the information.

" "

Finally, once all images have their dates set, you can execute " "Maintenance->Sort All by Date & Time to sort them in the database.

") , i18n("Images/Videos Are Not Sorted") , QString::fromLatin1( "checkWhetherImagesAreSorted" ) ); } } void XMLDB::FileReader::checkIfAllImagesHaveSizeAttributes() { QTime time; time.start(); if ( m_db->uiDelegate().isDialogDisabled( QString::fromLatin1( "checkWhetherAllImagesIncludesSize" ) ) ) return; if ( m_db->s_anyImageWithEmptySize ) { m_db->uiDelegate().information( QString::fromLatin1("Found image(s) without size information.") , i18n("

Not all the images in the database have information about image sizes; this is needed to " "get the best result in the thumbnail view. To fix this, simply go to the Maintenance menu, " "and first choose Remove All Thumbnails, and after that choose Build Thumbnails.

" "

Not doing so will result in extra space around images in the thumbnail view - that is all - so " "there is no urgency in doing it.

") , i18n("Not All Images Have Size Information") , QString::fromLatin1( "checkWhetherAllImagesIncludesSize" ) ); } } DB::ImageInfoPtr XMLDB::FileReader::load( const DB::FileName& fileName, ReaderPtr reader ) { DB::ImageInfoPtr info = XMLDB::Database::createImageInfo(fileName, reader, m_db); m_nextStackId = qMax( m_nextStackId, info->stackId() + 1 ); info->createFolderCategoryItem( m_folderCategory, m_db->m_members ); return info; } XMLDB::ReaderPtr XMLDB::FileReader::readConfigFile( const QString& configFile ) { - ReaderPtr reader = ReaderPtr(new XmlReader); + ReaderPtr reader = ReaderPtr(new XmlReader(m_db->uiDelegate())); QFile file( configFile ); if ( !file.exists() ) { // Load a default setup QFile file(QStandardPaths::locate(QStandardPaths::DataLocation, QString::fromLatin1("default-setup"))); if ( !file.open( QIODevice::ReadOnly ) ) { m_db->uiDelegate().information( QString::fromLatin1("default-setup not found in standard paths.") , i18n( "

KPhotoAlbum was unable to load a default setup, which indicates an installation error

" "

If you have installed KPhotoAlbum yourself, then you must remember to set the environment variable " "KDEDIRS, to point to the topmost installation directory.

" "

If you for example ran cmake with -DCMAKE_INSTALL_PREFIX=/usr/local/kde, then you must use the following " "environment variable setup (this example is for Bash and compatible shells):

" "

export KDEDIRS=/usr/local/kde

" "

In case you already have KDEDIRS set, simply append the string as if you where setting the PATH " "environment variable

") , i18n("No default setup file found") ); } else { QTextStream stream( &file ); stream.setCodec( QTextCodec::codecForName("UTF-8") ); QString str = stream.readAll(); // Replace the default setup's category and tag names with localized ones str = str.replace(QString::fromUtf8("People"), i18n("People")); str = str.replace(QString::fromUtf8("Places"), i18n("Places")); str = str.replace(QString::fromUtf8("Events"), i18n("Events")); str = str.replace(QString::fromUtf8("untagged"), i18n("untagged")); str = str.replace( QRegExp( QString::fromLatin1("imageDirectory=\"[^\"]*\"")), QString::fromLatin1("") ); str = str.replace( QRegExp( QString::fromLatin1("htmlBaseDir=\"[^\"]*\"")), QString::fromLatin1("") ); str = str.replace( QRegExp( QString::fromLatin1("htmlBaseURL=\"[^\"]*\"")), QString::fromLatin1("") ); reader->addData(str); } } else { if ( !file.open( QIODevice::ReadOnly ) ) { m_db->uiDelegate().error( QString::fromLatin1("Unable to open '%1' for reading").arg(configFile) , i18n("Unable to open '%1' for reading", configFile ) , i18n("Error Running Demo") ); exit(-1); } reader->addData(file.readAll()); #if 0 QString errMsg; int errLine; int errCol; if ( !doc.setContent( &file, false, &errMsg, &errLine, &errCol )) { file.close(); // If parsing index.xml fails let's see if we could use a backup instead Utilities::checkForBackupFile( configFile, i18n( "line %1 column %2 in file %3: %4", errLine , errCol , configFile , errMsg ) ); if ( !file.open( QIODevice::ReadOnly ) || ( !doc.setContent( &file, false, &errMsg, &errLine, &errCol ) ) ) { KMessageBox::error( messageParent(), i18n( "Failed to recover the backup: %1", errMsg ) ); exit(-1); } } #endif } // Now read the content of the file. #if 0 QDomElement top = doc.documentElement(); if ( top.isNull() ) { KMessageBox::error( messageParent(), i18n("Error in file %1: No elements found", configFile ) ); exit(-1); } if ( top.tagName().toLower() != QString::fromLatin1( "kphotoalbum" ) && top.tagName().toLower() != QString::fromLatin1( "kimdaba" ) ) { // KimDaBa compatibility KMessageBox::error( messageParent(), i18n("Error in file %1: expected 'KPhotoAlbum' as top element but found '%2'", configFile , top.tagName() ) ); exit(-1); } #endif file.close(); return reader; } /** * @brief Unescape a string used as an XML attribute name. * * @see XMLDB::FileWriter::escape * * @param str the string to be unescaped * @return the unescaped string */ QString XMLDB::FileReader::unescape( const QString& str ) { static bool hashUsesCompressedFormat = useCompressedFileFormat(); static QHash s_cache; if (hashUsesCompressedFormat != useCompressedFileFormat()) s_cache.clear(); if ( s_cache.contains(str) ) return s_cache[str]; QString tmp( str ); // Matches encoded characters in attribute names QRegExp rx( QString::fromLatin1( "(_.)([0-9A-F]{2})" ) ); int pos = 0; // Unencoding special characters if compressed XML is selected if ( useCompressedFileFormat() ) { while ( ( pos = rx.indexIn( tmp, pos ) ) != -1 ) { QString before = rx.cap( 1 ) + rx.cap( 2 ); QString after = QString::fromLatin1( QByteArray::fromHex( rx.cap( 2 ).toLocal8Bit() ) ); tmp.replace( pos, before.length(), after ); pos += after.length(); } } else tmp.replace( QString::fromLatin1( "_" ), QString::fromLatin1( " " ) ); s_cache.insert(str,tmp); return tmp; } // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/XMLDB/XmlReader.cpp b/XMLDB/XmlReader.cpp index f83e2f3e..e8896660 100644 --- a/XMLDB/XmlReader.cpp +++ b/XMLDB/XmlReader.cpp @@ -1,116 +1,122 @@ -/* Copyright (C) 2013 Jesper K. Pedersen +/* Copyright (C) 2013-2019 The KPhotoAlbum Development Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "XmlReader.h" + +#include #include -#include namespace XMLDB { -XmlReader::XmlReader() +XmlReader::XmlReader(DB::UIDelegate &ui) + : m_ui(ui) { } QString XmlReader::attribute( const QString& name, const QString& defaultValue ) { QStringRef ref = attributes().value(name); if ( ref.isNull() ) return defaultValue; else return ref.toString(); } ElementInfo XmlReader::readNextStartOrStopElement(const QString& expectedStart) { if (m_peek.isValid) { m_peek.isValid = false; return m_peek; } TokenType type = readNextInternal(); if ( hasError() ) reportError(i18n("Error reading next element")); if ( type != StartElement && type != EndElement ) reportError(i18n("Expected to read a start or stop element, but read %1",tokenString())); const QString elementName = name().toString(); if ( type == StartElement ) { if ( !expectedStart.isNull() && elementName != expectedStart) reportError(i18n("Expected to read %1, but read %2",expectedStart,elementName)); } return ElementInfo(type == StartElement, elementName); } void XmlReader::readEndElement(bool readNextElement) { if ( readNextElement ) readNextInternal(); if ( tokenType() != EndElement ) reportError(i18n("Expected to read an end element but read %1",tokenString())); } bool XmlReader::hasAttribute(const QString &name) { return attributes().hasAttribute(name); } ElementInfo XmlReader::peekNext() { if (m_peek.isValid) return m_peek; m_peek = readNextStartOrStopElement(QString()); return m_peek; } void XmlReader::complainStartElementExpected(const QString &name) { reportError(i18n("Expected to read start element '%1'",name)); } void XmlReader::reportError(const QString & text) { QString message = i18n( "

An error was encountered on line %1, column %2:" "%3

",lineNumber(),columnNumber(),text); if ( hasError() ) message += i18n("

Additional error information:%1

",errorString()); - KMessageBox::error(nullptr, message, i18n( "Error while reading database file" )); + m_ui.error( QString::fromUtf8("XmlReader: error in line %1, column %2 (%3)") + .arg(lineNumber()).arg(columnNumber()).arg(errorString()) + , message + , i18n("Error while reading database file") + ); exit(-1); } QXmlStreamReader::TokenType XmlReader::readNextInternal() { forever { TokenType type = readNext(); if (type == Comment || type == StartDocument) continue; else if (type == Characters ) { if (isWhitespace()) continue; } else return type; } } } // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/XMLDB/XmlReader.h b/XMLDB/XmlReader.h index 5fb4c5b7..12ed5230 100644 --- a/XMLDB/XmlReader.h +++ b/XMLDB/XmlReader.h @@ -1,70 +1,75 @@ -/* Copyright (C) 2013 Jesper K. Pedersen +/* Copyright (C) 2013-2019 The KPhotoAlbum Development Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef XMLREADER_H #define XMLREADER_H #include #include +namespace DB { +class UIDelegate; +} + namespace XMLDB { struct ElementInfo { ElementInfo(bool isStartToken, const QString& tokenName ) : isValid(true), isStartToken(isStartToken),tokenName(tokenName) {} ElementInfo() : isValid(false) {} bool isValid; bool isStartToken; QString tokenName; }; class XmlReader : public QXmlStreamReader { public: - explicit XmlReader(); + explicit XmlReader(DB::UIDelegate &ui); QString attribute(const QString &name, const QString& defaultValue = QString() ); ElementInfo readNextStartOrStopElement(const QString &expectedStart); /** * Read the next element and ensure that it's an EndElement. * If the XmlReader has already read the EndElement (e.g. by calling readNextStartOrStopElement()), * but you want to use this method to ensure consistent error messages, you can * set the parameter readNextElement to false. * * @param readNextElement if set to false, don't read the next element. */ void readEndElement(bool readNextElement = true); bool hasAttribute(const QString& name); ElementInfo peekNext(); void complainStartElementExpected(const QString& name); private: void reportError(const QString&); QString tokenToString(TokenType); TokenType readNextInternal(); + DB::UIDelegate &m_ui; ElementInfo m_peek; }; typedef QSharedPointer ReaderPtr; } #endif // XMLREADER_H // vi:expandtab:tabstop=4 shiftwidth=4: