File indexing completed on 2024-05-12 15:55:23

0001 // SPDX-FileCopyrightText: 2006-2007 Tuomas Suutari <tuomas@nepnep.net>
0002 // SPDX-FileCopyrightText: 2006-2014 Jesper K. Pedersen <jesper.pedersen@kdab.com>
0003 // SPDX-FileCopyrightText: 2007 Dirk Mueller <mueller@kde.org>
0004 // SPDX-FileCopyrightText: 2007 Laurent Montel <montel@kde.org>
0005 // SPDX-FileCopyrightText: 2007-2009 Jan Kundrát <jkt@flaska.net>
0006 // SPDX-FileCopyrightText: 2009 Andrew Coles <andrew.i.coles@googlemail.com>
0007 // SPDX-FileCopyrightText: 2009 Hassan Ibraheem <hasan.ibraheem@gmail.com>
0008 // SPDX-FileCopyrightText: 2009 Henner Zeller <h.zeller@acm.org>
0009 // SPDX-FileCopyrightText: 2012-2020 Yuri Chornoivan <yurchor@ukr.net>
0010 // SPDX-FileCopyrightText: 2012-2013 Miika Turkia <miika.turkia@gmail.com>
0011 // SPDX-FileCopyrightText: 2014-2020 Robert Krawitz <rlk@alum.mit.edu>
0012 // SPDX-FileCopyrightText: 2014-2020 Tobias Leupold <tl@stonemx.de>
0013 // SPDX-FileCopyrightText: 2015 Andreas Neustifter <andreas.neustifter@gmail.com>
0014 // SPDX-FileCopyrightText: 2018 Antoni Bella Pérez <antonibella5@yahoo.com>
0015 // SPDX-FileCopyrightText: 2013-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0016 //
0017 // SPDX-License-Identifier: GPL-2.0-or-later
0018 
0019 // Local includes
0020 #include "FileReader.h"
0021 
0022 #include "CompressFileInfo.h"
0023 
0024 #include <DB/Category.h>
0025 #include <DB/ImageDB.h>
0026 #include <DB/MD5Map.h>
0027 #include <kpabase/Logging.h>
0028 #include <kpabase/UIDelegate.h>
0029 
0030 // KDE includes
0031 #include <KLocalizedString>
0032 
0033 // Qt includes
0034 #include <QFile>
0035 #include <QHash>
0036 #include <QLocale>
0037 #include <QRegExp>
0038 #include <QStandardPaths>
0039 #include <QTextCodec>
0040 #include <QTextStream>
0041 
0042 void DB::FileReader::read(const QString &configFile)
0043 {
0044     static QString versionString = QString::fromUtf8("version");
0045     static QString compressedString = QString::fromUtf8("compressed");
0046 
0047     ReaderPtr reader = readConfigFile(configFile);
0048 
0049     ElementInfo info = reader->readNextStartOrStopElement(QString::fromUtf8("KPhotoAlbum"));
0050     if (!info.isStartToken)
0051         reader->complainStartElementExpected(QString::fromUtf8("KPhotoAlbum"));
0052 
0053     m_fileVersion = reader->attribute(versionString, QString::fromLatin1("1")).toInt();
0054 
0055     if (m_fileVersion > DB::ImageDB::fileVersion()) {
0056         DB::UserFeedback ret = m_db->uiDelegate().warningContinueCancel(
0057             DB::LogMessage { DBLog(), QString::fromLatin1("index.xml version %1 is newer than %2!").arg(m_fileVersion).arg(DB::ImageDB::fileVersion()) },
0058             i18n("<p>The database file (index.xml) is from a newer version of KPhotoAlbum!</p>"
0059                  "<p>Chances are you will be able to read this file, but when writing it back, "
0060                  "information saved in the newer version will be lost</p>"),
0061             i18n("index.xml version mismatch"), QString::fromLatin1("checkDatabaseFileVersion"));
0062         if (ret != DB::UserFeedback::Confirm)
0063             exit(-1);
0064     }
0065 
0066     setUseCompressedFileFormat(reader->attribute(compressedString).toInt());
0067 
0068     m_db->m_members.setLoading(true);
0069 
0070     loadCategories(reader);
0071     loadImages(reader);
0072     loadBlockList(reader);
0073     loadMemberGroups(reader);
0074     // loadSettings(reader);
0075     loadGlobalSortOrder(reader);
0076 
0077     repairDB();
0078 
0079     m_db->m_members.setLoading(false);
0080 
0081     checkIfImagesAreSorted();
0082     checkIfAllImagesHaveSizeAttributes();
0083 }
0084 
0085 void DB::FileReader::createSpecialCategories()
0086 {
0087     // Setup the "Folder" category
0088     m_folderCategory = new DB::Category(i18n("Folder"), QString::fromLatin1("folder"),
0089                                         DB::Category::TreeView, 32, false);
0090     m_folderCategory->setType(DB::Category::FolderCategory);
0091     // The folder category is not stored in the index.xml file,
0092     // but older versions of KPhotoAlbum stored a stub entry, which we need to remove first:
0093     if (m_db->m_categoryCollection.categoryForName(m_folderCategory->name()))
0094         m_db->m_categoryCollection.removeCategory(m_folderCategory->name());
0095     m_db->m_categoryCollection.addCategory(m_folderCategory);
0096     m_folderCategory->setShouldSave(false);
0097 
0098     // Setup the "Tokens" category
0099 
0100     DB::CategoryPtr tokenCat;
0101 
0102     if (m_fileVersion >= 7) {
0103         tokenCat = m_db->m_categoryCollection.categoryForSpecial(DB::Category::TokensCategory);
0104     } else {
0105         // Before version 7, the "Tokens" category name wasn't stored to the settings. So ...
0106         // look for a literal "Tokens" category ...
0107         tokenCat = m_db->m_categoryCollection.categoryForName(QString::fromUtf8("Tokens"));
0108         if (!tokenCat) {
0109             // ... and a translated "Tokens" category if we don't have the literal one.
0110             tokenCat = m_db->m_categoryCollection.categoryForName(i18n("Tokens"));
0111         }
0112         if (tokenCat) {
0113             // in this case we need to give the tokens category its special meaning:
0114             m_db->m_categoryCollection.removeCategory(tokenCat->name());
0115             tokenCat->setType(DB::Category::TokensCategory);
0116             m_db->m_categoryCollection.addCategory(tokenCat);
0117         }
0118     }
0119 
0120     if (!tokenCat) {
0121         // Create a new "Tokens" category
0122         tokenCat = new DB::Category(i18n("Tokens"), QString::fromUtf8("tag"),
0123                                     DB::Category::TreeView, 32, true);
0124         tokenCat->setType(DB::Category::TokensCategory);
0125         m_db->m_categoryCollection.addCategory(tokenCat);
0126     }
0127 
0128     // KPhotoAlbum 2.2 did not write the tokens to the category section,
0129     // so unless we do this small trick they will not show up when importing.
0130     for (char ch = 'A'; ch <= 'Z'; ++ch) {
0131         tokenCat->addItem(QString::fromUtf8("%1").arg(QChar::fromLatin1(ch)));
0132     }
0133 
0134     // Setup the "Media Type" category
0135     DB::CategoryPtr mediaCat;
0136     mediaCat = new DB::Category(i18n("Media Type"), QString::fromLatin1("video"),
0137                                 DB::Category::TreeView, 32, false);
0138     mediaCat->addItem(i18n("Image"));
0139     mediaCat->addItem(i18n("Video"));
0140     mediaCat->setType(DB::Category::MediaTypeCategory);
0141     mediaCat->setShouldSave(false);
0142     // The media type is not stored in the media category,
0143     // but older versions of KPhotoAlbum stored a stub entry, which we need to remove first:
0144     if (m_db->m_categoryCollection.categoryForName(mediaCat->name()))
0145         m_db->m_categoryCollection.removeCategory(mediaCat->name());
0146     m_db->m_categoryCollection.addCategory(mediaCat);
0147 }
0148 
0149 void DB::FileReader::loadCategories(ReaderPtr reader)
0150 {
0151     static QString nameString = QString::fromUtf8("name");
0152     static QString iconString = QString::fromUtf8("icon");
0153     static QString viewTypeString = QString::fromUtf8("viewtype");
0154     static QString showString = QString::fromUtf8("show");
0155     static QString thumbnailSizeString = QString::fromUtf8("thumbnailsize");
0156     static QString positionableString = QString::fromUtf8("positionable");
0157     static QString metaString = QString::fromUtf8("meta");
0158     static QString tokensString = QString::fromUtf8("tokens");
0159     static QString valueString = QString::fromUtf8("value");
0160     static QString idString = QString::fromUtf8("id");
0161     static QString birthDateString = QString::fromUtf8("birthDate");
0162     static QString categoriesString = QString::fromUtf8("Categories");
0163     static QString categoryString = QString::fromUtf8("Category");
0164     static QString untaggedString = QString::fromUtf8("mark-untagged");
0165 
0166     ElementInfo info = reader->readNextStartOrStopElement(categoriesString);
0167     if (!info.isStartToken)
0168         reader->complainStartElementExpected(categoriesString);
0169 
0170     while (reader->readNextStartOrStopElement(categoryString).isStartToken) {
0171         const QString categoryName = unescape(reader->attribute(nameString));
0172         if (!categoryName.isNull()) {
0173             // Read Category info
0174             QString icon = reader->attribute(iconString);
0175             DB::Category::ViewType type = (DB::Category::ViewType)reader->attribute(viewTypeString, QString::fromLatin1("0")).toInt();
0176             int thumbnailSize = reader->attribute(thumbnailSizeString, QString::fromLatin1("32")).toInt();
0177             bool show = (bool)reader->attribute(showString, QString::fromLatin1("1")).toInt();
0178             bool positionable = (bool)reader->attribute(positionableString, QString::fromLatin1("0")).toInt();
0179             bool tokensCat = reader->attribute(metaString) == tokensString;
0180 
0181             DB::CategoryPtr cat = m_db->m_categoryCollection.categoryForName(categoryName);
0182             bool repairMode = false;
0183             if (cat) {
0184                 DB::UserFeedback choice = m_db->uiDelegate().warningContinueCancel(
0185                     DB::LogMessage { DBLog(),
0186                                      QString::fromUtf8("Line %1, column %2: duplicate category '%3'")
0187                                          .arg(reader->lineNumber())
0188                                          .arg(reader->columnNumber())
0189                                          .arg(categoryName) },
0190                     i18n("<p>Line %1, column %2: duplicate category '%3'</p>"
0191                          "<p>Choose continue to ignore the duplicate category and try an automatic repair, "
0192                          "or choose cancel to quit.</p>",
0193                          reader->lineNumber(),
0194                          reader->columnNumber(),
0195                          categoryName),
0196                     i18n("Error in database file"));
0197                 if (choice == DB::UserFeedback::Confirm)
0198                     repairMode = true;
0199                 else
0200                     exit(-1);
0201             } else {
0202                 cat = new DB::Category(categoryName, icon, type, thumbnailSize, show, positionable);
0203                 if (tokensCat)
0204                     cat->setType(DB::Category::TokensCategory);
0205                 m_db->m_categoryCollection.addCategory(cat);
0206             }
0207 
0208             // Read values
0209             QStringList items;
0210             QString untaggedTag;
0211             while (reader->readNextStartOrStopElement(valueString).isStartToken) {
0212                 QString value = reader->attribute(valueString);
0213                 if (reader->hasAttribute(idString)) {
0214                     int id = reader->attribute(idString).toInt();
0215                     if (id != 0) {
0216                         cat->setIdMapping(value, id);
0217                     } else {
0218                         if (useCompressedFileFormat()) {
0219                             qCWarning(DBLog) << "Tag" << categoryName << "/" << value << "has id=0!";
0220                             m_repairTagsWithNullIds = true;
0221                             cat->addZeroMapping(value);
0222                         }
0223                         // else just don't set the id mapping so that a new id gets assigned
0224                     }
0225                 }
0226                 if (reader->hasAttribute(birthDateString))
0227                     cat->setBirthDate(value, QDate::fromString(reader->attribute(birthDateString), Qt::ISODate));
0228                 if (reader->hasAttribute(metaString) && reader->attribute(metaString) == untaggedString) {
0229                     untaggedTag = value;
0230                 }
0231                 items.append(value);
0232                 reader->readEndElement();
0233             }
0234             if (repairMode) {
0235                 // merge with duplicate category
0236                 qCInfo(DBLog) << "Repairing category " << categoryName << ": merging items "
0237                               << cat->items() << " with " << items;
0238                 items.append(cat->items());
0239                 items.removeDuplicates();
0240             }
0241             cat->setItems(items);
0242             if (!untaggedTag.isEmpty()) {
0243                 m_db->setUntaggedTag(cat->itemForName(untaggedTag));
0244             }
0245         }
0246     }
0247 
0248     createSpecialCategories();
0249 
0250     if (m_fileVersion < 7) {
0251         m_db->uiDelegate().information(
0252             DB::LogMessage { DBLog(), QString::fromLatin1("Standard category names are no longer used since index.xml "
0253                                                           "version 7. Standard categories will be left untranslated from now on.") },
0254             i18nc("Leave \"Folder\" and \"Media Type\" untranslated below, those will show up with "
0255                   "these exact names. Thanks :-)",
0256                   "<p><b>This version of KPhotoAlbum does not translate \"standard\" categories "
0257                   "any more.</b></p>"
0258                   "<p>This may mean that – if you use a locale other than English – some of your "
0259                   "categories are now displayed in English.</p>"
0260                   "<p>You can manually rename your categories any time and then save your database."
0261                   "</p>"
0262                   "<p>In some cases, you may get two additional empty categories, \"Folder\" and "
0263                   "\"Media Type\". You can delete those.</p>"),
0264             i18n("Changed standard category names"));
0265     }
0266 }
0267 
0268 void DB::FileReader::loadImages(ReaderPtr reader)
0269 {
0270     static QString fileString = QString::fromUtf8("file");
0271     static QString imagesString = QString::fromUtf8("images");
0272     static QString imageString = QString::fromUtf8("image");
0273 
0274     ElementInfo info = reader->readNextStartOrStopElement(imagesString);
0275     if (!info.isStartToken)
0276         reader->complainStartElementExpected(imagesString);
0277 
0278     while (reader->readNextStartOrStopElement(imageString).isStartToken) {
0279         const QString fileNameStr = reader->attribute(fileString);
0280         if (fileNameStr.isNull()) {
0281             qCWarning(DBLog, "Element did not contain a file attribute");
0282             return;
0283         }
0284 
0285         const DB::FileName dbFileName = DB::FileName::fromRelativePath(fileNameStr);
0286 
0287         DB::ImageInfoPtr info = load(dbFileName, reader);
0288         if (m_db->md5Map()->containsFile(dbFileName)) {
0289             if (m_db->md5Map()->contains(info->MD5Sum())) {
0290                 qCWarning(DBLog) << "Merging duplicate entry for file" << dbFileName.relative();
0291                 DB::ImageInfoPtr existingInfo = m_db->info(dbFileName);
0292                 existingInfo->merge(*info);
0293             } else {
0294                 m_db->uiDelegate().error(
0295                     DB::LogMessage { DBLog(), QString::fromUtf8("Conflicting information for file '%1': duplicate entry with different MD5 sum! Bailing out...").arg(dbFileName.relative()) },
0296                     i18n("<p>Line %1, column %2: duplicate entry for file '%3' with different MD5 sum.</p>"
0297                          "<p>Manual repair required!</p>",
0298                          reader->lineNumber(),
0299                          reader->columnNumber(),
0300                          dbFileName.relative()),
0301                     i18n("Error in database file"));
0302                 exit(-1);
0303             }
0304         } else {
0305             m_db->m_images.append(info);
0306             m_db->m_md5map.insert(info->MD5Sum(), dbFileName);
0307         }
0308     }
0309 }
0310 
0311 void DB::FileReader::loadBlockList(ReaderPtr reader)
0312 {
0313     static QString fileString = QString::fromUtf8("file");
0314     static QString blockListString = QString::fromUtf8("blocklist");
0315     static QString blockString = QString::fromUtf8("block");
0316 
0317     ElementInfo info = reader->peekNext();
0318     if (info.isStartToken && info.tokenName == blockListString) {
0319         reader->readNextStartOrStopElement(blockListString);
0320         while (reader->readNextStartOrStopElement(blockString).isStartToken) {
0321             QString fileName = reader->attribute(fileString);
0322             if (!fileName.isEmpty())
0323                 m_db->m_blockList.insert(DB::FileName::fromRelativePath(fileName));
0324             reader->readEndElement();
0325         }
0326     }
0327 }
0328 
0329 void DB::FileReader::loadMemberGroups(ReaderPtr reader)
0330 {
0331     static QString categoryString = QString::fromUtf8("category");
0332     static QString groupNameString = QString::fromUtf8("group-name");
0333     static QString memberString = QString::fromUtf8("member");
0334     static QString membersString = QString::fromUtf8("members");
0335     static QString memberGroupsString = QString::fromUtf8("member-groups");
0336 
0337     ElementInfo info = reader->peekNext();
0338     if (info.isStartToken && info.tokenName == memberGroupsString) {
0339         reader->readNextStartOrStopElement(memberGroupsString);
0340         while (reader->readNextStartOrStopElement(memberString).isStartToken) {
0341             QString category = reader->attribute(categoryString);
0342 
0343             QString group = reader->attribute(groupNameString);
0344             if (reader->hasAttribute(memberString)) {
0345                 QString member = reader->attribute(memberString);
0346                 m_db->m_members.addMemberToGroup(category, group, member);
0347             } else {
0348                 const QStringList members = reader->attribute(membersString).split(QString::fromLatin1(","), Qt::SkipEmptyParts);
0349                 for (const QString &memberItem : members) {
0350                     DB::CategoryPtr catPtr = m_db->m_categoryCollection.categoryForName(category);
0351                     if (!catPtr) { // category was not declared in "Categories"
0352                         qCWarning(DBLog) << "File corruption in index.xml. Inserting missing category: " << category;
0353                         catPtr = new DB::Category(category, QString::fromUtf8("dialog-warning"), DB::Category::TreeView, 32, false);
0354                         m_db->m_categoryCollection.addCategory(catPtr);
0355                     }
0356                     const QString member = catPtr->nameForId(memberItem.toInt());
0357                     if (member.isNull()) {
0358                         qCWarning(DBLog) << "Tag group" << category << "references non-existing tag with id"
0359                                          << memberItem << "!";
0360                         continue;
0361                     }
0362                     m_db->m_members.addMemberToGroup(category, group, member);
0363                 }
0364 
0365                 if (members.size() == 0) {
0366                     // Groups are stored even if they are empty, so we also have to read them.
0367                     // With no members, the above for loop will not be executed.
0368                     m_db->m_members.addGroup(category, group);
0369                 }
0370             }
0371 
0372             reader->readEndElement();
0373         }
0374     }
0375 }
0376 
0377 void DB::FileReader::loadGlobalSortOrder(ReaderPtr reader)
0378 {
0379     ElementInfo info = reader->peekNext();
0380     auto &list = m_db->categoryCollection()->globalSortOrder()->m_sortOrder;
0381 
0382     if (info.isStartToken && info.tokenName == QStringLiteral("global-sort-order")) {
0383         reader->readNextStartOrStopElement(QStringLiteral("item"));
0384         while (reader->readNextStartOrStopElement(QStringLiteral("item")).isStartToken) {
0385             const QString category = reader->attribute(QStringLiteral("category"));
0386             const QString item = reader->attribute(QStringLiteral("item"));
0387             list.append({ category, item });
0388             reader->readEndElement();
0389         }
0390     }
0391 }
0392 
0393 /*
0394 void DB::FileReader::loadSettings(ReaderPtr reader)
0395 {
0396     static QString settingsString = QString::fromUtf8("settings");
0397     static QString settingString = QString::fromUtf8("setting");
0398     static QString keyString = QString::fromUtf8("key");
0399     static QString valueString = QString::fromUtf8("value");
0400 
0401     ElementInfo info = reader->peekNext();
0402     if (info.isStartToken && info.tokenName == settingsString) {
0403         reader->readNextStartOrStopElement(settingString);
0404         while(reader->readNextStartOrStopElement(settingString).isStartToken) {
0405             if (reader->hasAttribute(keyString) && reader->hasAttribute(valueString)) {
0406                 m_db->m_settings.insert(unescape(reader->attribute(keyString)),
0407                                         unescape(reader->attribute(valueString)));
0408             } else {
0409                 qWarning() << "File corruption in index.xml. Setting either lacking a key or a "
0410                            << "value attribute. Ignoring this entry.";
0411             }
0412             reader->readEndElement();
0413         }
0414     }
0415 }
0416 */
0417 
0418 void DB::FileReader::checkIfImagesAreSorted()
0419 {
0420     if (m_db->uiDelegate().isDialogDisabled(QString::fromLatin1("checkWhetherImagesAreSorted")))
0421         return;
0422 
0423     Utilities::FastDateTime last(QDate(1900, 1, 1));
0424     bool wrongOrder = false;
0425     for (DB::ImageInfoListIterator it = m_db->m_images.begin(); !wrongOrder && it != m_db->m_images.end(); ++it) {
0426         if (last > (*it)->date().start() && (*it)->date().start().isValid())
0427             wrongOrder = true;
0428         last = (*it)->date().start();
0429     }
0430 
0431     if (wrongOrder) {
0432         m_db->uiDelegate().information(
0433             DB::LogMessage { DBLog(),
0434                              QString::fromLatin1("Database is not sorted by date.") },
0435             i18n("<p>Your images/videos are not sorted, which means that navigating using the date bar "
0436                  "will only work suboptimally.</p>"
0437                  "<p>In the <b>Maintenance</b> menu, you can find <b>Display Images with Incomplete Dates</b> "
0438                  "which you can use to find the images that are missing date information.</p>"
0439                  "<p>You can then select the images that you have reason to believe have a correct date "
0440                  "in either their Exif data or on the file, and execute <b>Maintenance->Read Exif Info</b> "
0441                  "to reread the information.</p>"
0442                  "<p>Finally, once all images have their dates set, you can execute "
0443                  "<b>Maintenance->Sort All by Date & Time</b> to sort them in the database. </p>"),
0444             i18n("Images/Videos Are Not Sorted"), QString::fromLatin1("checkWhetherImagesAreSorted"));
0445     }
0446 }
0447 
0448 void DB::FileReader::checkIfAllImagesHaveSizeAttributes()
0449 {
0450     if (m_db->uiDelegate().isDialogDisabled(QString::fromLatin1("checkWhetherAllImagesIncludesSize")))
0451         return;
0452 
0453     if (m_db->s_anyImageWithEmptySize) {
0454         m_db->uiDelegate().information(
0455             DB::LogMessage { DBLog(), QString::fromLatin1("Found image(s) without size information.") },
0456             i18n("<p>Not all the images in the database have information about image sizes; this is needed to "
0457                  "get the best result in the thumbnail view. To fix this, simply go to the <b>Maintenance</b> menu, "
0458                  "and first choose <b>Remove All Thumbnails</b>, and after that choose <tt>Build Thumbnails</tt>.</p>"
0459                  "<p>Not doing so will result in extra space around images in the thumbnail view - that is all - so "
0460                  "there is no urgency in doing it.</p>"),
0461             i18n("Not All Images Have Size Information"), QString::fromLatin1("checkWhetherAllImagesIncludesSize"));
0462     }
0463 }
0464 
0465 void DB::FileReader::repairDB()
0466 {
0467     if (m_repairTagsWithNullIds) {
0468         // the m_repairTagsWithNullIds is set in loadCategories()
0469         // -> care is taken so that multiple tags with id=0 all end up in the IdMap
0470         // afterwards, loadImages() applies fixes to the affected images
0471         // -> this happens in DB::ImageDB::possibleLoadCompressedCategories()
0472         // i.e. the zero ids still require cleanup:
0473         qCInfo(DBLog) << "Database contained tags with id=0 (possibly related to bug #415415). Assigning new ids for affected categories...";
0474         QString message = i18nc("repair merged tags",
0475                                 "<p>Inconsistencies were found and repaired in your database. "
0476                                 "Some categories now contain tags that were merged during the repair.</p>"
0477                                 "<p>The following tags require manual inspection:"
0478                                 "<ul>");
0479         QString logSummary = QString::fromLatin1("List of tags where manual inspection is required:\n");
0480         bool manualRepairNeeded = false;
0481         for (auto category : m_db->categoryCollection()->categories()) {
0482             const QStringList tags = category->namesForIdZero();
0483             if (tags.size() > 1) {
0484                 manualRepairNeeded = true;
0485                 message += i18nc("repair merged tags", "<li>%1:<br/>", category->name());
0486                 for (auto tagName : tags) {
0487                     message += i18nc("repair merged tags", "%1<br/>", tagName);
0488                     logSummary += QString::fromLatin1("%1/%2\n").arg(category->name(), tagName);
0489                 }
0490                 message += i18nc("repair merged tags", "</li>");
0491             }
0492             category->clearNullIds();
0493         }
0494         message += i18nc("repair merged tags",
0495                          "</ul></p>"
0496                          "<p>All affected images have also been marked with a tag "
0497                          "<em>KPhotoAlbum - manual repair needed</em>.</p>");
0498         if (manualRepairNeeded) {
0499             m_db->uiDelegate().information(DB::LogMessage { DBLog(), logSummary }, message, i18n("Database repair required"));
0500         }
0501     }
0502 }
0503 
0504 DB::ImageInfoPtr DB::FileReader::load(const DB::FileName &fileName, ReaderPtr reader)
0505 {
0506     DB::ImageInfoPtr info = DB::ImageDB::createImageInfo(fileName, reader, m_db);
0507     m_nextStackId = qMax(m_nextStackId, info->stackId() + 1);
0508     info->createFolderCategoryItem(m_folderCategory, m_db->m_members);
0509     return info;
0510 }
0511 
0512 DB::ReaderPtr DB::FileReader::readConfigFile(const QString &configFile)
0513 {
0514     ReaderPtr reader = ReaderPtr(new XmlReader(m_db->uiDelegate(), configFile));
0515     QFile file(configFile);
0516     if (!file.exists()) {
0517         // Load a default setup
0518         QFile file(QStandardPaths::locate(QStandardPaths::DataLocation, QString::fromLatin1("default-setup")));
0519         if (!file.open(QIODevice::ReadOnly)) {
0520             m_db->uiDelegate().information(
0521                 DB::LogMessage { DBLog(), QString::fromLatin1("default-setup not found in standard paths.") },
0522                 i18n("<p>KPhotoAlbum was unable to load a default setup, which indicates an installation error</p>"
0523                      "<p>If you have installed KPhotoAlbum yourself, then you must remember to set the environment variable "
0524                      "<b>KDEDIRS</b>, to point to the topmost installation folder.</p>"
0525                      "<p>If you for example ran cmake with <b>-DCMAKE_INSTALL_PREFIX=/usr/local/kde</b>, then you must use the following "
0526                      "environment variable setup (this example is for Bash and compatible shells):</p>"
0527                      "<p><b>export KDEDIRS=/usr/local/kde</b></p>"
0528                      "<p>In case you already have KDEDIRS set, simply append the string as if you where setting the <b>PATH</b> "
0529                      "environment variable</p>"),
0530                 i18n("No default setup file found"));
0531         } else {
0532             QTextStream stream(&file);
0533             stream.setCodec(QTextCodec::codecForName("UTF-8"));
0534             QString str = stream.readAll();
0535 
0536             // Replace the default setup's category and tag names with localized ones
0537             str = str.replace(QString::fromUtf8("People"), i18n("People"));
0538             str = str.replace(QString::fromUtf8("Places"), i18n("Places"));
0539             str = str.replace(QString::fromUtf8("Events"), i18n("Events"));
0540             str = str.replace(QString::fromUtf8("untagged"), i18n("untagged"));
0541 
0542             str = str.replace(QRegExp(QString::fromLatin1("imageDirectory=\"[^\"]*\"")), QString::fromLatin1(""));
0543             str = str.replace(QRegExp(QString::fromLatin1("htmlBaseDir=\"[^\"]*\"")), QString::fromLatin1(""));
0544             str = str.replace(QRegExp(QString::fromLatin1("htmlBaseURL=\"[^\"]*\"")), QString::fromLatin1(""));
0545             reader->addData(str);
0546         }
0547     } else {
0548         if (!file.open(QIODevice::ReadOnly)) {
0549             m_db->uiDelegate().error(
0550                 DB::LogMessage { DBLog(), QString::fromLatin1("Unable to open '%1' for reading").arg(configFile) },
0551                 i18n("Unable to open '%1' for reading", configFile), i18n("Error Running Demo"));
0552             exit(-1);
0553         }
0554 
0555         reader->addData(file.readAll());
0556 #if 0
0557         QString errMsg;
0558         int errLine;
0559         int errCol;
0560 
0561         if ( !doc.setContent( &file, false, &errMsg, &errLine, &errCol )) {
0562             file.close();
0563             // If parsing index.xml fails let's see if we could use a backup instead
0564             Utilities::checkForBackupFile( configFile, i18n( "line %1 column %2 in file %3: %4", errLine , errCol , configFile , errMsg ) );
0565             if ( !file.open( QIODevice::ReadOnly ) || ( !doc.setContent( &file, false, &errMsg, &errLine, &errCol ) ) ) {
0566                 KMessageBox::error( messageParent(), i18n( "Failed to recover the backup: %1", errMsg ) );
0567                 exit(-1);
0568             }
0569         }
0570 #endif
0571     }
0572 
0573     // Now read the content of the file.
0574 #if 0
0575     QDomElement top = doc.documentElement();
0576     if ( top.isNull() ) {
0577         KMessageBox::error( messageParent(), i18n("Error in file %1: No elements found", configFile ) );
0578         exit(-1);
0579     }
0580 
0581     if ( top.tagName().toLower() != QString::fromLatin1( "kphotoalbum" ) &&
0582          top.tagName().toLower() != QString::fromLatin1( "kimdaba" ) ) { // KimDaBa compatibility
0583         KMessageBox::error( messageParent(), i18n("Error in file %1: expected 'KPhotoAlbum' as top element but found '%2'", configFile , top.tagName() ) );
0584         exit(-1);
0585     }
0586 #endif
0587 
0588     file.close();
0589     return reader;
0590 }
0591 
0592 /**
0593  * @brief Unescape a string used as an XML attribute name.
0594  *
0595  * @see DB::FileWriter::escape
0596  *
0597  * @param str the string to be unescaped
0598  * @return the unescaped string
0599  */
0600 QString DB::FileReader::unescape(const QString &str)
0601 {
0602     static bool hashUsesCompressedFormat = useCompressedFileFormat();
0603     static QHash<QString, QString> s_cache;
0604     if (hashUsesCompressedFormat != useCompressedFileFormat())
0605         s_cache.clear();
0606     if (s_cache.contains(str))
0607         return s_cache[str];
0608 
0609     QString tmp(str);
0610     // Matches encoded characters in attribute names
0611     QRegExp rx(QString::fromLatin1("(_.)([0-9A-F]{2})"));
0612     int pos = 0;
0613 
0614     // Unencoding special characters if compressed XML is selected
0615     if (useCompressedFileFormat()) {
0616         while ((pos = rx.indexIn(tmp, pos)) != -1) {
0617             QString before = rx.cap(1) + rx.cap(2);
0618             QString after = QString::fromLatin1(QByteArray::fromHex(rx.cap(2).toLocal8Bit()));
0619             tmp.replace(pos, before.length(), after);
0620             pos += after.length();
0621         }
0622     } else
0623         tmp.replace(QString::fromLatin1("_"), QString::fromLatin1(" "));
0624 
0625     s_cache.insert(str, tmp);
0626     return tmp;
0627 }
0628 
0629 // vi:expandtab:tabstop=4 shiftwidth=4: