File indexing completed on 2024-05-19 04:23:36
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: