diff --git a/core/libs/album/albummanager.cpp b/core/libs/album/albummanager.cpp index 3634b5a227..0d96fa8064 100644 --- a/core/libs/album/albummanager.cpp +++ b/core/libs/album/albummanager.cpp @@ -1,3643 +1,3649 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-06-15 * Description : Albums manager interface. * * Copyright (C) 2004 by Renchi Raju * Copyright (C) 2006-2018 by Gilles Caulier * Copyright (C) 2006-2011 by Marcel Wiesweg * Copyright (C) 2015 by Mohamed Anwer * * 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, 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. * * ============================================================ */ #include "albummanager.h" // C ANSI includes extern "C" { #include #include #include } // C++ includes #include #include #include #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "coredb.h" #include "album.h" #include "applicationsettings.h" #include "albumwatch.h" #include "imageattributeswatch.h" #include "collectionlocation.h" #include "collectionmanager.h" #include "digikam_config.h" #include "coredbaccess.h" #include "coredboperationgroup.h" #include "dbengineguierrorhandler.h" #include "dbengineparameters.h" #include "databaseserverstarter.h" #include "coredbthumbinfoprovider.h" #include "coredburl.h" #include "coredbsearchxml.h" #include "coredbwatch.h" #include "dio.h" #include "facetags.h" #include "facetagseditor.h" #include "imagelister.h" #include "scancontroller.h" #include "setupcollections.h" #include "setup.h" #include "tagscache.h" #include "thumbsdbaccess.h" #include "thumbnailloadthread.h" #include "dnotificationwrapper.h" #include "dbjobinfo.h" #include "dbjobsmanager.h" #include "dbjobsthread.h" #include "similaritydb.h" #include "similaritydbaccess.h" namespace Digikam { class PAlbumPath { public: PAlbumPath() : albumRootId(-1) { } PAlbumPath(int albumRootId, const QString& albumPath) : albumRootId(albumRootId), albumPath(albumPath) { } PAlbumPath(PAlbum* const album) { if (album->isRoot()) { albumRootId = -1; } else { albumRootId = album->albumRootId(); albumPath = album->albumPath(); } } bool operator==(const PAlbumPath& other) const { return (other.albumRootId == albumRootId && other.albumPath == albumPath); } public: int albumRootId; QString albumPath; }; // ----------------------------------------------------------------------------------- uint qHash(const PAlbumPath& id) { return ( ::qHash(id.albumRootId) ^ ::qHash(id.albumPath) ); } // ----------------------------------------------------------------------------------- class AlbumManager::Private { public: Private() : changed(false), hasPriorizedDbPath(false), dbFakeConnection(false), showOnlyAvailableAlbums(false), albumListJob(0), dateListJob(0), tagListJob(0), personListJob(0), albumWatch(0), rootPAlbum(0), rootTAlbum(0), rootDAlbum(0), rootSAlbum(0), currentlyMovingAlbum(0), changingDB(false), scanPAlbumsTimer(0), scanTAlbumsTimer(0), scanSAlbumsTimer(0), scanDAlbumsTimer(0), updatePAlbumsTimer(0), albumItemCountTimer(0), tagItemCountTimer(0) { } bool changed; bool hasPriorizedDbPath; bool dbFakeConnection; bool showOnlyAvailableAlbums; AlbumsDBJobsThread* albumListJob; DatesDBJobsThread* dateListJob; TagsDBJobsThread* tagListJob; TagsDBJobsThread* personListJob; AlbumWatch* albumWatch; PAlbum* rootPAlbum; TAlbum* rootTAlbum; DAlbum* rootDAlbum; SAlbum* rootSAlbum; QHash allAlbumsIdHash; QHash albumPathHash; QHash albumRootAlbumHash; Album* currentlyMovingAlbum; QMultiHash guardedPointers; /** For multiple selection support **/ QList currentAlbums; bool changingDB; QTimer* scanPAlbumsTimer; QTimer* scanTAlbumsTimer; QTimer* scanSAlbumsTimer; QTimer* scanDAlbumsTimer; QTimer* updatePAlbumsTimer; QTimer* albumItemCountTimer; QTimer* tagItemCountTimer; QSet changedPAlbums; QMap pAlbumsCount; QMap tAlbumsCount; QMap dAlbumsCount; QMap fAlbumsCount; public: QString labelForAlbumRootAlbum(const CollectionLocation& location) { QString label = location.label(); if (label.isEmpty()) { label = location.albumRootPath(); } return label; } }; // ----------------------------------------------------------------------------------- class ChangingDB { public: explicit ChangingDB(AlbumManager::Private* const d) : d(d) { d->changingDB = true; } ~ChangingDB() { d->changingDB = false; } AlbumManager::Private* const d; }; // ----------------------------------------------------------------------------------- class AlbumManagerCreator { public: AlbumManager object; }; Q_GLOBAL_STATIC(AlbumManagerCreator, creator) // ----------------------------------------------------------------------------------- // A friend-class shortcut to circumvent accessing this from within the destructor AlbumManager* AlbumManager::internalInstance = 0; AlbumManager* AlbumManager::instance() { return &creator->object; } AlbumManager::AlbumManager() : d(new Private) { qRegisterMetaType>("QMap"); qRegisterMetaType>("QMap"); qRegisterMetaType >>("QMap >"); internalInstance = this; d->albumWatch = new AlbumWatch(this); // these operations are pretty fast, no need for long queuing d->scanPAlbumsTimer = new QTimer(this); d->scanPAlbumsTimer->setInterval(50); d->scanPAlbumsTimer->setSingleShot(true); connect(d->scanPAlbumsTimer, SIGNAL(timeout()), this, SLOT(scanPAlbums())); d->scanTAlbumsTimer = new QTimer(this); d->scanTAlbumsTimer->setInterval(50); d->scanTAlbumsTimer->setSingleShot(true); connect(d->scanTAlbumsTimer, SIGNAL(timeout()), this, SLOT(scanTAlbums())); d->scanSAlbumsTimer = new QTimer(this); d->scanSAlbumsTimer->setInterval(50); d->scanSAlbumsTimer->setSingleShot(true); connect(d->scanSAlbumsTimer, SIGNAL(timeout()), this, SLOT(scanSAlbums())); d->updatePAlbumsTimer = new QTimer(this); d->updatePAlbumsTimer->setInterval(50); d->updatePAlbumsTimer->setSingleShot(true); connect(d->updatePAlbumsTimer, SIGNAL(timeout()), this, SLOT(updateChangedPAlbums())); // this operation is much more expensive than the other scan methods d->scanDAlbumsTimer = new QTimer(this); d->scanDAlbumsTimer->setInterval(30 * 1000); d->scanDAlbumsTimer->setSingleShot(true); connect(d->scanDAlbumsTimer, SIGNAL(timeout()), this, SLOT(scanDAlbumsScheduled())); // moderately expensive d->albumItemCountTimer = new QTimer(this); d->albumItemCountTimer->setInterval(1000); d->albumItemCountTimer->setSingleShot(true); connect(d->albumItemCountTimer, SIGNAL(timeout()), this, SLOT(getAlbumItemsCount())); // more expensive d->tagItemCountTimer = new QTimer(this); d->tagItemCountTimer->setInterval(2500); d->tagItemCountTimer->setSingleShot(true); connect(d->tagItemCountTimer, SIGNAL(timeout()), this, SLOT(getTagItemsCount())); } AlbumManager::~AlbumManager() { delete d->rootPAlbum; delete d->rootTAlbum; delete d->rootDAlbum; delete d->rootSAlbum; internalInstance = 0; delete d; } void AlbumManager::cleanUp() { // This is what we prefer to do before Application destruction if (d->dateListJob) { d->dateListJob->cancel(); d->dateListJob = 0; } if (d->albumListJob) { d->albumListJob->cancel(); d->albumListJob = 0; } if (d->tagListJob) { d->tagListJob->cancel(); d->tagListJob = 0; } if (d->personListJob) { d->personListJob->cancel(); d->personListJob = 0; } } bool AlbumManager::databaseEqual(const DbEngineParameters& parameters) const { DbEngineParameters params = CoreDbAccess::parameters(); return (params == parameters); } static bool moveToBackup(const QFileInfo& info) { if (info.exists()) { QFileInfo backup(info.dir(), info.fileName() + QLatin1String("-backup-") + QDateTime::currentDateTime().toString(Qt::ISODate)); bool ret = QDir().rename(info.filePath(), backup.filePath()); if (!ret) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), i18n("Failed to backup the existing database file (\"%1\"). " "Refusing to replace file without backup, using the existing file.", QDir::toNativeSeparators(info.filePath()))); return false; } } return true; } static bool copyToNewLocation(const QFileInfo& oldFile, const QFileInfo& newFile, const QString otherMessage = QString()) { QString message = otherMessage; if (message.isNull()) { message = i18n("Failed to copy the old database file (\"%1\") " "to its new location (\"%2\"). " "Starting with an empty database.", QDir::toNativeSeparators(oldFile.filePath()), QDir::toNativeSeparators(newFile.filePath())); } bool ret = QFile::copy(oldFile.filePath(), newFile.filePath()); if (!ret) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), message); return false; } return true; } void AlbumManager::checkDatabaseDirsAfterFirstRun(const QString& dbPath, const QString& albumPath) { // for bug #193522 QDir newDir(dbPath); QDir albumDir(albumPath); DbEngineParameters newParams = DbEngineParameters::parametersForSQLiteDefaultFile(newDir.path()); QFileInfo digikam4DB(newParams.SQLiteDatabaseFile()); if (!digikam4DB.exists()) { QFileInfo digikam3DB(newDir, QLatin1String("digikam3.db")); QFileInfo digikamVeryOldDB(newDir, QLatin1String("digikam.db")); if (digikam3DB.exists() || digikamVeryOldDB.exists()) { QMessageBox msgBox(QMessageBox::Warning, i18n("Database Folder"), i18n("

You have chosen the folder \"%1\" as the place to store the database. " "A database file from an older version of digiKam is found in this folder.

" "

Would you like to upgrade the old database file - confirming " "that this database file was indeed created for the pictures located in the folder \"%2\" - " "or ignore the old file and start with a new database?

", QDir::toNativeSeparators(newDir.path()), QDir::toNativeSeparators(albumDir.path())), QMessageBox::Yes | QMessageBox::No, qApp->activeWindow()); msgBox.button(QMessageBox::Yes)->setText(i18n("Upgrade Database")); msgBox.button(QMessageBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("view-refresh"))); msgBox.button(QMessageBox::No)->setText(i18n("Create New Database")); msgBox.button(QMessageBox::No)->setIcon(QIcon::fromTheme(QLatin1String("document-new"))); msgBox.setDefaultButton(QMessageBox::Yes); int result = msgBox.exec(); if (result == QMessageBox::Yes) { // CoreDbSchemaUpdater expects Album Path to point to the album root of the 0.9 db file. // Restore this situation. KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("Album Settings"); group.writeEntry("Album Path", albumDir.path()); group.sync(); } else if (result == QMessageBox::No) { moveToBackup(digikam3DB); moveToBackup(digikamVeryOldDB); } } } } void AlbumManager::changeDatabase(const DbEngineParameters& newParams) { // if there is no file at the new place, copy old one DbEngineParameters params = CoreDbAccess::parameters(); // New database type SQLITE if (newParams.isSQLite()) { DatabaseServerStarter::instance()->stopServerManagerProcess(); QDir newDir(newParams.getCoreDatabaseNameOrDir()); QFileInfo newFile(newDir, QLatin1String("digikam4.db")); if (!newFile.exists()) { QFileInfo digikam3DB(newDir, QLatin1String("digikam3.db")); QFileInfo digikamVeryOldDB(newDir, QLatin1String("digikam.db")); if (digikam3DB.exists() || digikamVeryOldDB.exists()) { int result = -1; if (params.isSQLite()) { QMessageBox msgBox(QMessageBox::Warning, i18n("New database folder"), i18n("

You have chosen the folder \"%1\" as the new place to store the database. " "A database file from an older version of digiKam is found in this folder.

" "

Would you like to upgrade the old database file, start with a new database, " "or copy the current database to this location and continue using it?

", QDir::toNativeSeparators(newDir.path())), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, qApp->activeWindow()); msgBox.button(QMessageBox::Yes)->setText(i18n("Upgrade Database")); msgBox.button(QMessageBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("view-refresh"))); msgBox.button(QMessageBox::No)->setText(i18n("Create New Database")); msgBox.button(QMessageBox::No)->setIcon(QIcon::fromTheme(QLatin1String("document-new"))); msgBox.button(QMessageBox::Cancel)->setText(i18n("Copy Current Database")); msgBox.button(QMessageBox::Cancel)->setIcon(QIcon::fromTheme(QLatin1String("edit-copy"))); msgBox.setDefaultButton(QMessageBox::Yes); result = msgBox.exec(); } else { QMessageBox msgBox(QMessageBox::Warning, i18n("New database folder"), i18n("

You have chosen the folder \"%1\" as the new place to store the database. " "A database file from an older version of digiKam is found in this folder.

" "

Would you like to upgrade the old database file or start with a new database?

", QDir::toNativeSeparators(newDir.path())), QMessageBox::Yes | QMessageBox::No, qApp->activeWindow()); msgBox.button(QMessageBox::Yes)->setText(i18n("Upgrade Database")); msgBox.button(QMessageBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("view-refresh"))); msgBox.button(QMessageBox::No)->setText(i18n("Create New Database")); msgBox.button(QMessageBox::No)->setIcon(QIcon::fromTheme(QLatin1String("document-new"))); msgBox.setDefaultButton(QMessageBox::Yes); result = msgBox.exec(); } if (result == QMessageBox::Yes) { // CoreDbSchemaUpdater expects Album Path to point to the album root of the 0.9 db file. // Restore this situation. KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Album Settings")); group.writeEntry(QLatin1String("Album Path"), newDir.path()); group.sync(); } else if (result == QMessageBox::No) { moveToBackup(digikam3DB); moveToBackup(digikamVeryOldDB); } else if (result == QMessageBox::Cancel) { QFileInfo oldFile(params.SQLiteDatabaseFile()); copyToNewLocation(oldFile, newFile, i18n("Failed to copy the old database file (\"%1\") " "to its new location (\"%2\"). " "Trying to upgrade old databases.", QDir::toNativeSeparators(oldFile.filePath()), QDir::toNativeSeparators(newFile.filePath()))); } } else { int result = QMessageBox::Yes; if (params.isSQLite()) { QMessageBox msgBox(QMessageBox::Warning, i18n("New database folder"), i18n("

You have chosen the folder \"%1\" as the new place to store the database.

" "

Would you like to copy the current database to this location " "and continue using it, or start with a new database?

", QDir::toNativeSeparators(newDir.path())), QMessageBox::Yes | QMessageBox::No, qApp->activeWindow()); msgBox.button(QMessageBox::Yes)->setText(i18n("Create New Database")); msgBox.button(QMessageBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("document-new"))); msgBox.button(QMessageBox::No)->setText(i18n("Copy Current Database")); msgBox.button(QMessageBox::No)->setIcon(QIcon::fromTheme(QLatin1String("edit-copy"))); msgBox.setDefaultButton(QMessageBox::Yes); result = msgBox.exec(); } if (result == QMessageBox::No) { QFileInfo oldFile(params.SQLiteDatabaseFile()); copyToNewLocation(oldFile, newFile); } } } else { int result = QMessageBox::No; if (params.isSQLite()) { QMessageBox msgBox(QMessageBox::Warning, i18n("New database folder"), i18n("

You have chosen the folder \"%1\" as the new place to store the database. " "There is already a database file in this location.

" "

Would you like to use this existing file as the new database, or remove it " "and copy the current database to this place?

", QDir::toNativeSeparators(newDir.path())), QMessageBox::Yes | QMessageBox::No, qApp->activeWindow()); msgBox.button(QMessageBox::Yes)->setText(i18n("Copy Current Database")); msgBox.button(QMessageBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("edit-copy"))); msgBox.button(QMessageBox::No)->setText(i18n("Use Existing File")); msgBox.button(QMessageBox::No)->setIcon(QIcon::fromTheme(QLatin1String("document-open"))); msgBox.setDefaultButton(QMessageBox::Yes); result = msgBox.exec(); } if (result == QMessageBox::Yes) { // first backup if (moveToBackup(newFile)) { QFileInfo oldFile(params.SQLiteDatabaseFile()); // then copy copyToNewLocation(oldFile, newFile); } } } } if (setDatabase(newParams, false)) { QApplication::setOverrideCursor(Qt::WaitCursor); startScan(); QApplication::restoreOverrideCursor(); ScanController::instance()->completeCollectionScan(); } } bool AlbumManager::setDatabase(const DbEngineParameters& params, bool priority, const QString& suggestedAlbumRoot) { // This is to ensure that the setup does not overrule the command line. // TODO: there is a bug that setup is showing something different here. if (priority) { d->hasPriorizedDbPath = true; } else if (d->hasPriorizedDbPath) { // ignore change without priority return true; } // shutdown possibly running collection scans. Must call resumeCollectionScan further down. ScanController::instance()->cancelAllAndSuspendCollectionScan(); QApplication::setOverrideCursor(Qt::WaitCursor); d->changed = true; disconnect(CollectionManager::instance(), 0, this, 0); CollectionManager::instance()->setWatchDisabled(); if (CoreDbAccess::databaseWatch()) { disconnect(CoreDbAccess::databaseWatch(), 0, this, 0); } DatabaseServerStarter::instance()->stopServerManagerProcess(); d->albumWatch->clear(); cleanUp(); d->currentAlbums.clear(); emit signalAlbumCurrentChanged(d->currentAlbums); emit signalAlbumsCleared(); d->albumPathHash.clear(); d->allAlbumsIdHash.clear(); d->albumRootAlbumHash.clear(); // deletes all child albums as well delete d->rootPAlbum; delete d->rootTAlbum; delete d->rootDAlbum; delete d->rootSAlbum; d->rootPAlbum = 0; d->rootTAlbum = 0; d->rootDAlbum = 0; d->rootSAlbum = 0; // -- Database initialization ------------------------------------------------- // ensure, embedded database is loaded qCDebug(DIGIKAM_GENERAL_LOG) << params; // workaround for the problem mariaDB >= 10.2 and QTBUG-63108 if (params.isMySQL()) { addFakeConnection(); } if (params.internalServer) { DatabaseServerError result = DatabaseServerStarter::instance()->startServerManagerProcess(params); if (result.getErrorType() != DatabaseServerError::NoErrors) { QWidget* const parent = QWidget::find(0); QString message = i18n("

An error occurred during the internal server start.

" "Details:\n %1", result.getErrorText()); QApplication::changeOverrideCursor(Qt::ArrowCursor); QMessageBox::critical(parent, qApp->applicationName(), message); QApplication::changeOverrideCursor(Qt::WaitCursor); } } CoreDbAccess::setParameters(params, CoreDbAccess::MainApplication); DbEngineGuiErrorHandler* const handler = new DbEngineGuiErrorHandler(CoreDbAccess::parameters()); CoreDbAccess::initDbEngineErrorHandler(handler); if (!handler->checkDatabaseConnection()) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), i18n("

Failed to open the database. " "

You cannot use digiKam without a working database. " "digiKam will attempt to start now, but it will not be functional. " "Please check the database settings in the configuration menu.

" )); CoreDbAccess::setParameters(DbEngineParameters(), CoreDbAccess::DatabaseSlave); QApplication::restoreOverrideCursor(); return true; } d->albumWatch->setDbEngineParameters(params); // still suspended from above ScanController::instance()->resumeCollectionScan(); ScanController::Advice advice = ScanController::instance()->databaseInitialization(); QApplication::restoreOverrideCursor(); switch (advice) { case ScanController::Success: break; case ScanController::ContinueWithoutDatabase: { QString errorMsg = CoreDbAccess().lastError(); if (errorMsg.isEmpty()) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), i18n("

Failed to open the database. " "

You cannot use digiKam without a working database. " "digiKam will attempt to start now, but it will not be functional. " "Please check the database settings in the configuration menu.

" )); } else { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), i18n("

Failed to open the database. Error message from database:

" "

%1

" "

You cannot use digiKam without a working database. " "digiKam will attempt to start now, but it will not be functional. " "Please check the database settings in the configuration menu.

", errorMsg)); } return true; } case ScanController::AbortImmediately: return false; } // -- Locale Checking --------------------------------------------------------- QString currLocale = QString::fromUtf8((QTextCodec::codecForLocale()->name())); QString dbLocale = CoreDbAccess().db()->getSetting(QLatin1String("Locale")); // guilty until proven innocent bool localeChanged = true; if (dbLocale.isNull()) { qCDebug(DIGIKAM_GENERAL_LOG) << "No locale found in database"; // Copy an existing locale from the settings file (used < 0.8) // to the database. KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("General Settings")); if (group.hasKey(QLatin1String("Locale"))) { qCDebug(DIGIKAM_GENERAL_LOG) << "Locale found in configfile"; dbLocale = group.readEntry(QLatin1String("Locale"), QString()); // this hack is necessary, as we used to store the entire // locale info LC_ALL (for eg: en_US.UTF-8) earlier, // we now save only the encoding (UTF-8) QString oldConfigLocale = QString::fromUtf8(::setlocale(0, 0)); if (oldConfigLocale == dbLocale) { dbLocale = currLocale; localeChanged = false; CoreDbAccess().db()->setSetting(QLatin1String("Locale"), dbLocale); } } else { qCDebug(DIGIKAM_GENERAL_LOG) << "No locale found in config file"; dbLocale = currLocale; localeChanged = false; CoreDbAccess().db()->setSetting(QLatin1String("Locale"), dbLocale); } } else { if (dbLocale == currLocale) { localeChanged = false; } } if (localeChanged) { // TODO it would be better to replace all yes/no confirmation dialogs with ones that has custom // buttons that denote the actions directly, i.e.: ["Ignore and Continue"] ["Adjust locale"] int result = QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), i18n("Your locale has changed since this " "album was last opened.\n" "Old locale: %1, new locale: %2\n" "If you have recently changed your locale, you need not be concerned.\n" "Please note that if you switched to a locale " "that does not support some of the filenames in your collection, " "these files may no longer be found in the collection. " "If you are sure that you want to " "continue, click 'Yes'. " "Otherwise, click 'No' and correct your " "locale setting before restarting digiKam.", dbLocale, currLocale), QMessageBox::Yes | QMessageBox::No); if (result != QMessageBox::Yes) { return false; } CoreDbAccess().db()->setSetting(QLatin1String("Locale"), currLocale); } // -- UUID Checking --------------------------------------------------------- QList disappearedLocations = CollectionManager::instance()->checkHardWiredLocations(); foreach(const CollectionLocation& loc, disappearedLocations) { QString locDescription; QStringList candidateIds, candidateDescriptions; CollectionManager::instance()->migrationCandidates(loc, &locDescription, &candidateIds, &candidateDescriptions); qCDebug(DIGIKAM_GENERAL_LOG) << "Migration candidates for" << locDescription << ":" << candidateIds << candidateDescriptions; QDialog* const dialog = new QDialog; QWidget* const widget = new QWidget(dialog); QGridLayout* const mainLayout = new QGridLayout; mainLayout->setColumnStretch(1, 1); QLabel* const deviceIconLabel = new QLabel; deviceIconLabel->setPixmap(QIcon::fromTheme(QLatin1String("drive-harddisk")).pixmap(64)); mainLayout->addWidget(deviceIconLabel, 0, 0); QLabel* const mainLabel = new QLabel(i18n("

The collection

%1
(%2)

is currently not found on your system.
" "Please choose the most appropriate option to handle this situation:

", loc.label(), QDir::toNativeSeparators(locDescription))); mainLabel->setWordWrap(true); mainLayout->addWidget(mainLabel, 0, 1); QGroupBox* const groupBox = new QGroupBox; mainLayout->addWidget(groupBox, 1, 0, 1, 2); QGridLayout* const layout = new QGridLayout; layout->setColumnStretch(1, 1); QRadioButton* migrateButton = 0; QComboBox* migrateChoices = 0; if (!candidateIds.isEmpty()) { migrateButton = new QRadioButton; QLabel* const migrateLabel = new QLabel(i18n("

The collection is still available, but the identifier changed.
" "This can be caused by restoring a backup, changing the partition layout " "or the file system settings.
" "The collection is now located at this place:

")); migrateLabel->setWordWrap(true); migrateChoices = new QComboBox; for (int i = 0 ; i < candidateIds.size() ; ++i) { migrateChoices->addItem(QDir::toNativeSeparators(candidateDescriptions.at(i)), candidateIds.at(i)); } layout->addWidget(migrateButton, 0, 0, Qt::AlignTop); layout->addWidget(migrateLabel, 0, 1); layout->addWidget(migrateChoices, 1, 1); } QRadioButton* const isRemovableButton = new QRadioButton; QLabel* const isRemovableLabel = new QLabel(i18n("The collection is located on a storage device which is not always attached. " "Mark the collection as a removable collection.")); isRemovableLabel->setWordWrap(true); layout->addWidget(isRemovableButton, 2, 0, Qt::AlignTop); layout->addWidget(isRemovableLabel, 2, 1); QRadioButton* const solveManuallyButton = new QRadioButton; QLabel* const solveManuallyLabel = new QLabel(i18n("Take no action now. I would like to solve the problem " "later using the setup dialog")); solveManuallyLabel->setWordWrap(true); layout->addWidget(solveManuallyButton, 3, 0, Qt::AlignTop); layout->addWidget(solveManuallyLabel, 3, 1); groupBox->setLayout(layout); widget->setLayout(mainLayout); QVBoxLayout* const vbx = new QVBoxLayout(dialog); QDialogButtonBox* const buttons = new QDialogButtonBox(QDialogButtonBox::Ok, dialog); vbx->addWidget(widget); vbx->addWidget(buttons); dialog->setLayout(vbx); dialog->setWindowTitle(i18n("Collection not found")); connect(buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), dialog, SLOT(accept())); // Default option: If there is only one candidate, default to migration. // Otherwise default to do nothing now. if (migrateButton && candidateIds.size() == 1) { migrateButton->setChecked(true); } else { solveManuallyButton->setChecked(true); } if (dialog->exec()) { if (migrateButton && migrateButton->isChecked()) { CollectionManager::instance()->migrateToVolume(loc, migrateChoices->itemData(migrateChoices->currentIndex()).toString()); } else if (isRemovableButton->isChecked()) { CollectionManager::instance()->changeType(loc, CollectionLocation::TypeVolumeRemovable); } } delete dialog; } // -- --------------------------------------------------------- // check that we have one album root if (CollectionManager::instance()->allLocations().isEmpty()) { if (suggestedAlbumRoot.isEmpty()) { Setup::execSinglePage(Setup::CollectionsPage); } else { QUrl albumRoot(QUrl::fromLocalFile(suggestedAlbumRoot)); CollectionManager::instance()->addLocation(albumRoot, albumRoot.fileName()); // Not needed? See bug #188959 //ScanController::instance()->completeCollectionScan(); } } // -- --------------------------------------------------------- QApplication::setOverrideCursor(Qt::WaitCursor); ThumbnailLoadThread::initializeThumbnailDatabase(CoreDbAccess::parameters().thumbnailParameters(), new ThumbsDbInfoProvider()); DbEngineGuiErrorHandler* const thumbnailsDBHandler = new DbEngineGuiErrorHandler(ThumbsDbAccess::parameters()); ThumbsDbAccess::initDbEngineErrorHandler(thumbnailsDBHandler); // Activate the similarity database. SimilarityDbAccess::setParameters(params.similarityParameters()); DbEngineGuiErrorHandler* const similarityHandler = new DbEngineGuiErrorHandler(SimilarityDbAccess::parameters()); SimilarityDbAccess::initDbEngineErrorHandler(similarityHandler); if (SimilarityDbAccess::checkReadyForUse(0)) { qCDebug(DIGIKAM_SIMILARITYDB_LOG) << "Similarity database ready for use"; } else { qCDebug(DIGIKAM_SIMILARITYDB_LOG) << "Failed to initialize similarity database"; } QApplication::restoreOverrideCursor(); return true; } void AlbumManager::startScan() { if (!d->changed) { return; } d->changed = false; // create root albums d->rootPAlbum = new PAlbum(i18n("Albums")); insertPAlbum(d->rootPAlbum, 0); d->rootTAlbum = new TAlbum(i18n("Tags"), 0, true); insertTAlbum(d->rootTAlbum, 0); d->rootSAlbum = new SAlbum(i18n("Searches"), 0, true); emit signalAlbumAboutToBeAdded(d->rootSAlbum, 0, 0); d->allAlbumsIdHash[d->rootSAlbum->globalID()] = d->rootSAlbum; emit signalAlbumAdded(d->rootSAlbum); d->rootDAlbum = new DAlbum(QDate(), true); emit signalAlbumAboutToBeAdded(d->rootDAlbum, 0, 0); d->allAlbumsIdHash[d->rootDAlbum->globalID()] = d->rootDAlbum; emit signalAlbumAdded(d->rootDAlbum); // Create albums for album roots. Reuse logic implemented in the method foreach(const CollectionLocation& location, CollectionManager::instance()->allLocations()) { handleCollectionStatusChange(location, CollectionLocation::LocationNull); } // listen to location status changes connect(CollectionManager::instance(), SIGNAL(locationStatusChanged(CollectionLocation,int)), this, SLOT(slotCollectionLocationStatusChanged(CollectionLocation,int))); connect(CollectionManager::instance(), SIGNAL(locationPropertiesChanged(CollectionLocation)), this, SLOT(slotCollectionLocationPropertiesChanged(CollectionLocation))); // reload albums refresh(); // listen to album database changes connect(CoreDbAccess::databaseWatch(), SIGNAL(albumChange(AlbumChangeset)), this, SLOT(slotAlbumChange(AlbumChangeset))); connect(CoreDbAccess::databaseWatch(), SIGNAL(tagChange(TagChangeset)), this, SLOT(slotTagChange(TagChangeset))); connect(CoreDbAccess::databaseWatch(), SIGNAL(searchChange(SearchChangeset)), this, SLOT(slotSearchChange(SearchChangeset))); // listen to collection image changes connect(CoreDbAccess::databaseWatch(), SIGNAL(collectionImageChange(CollectionImageChangeset)), this, SLOT(slotCollectionImageChange(CollectionImageChangeset))); connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)), this, SLOT(slotImageTagChange(ImageTagChangeset))); // listen to image attribute changes connect(ImageAttributesWatch::instance(), SIGNAL(signalImageDateChanged(qlonglong)), d->scanDAlbumsTimer, SLOT(start())); emit signalAllAlbumsLoaded(); } void AlbumManager::slotCollectionLocationStatusChanged(const CollectionLocation& location, int oldStatus) { // not before initialization if (!d->rootPAlbum) { return; } if (handleCollectionStatusChange(location, oldStatus)) { // a change occurred. Possibly albums have appeared or disappeared scanPAlbums(); } } /// Returns true if it added or removed an album bool AlbumManager::handleCollectionStatusChange(const CollectionLocation& location, int oldStatus) { enum Action { Add, Remove, DoNothing }; Action action = DoNothing; switch (oldStatus) { case CollectionLocation::LocationNull: case CollectionLocation::LocationHidden: case CollectionLocation::LocationUnavailable: { switch (location.status()) { case CollectionLocation::LocationNull: // not possible break; case CollectionLocation::LocationHidden: action = Remove; break; case CollectionLocation::LocationAvailable: action = Add; break; case CollectionLocation::LocationUnavailable: if (d->showOnlyAvailableAlbums) { action = Remove; } else { action = Add; } break; case CollectionLocation::LocationDeleted: action = Remove; break; } break; } case CollectionLocation::LocationAvailable: { switch (location.status()) { case CollectionLocation::LocationNull: case CollectionLocation::LocationHidden: case CollectionLocation::LocationDeleted: action = Remove; break; case CollectionLocation::LocationUnavailable: if (d->showOnlyAvailableAlbums) { action = Remove; } break; case CollectionLocation::LocationAvailable: // not possible break; } break; } case CollectionLocation::LocationDeleted: // not possible break; } if (action == Add && !d->albumRootAlbumHash.value(location.id())) { // This is the only place where album root albums are added addAlbumRoot(location); return true; } else if (action == Remove && d->albumRootAlbumHash.value(location.id())) { removeAlbumRoot(location); return true; } return false; } void AlbumManager::slotCollectionLocationPropertiesChanged(const CollectionLocation& location) { PAlbum* const album = d->albumRootAlbumHash.value(location.id()); if (album) { QString newLabel = d->labelForAlbumRootAlbum(location); if (album->title() != newLabel) { album->setTitle(newLabel); emit signalAlbumRenamed(album); } } } void AlbumManager::addAlbumRoot(const CollectionLocation& location) { PAlbum* album = d->albumRootAlbumHash.value(location.id()); if (!album) { // Create a PAlbum for the Album Root. QString label = d->labelForAlbumRootAlbum(location); album = new PAlbum(location.id(), label); qCDebug(DIGIKAM_GENERAL_LOG) << "Added root album called: " << album->title(); // insert album root created into hash d->albumRootAlbumHash.insert(location.id(), album); } } void AlbumManager::removeAlbumRoot(const CollectionLocation& location) { // retrieve and remove from hash PAlbum* const album = d->albumRootAlbumHash.take(location.id()); if (album) { // delete album and all its children removePAlbum(album); } } bool AlbumManager::isShowingOnlyAvailableAlbums() const { return d->showOnlyAvailableAlbums; } void AlbumManager::setShowOnlyAvailableAlbums(bool onlyAvailable) { if (d->showOnlyAvailableAlbums == onlyAvailable) { return; } d->showOnlyAvailableAlbums = onlyAvailable; emit signalShowOnlyAvailableAlbumsChanged(d->showOnlyAvailableAlbums); // We need to update the unavailable locations. // We assume the handleCollectionStatusChange does the right thing (even though old status == current status) foreach (const CollectionLocation& location, CollectionManager::instance()->allLocations()) { if (location.status() == CollectionLocation::LocationUnavailable) { handleCollectionStatusChange(location, CollectionLocation::LocationUnavailable); } } } void AlbumManager::refresh() { scanPAlbums(); scanTAlbums(); scanSAlbums(); scanDAlbums(); } void AlbumManager::prepareItemCounts() { // There is no way to find out if any data we had collected // previously is still valid - recompute scanDAlbums(); getAlbumItemsCount(); getTagItemsCount(); } void AlbumManager::scanPAlbums() { d->scanPAlbumsTimer->stop(); // first insert all the current normal PAlbums into a map for quick lookup QHash oldAlbums; AlbumIterator it(d->rootPAlbum); while (it.current()) { PAlbum* const a = (PAlbum*)(*it); oldAlbums[a->id()] = a; ++it; } // scan db and get a list of all albums QList currentAlbums = CoreDbAccess().db()->scanAlbums(); // sort by relative path so that parents are created before children std::sort(currentAlbums.begin(), currentAlbums.end()); QList newAlbums; // go through all the Albums and see which ones are already present foreach(const AlbumInfo& info, currentAlbums) { // check that location of album is available if (d->showOnlyAvailableAlbums && !CollectionManager::instance()->locationForAlbumRootId(info.albumRootId).isAvailable()) { continue; } if (oldAlbums.contains(info.id)) { oldAlbums.remove(info.id); } else { newAlbums << info; } } // now oldAlbums contains all the deleted albums and // newAlbums contains all the new albums // delete old albums, informing all frontends // The albums have to be removed with children being removed first, // removePAlbum takes care of that. // So we only feed it the albums from oldAlbums topmost in hierarchy. QSet topMostOldAlbums; foreach(PAlbum* const album, oldAlbums) { if (album->isTrashAlbum()) { continue; } if (!album->parent() || !oldAlbums.contains(album->parent()->id())) { topMostOldAlbums << album; } } foreach(PAlbum* const album, topMostOldAlbums) { // recursively removes all children and the album removePAlbum(album); } // sort by relative path so that parents are created before children std::sort(newAlbums.begin(), newAlbums.end()); // create all new albums foreach(const AlbumInfo& info, newAlbums) { if (info.relativePath.isEmpty()) { continue; } PAlbum* album = 0, *parent = 0; if (info.relativePath == QLatin1String("/")) { // Albums that represent the root directory of an album root // We have them as here new albums first time after their creation parent = d->rootPAlbum; album = d->albumRootAlbumHash.value(info.albumRootId); if (!album) { qCDebug(DIGIKAM_GENERAL_LOG) << "Did not find album root album in hash"; continue; } // it has been created from the collection location // with album root id, parentPath "/" and a name, but no album id yet. album->m_id = info.id; } else { // last section, no slash QString name = info.relativePath.section(QLatin1Char('/'), -1, -1); // all but last sections, leading slash, no trailing slash QString parentPath = info.relativePath.section(QLatin1Char('/'), 0, -2); if (parentPath.isEmpty()) { parent = d->albumRootAlbumHash.value(info.albumRootId); } else { parent = d->albumPathHash.value(PAlbumPath(info.albumRootId, parentPath)); } if (!parent) { qCDebug(DIGIKAM_GENERAL_LOG) << "Could not find parent with url: " << parentPath << " for: " << info.relativePath; continue; } // Create the new album album = new PAlbum(info.albumRootId, parentPath, name, info.id); } album->m_caption = info.caption; album->m_category = info.category; album->m_date = info.date; album->m_iconId = info.iconId; insertPAlbum(album, parent); if (album->isAlbumRoot()) { // Inserting virtual Trash PAlbum for AlbumsRootAlbum using special constructor PAlbum* trashAlbum = new PAlbum(album->title(), album->id()); insertPAlbum(trashAlbum, album); } } if (!topMostOldAlbums.isEmpty() || !newAlbums.isEmpty()) { emit signalAlbumsUpdated(Album::PHYSICAL); } getAlbumItemsCount(); } void AlbumManager::updateChangedPAlbums() { d->updatePAlbumsTimer->stop(); // scan db and get a list of all albums QList currentAlbums = CoreDbAccess().db()->scanAlbums(); bool needScanPAlbums = false; // Find the AlbumInfo for each id in changedPAlbums foreach(int id, d->changedPAlbums) { foreach(const AlbumInfo& info, currentAlbums) { if (info.id == id) { d->changedPAlbums.remove(info.id); PAlbum* album = findPAlbum(info.id); if (album) { // Renamed? if (info.relativePath != QLatin1String("/")) { // Handle rename of album name // last section, no slash QString name = info.relativePath.section(QLatin1Char('/'), -1, -1); QString parentPath = info.relativePath; parentPath.chop(name.length()); if (parentPath != album->m_parentPath || info.albumRootId != album->albumRootId()) { // Handle actual move operations: trigger ScanPAlbums needScanPAlbums = true; removePAlbum(album); break; } else if (name != album->title()) { album->setTitle(name); updateAlbumPathHash(); emit signalAlbumRenamed(album); } } // Update caption, collection, date album->m_caption = info.caption; album->m_category = info.category; album->m_date = info.date; // Icon changed? if (album->m_iconId != info.iconId) { album->m_iconId = info.iconId; emit signalAlbumIconChanged(album); } } } } } if (needScanPAlbums) { scanPAlbums(); } } void AlbumManager::getAlbumItemsCount() { d->albumItemCountTimer->stop(); if (!ApplicationSettings::instance()->getShowFolderTreeViewItemsCount()) { return; } if (d->albumListJob) { d->albumListJob->cancel(); d->albumListJob = 0; } AlbumsDBJobInfo jInfo; jInfo.setFoldersJob(); d->albumListJob = DBJobsManager::instance()->startAlbumsJobThread(jInfo); connect(d->albumListJob, SIGNAL(finished()), this, SLOT(slotAlbumsJobResult())); connect(d->albumListJob, SIGNAL(foldersData(QMap)), this, SLOT(slotAlbumsJobData(QMap))); } void AlbumManager::scanTAlbums() { d->scanTAlbumsTimer->stop(); // list TAlbums directly from the db // first insert all the current TAlbums into a map for quick lookup typedef QMap TagMap; TagMap tmap; tmap.insert(0, d->rootTAlbum); AlbumIterator it(d->rootTAlbum); while (it.current()) { TAlbum* t = (TAlbum*)(*it); tmap.insert(t->id(), t); ++it; } // Retrieve the list of tags from the database TagInfo::List tList = CoreDbAccess().db()->scanTags(); // sort the list. needed because we want the tags can be read in any order, // but we want to make sure that we are ensure to find the parent TAlbum // for a new TAlbum { QHash tagHash; // insert items into a dict for quick lookup for (TagInfo::List::const_iterator iter = tList.constBegin() ; iter != tList.constEnd() ; ++iter) { TagInfo info = *iter; TAlbum* const album = new TAlbum(info.name, info.id); album->m_icon = info.icon; album->m_iconId = info.iconId; album->m_pid = info.pid; tagHash.insert(info.id, album); } tList.clear(); // also add root tag TAlbum* const rootTag = new TAlbum(QLatin1String("root"), 0, true); tagHash.insert(0, rootTag); // build tree for (QHash::const_iterator iter = tagHash.constBegin() ; iter != tagHash.constEnd() ; ++iter) { TAlbum* album = *iter; if (album->m_id == 0) { continue; } TAlbum* const parent = tagHash.value(album->m_pid); if (parent) { album->setParent(parent); } else { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to find parent tag for tag " << album->m_title << " with pid " << album->m_pid; } } tagHash.clear(); // now insert the items into the list. becomes sorted AlbumIterator it(rootTag); while (it.current()) { TagInfo info; TAlbum* const album = static_cast(it.current()); if (album) { info.id = album->m_id; info.pid = album->m_pid; info.name = album->m_title; info.icon = album->m_icon; info.iconId = album->m_iconId; } tList.append(info); ++it; } // this will also delete all child albums delete rootTag; } for (TagInfo::List::const_iterator it = tList.constBegin() ; it != tList.constEnd() ; ++it) { TagInfo info = *it; // check if we have already added this tag if (tmap.contains(info.id)) { continue; } // Its a new album. Find the parent of the album TagMap::const_iterator iter = tmap.constFind(info.pid); if (iter == tmap.constEnd()) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to find parent tag for tag " << info.name << " with pid " << info.pid; continue; } TAlbum* const parent = iter.value(); // Create the new TAlbum TAlbum* const album = new TAlbum(info.name, info.id, false); album->m_icon = info.icon; album->m_iconId = info.iconId; insertTAlbum(album, parent); // also insert it in the map we are doing lookup of parent tags tmap.insert(info.id, album); } if (!tList.isEmpty()) { emit signalAlbumsUpdated(Album::TAG); } getTagItemsCount(); } void AlbumManager::getTagItemsCount() { d->tagItemCountTimer->stop(); if (!ApplicationSettings::instance()->getShowFolderTreeViewItemsCount()) { return; } tagItemsCount(); personItemsCount(); } void AlbumManager::tagItemsCount() { if (d->tagListJob) { d->tagListJob->cancel(); d->tagListJob = 0; } TagsDBJobInfo jInfo; jInfo.setFoldersJob(); d->tagListJob = DBJobsManager::instance()->startTagsJobThread(jInfo); connect(d->tagListJob, SIGNAL(finished()), this, SLOT(slotTagsJobResult())); connect(d->tagListJob, SIGNAL(foldersData(QMap)), this, SLOT(slotTagsJobData(QMap))); } void AlbumManager::personItemsCount() { if (d->personListJob) { d->personListJob->cancel(); d->personListJob = 0; } TagsDBJobInfo jInfo; jInfo.setFaceFoldersJob(); d->personListJob = DBJobsManager::instance()->startTagsJobThread(jInfo); connect(d->personListJob, SIGNAL(finished()), this, SLOT(slotPeopleJobResult())); connect(d->personListJob, SIGNAL(faceFoldersData(QMap >)), this, SLOT(slotPeopleJobData(QMap >))); } void AlbumManager::scanSAlbums() { d->scanSAlbumsTimer->stop(); // list SAlbums directly from the db // first insert all the current SAlbums into a map for quick lookup QMap oldSearches; AlbumIterator it(d->rootSAlbum); while (it.current()) { SAlbum* const search = (SAlbum*)(*it); oldSearches[search->id()] = search; ++it; } // scan db and get a list of all albums QList currentSearches = CoreDbAccess().db()->scanSearches(); QList newSearches; // go through all the Albums and see which ones are already present foreach(const SearchInfo& info, currentSearches) { if (oldSearches.contains(info.id)) { SAlbum* const album = oldSearches[info.id]; if (info.name != album->title() || info.type != album->searchType() || info.query != album->query()) { QString oldName = album->title(); album->setSearch(info.type, info.query); album->setTitle(info.name); if (oldName != album->title()) { emit signalAlbumRenamed(album); } emit signalSearchUpdated(album); } oldSearches.remove(info.id); } else { newSearches << info; } } // remove old albums that have been deleted foreach(SAlbum* const album, oldSearches) { emit signalAlbumAboutToBeDeleted(album); d->allAlbumsIdHash.remove(album->globalID()); emit signalAlbumDeleted(album); quintptr deletedAlbum = reinterpret_cast(album); delete album; emit signalAlbumHasBeenDeleted(deletedAlbum); } // add new albums foreach(const SearchInfo& info, newSearches) { SAlbum* const album = new SAlbum(info.name, info.id); album->setSearch(info.type, info.query); emit signalAlbumAboutToBeAdded(album, d->rootSAlbum, d->rootSAlbum->lastChild()); album->setParent(d->rootSAlbum); d->allAlbumsIdHash[album->globalID()] = album; emit signalAlbumAdded(album); } } void AlbumManager::scanDAlbumsScheduled() { // Avoid a cycle of killing a job which takes longer than the timer interval if (d->dateListJob) { d->scanDAlbumsTimer->start(); return; } scanDAlbums(); } void AlbumManager::scanDAlbums() { d->scanDAlbumsTimer->stop(); if (d->dateListJob) { d->dateListJob->cancel(); d->dateListJob = 0; } DatesDBJobInfo jInfo; jInfo.setFoldersJob(); d->dateListJob = DBJobsManager::instance()->startDatesJobThread(jInfo); connect(d->dateListJob, SIGNAL(finished()), this, SLOT(slotDatesJobResult())); connect(d->dateListJob, SIGNAL(foldersData(QMap)), this, SLOT(slotDatesJobData(QMap))); } AlbumList AlbumManager::allPAlbums() const { AlbumList list; if (d->rootPAlbum) { list.append(d->rootPAlbum); } AlbumIterator it(d->rootPAlbum); while (it.current()) { list.append(*it); ++it; } return list; } AlbumList AlbumManager::allTAlbums() const { AlbumList list; if (d->rootTAlbum) { list.append(d->rootTAlbum); } AlbumIterator it(d->rootTAlbum); while (it.current()) { list.append(*it); ++it; } return list; } AlbumList AlbumManager::allSAlbums() const { AlbumList list; if (d->rootSAlbum) { list.append(d->rootSAlbum); } AlbumIterator it(d->rootSAlbum); while (it.current()) { list.append(*it); ++it; } return list; } AlbumList AlbumManager::allDAlbums() const { AlbumList list; if (d->rootDAlbum) { list.append(d->rootDAlbum); } AlbumIterator it(d->rootDAlbum); while (it.current()) { list.append(*it); ++it; } return list; } void AlbumManager::setCurrentAlbums(QList albums) { if (albums.isEmpty()) return; QList filtered; /** * Filter out the null pointers */ Q_FOREACH(Album* const album, albums) { if (album != 0) { filtered.append(album); } } albums = filtered; /** * Sort is needed to identify selection correctly, ex AlbumHistory */ std::sort(albums.begin(), albums.end()); d->currentAlbums.clear(); d->currentAlbums+=albums; emit signalAlbumCurrentChanged(d->currentAlbums); } AlbumList AlbumManager::currentAlbums() const { return d->currentAlbums; } PAlbum* AlbumManager::currentPAlbum() const { /** * Temporary fix, to return multiple items, * iterate and cast each element */ if (!d->currentAlbums.isEmpty()) return dynamic_cast(d->currentAlbums.first()); else return 0; } QList AlbumManager::currentTAlbums() const { /** * This method is not yet used */ QList talbums; QList::iterator it; for (it = d->currentAlbums.begin() ; it != d->currentAlbums.end() ; ++it) { TAlbum* const temp = dynamic_cast(*it); if (temp) talbums.push_back(temp); } return talbums; } PAlbum* AlbumManager::findPAlbum(const QUrl& url) const { CollectionLocation location = CollectionManager::instance()->locationForUrl(url); if (location.isNull()) { return 0; } return d->albumPathHash.value(PAlbumPath(location.id(), CollectionManager::instance()->album(location, url))); } PAlbum* AlbumManager::findPAlbum(int id) const { if (!d->rootPAlbum) { return 0; } int gid = d->rootPAlbum->globalID() + id; return static_cast((d->allAlbumsIdHash.value(gid))); } TAlbum* AlbumManager::findTAlbum(int id) const { if (!d->rootTAlbum) { return 0; } int gid = d->rootTAlbum->globalID() + id; return static_cast((d->allAlbumsIdHash.value(gid))); } SAlbum* AlbumManager::findSAlbum(int id) const { if (!d->rootSAlbum) { return 0; } int gid = d->rootSAlbum->globalID() + id; return static_cast((d->allAlbumsIdHash.value(gid))); } DAlbum* AlbumManager::findDAlbum(int id) const { if (!d->rootDAlbum) { return 0; } int gid = d->rootDAlbum->globalID() + id; return static_cast((d->allAlbumsIdHash.value(gid))); } Album* AlbumManager::findAlbum(int gid) const { return d->allAlbumsIdHash.value(gid); } Album* AlbumManager::findAlbum(Album::Type type, int id) const { return findAlbum(Album::globalID(type, id)); } TAlbum* AlbumManager::findTAlbum(const QString& tagPath) const { // handle gracefully with or without leading slash bool withLeadingSlash = tagPath.startsWith(QLatin1Char('/')); AlbumIterator it(d->rootTAlbum); while (it.current()) { TAlbum* const talbum = static_cast(*it); if (talbum->tagPath(withLeadingSlash) == tagPath) { return talbum; } ++it; } return 0; } SAlbum* AlbumManager::findSAlbum(const QString& name) const { for (Album* album = d->rootSAlbum->firstChild() ; album ; album = album->next()) { if (album->title() == name) { return dynamic_cast(album); } } return 0; } QList AlbumManager::findSAlbumsBySearchType(int searchType) const { QList albums; for (Album* album = d->rootSAlbum->firstChild() ; album ; album = album->next()) { if (album != 0) { SAlbum* sAlbum = dynamic_cast(album); if ((sAlbum != 0) && (sAlbum->searchType() == searchType)) { albums.append(sAlbum); } } } return albums; } void AlbumManager::addGuardedPointer(Album* album, Album** pointer) { if (album) { d->guardedPointers.insert(album, pointer); } } void AlbumManager::removeGuardedPointer(Album* album, Album** pointer) { if (album) { d->guardedPointers.remove(album, pointer); } } void AlbumManager::changeGuardedPointer(Album* oldAlbum, Album* album, Album** pointer) { if (oldAlbum) { d->guardedPointers.remove(oldAlbum, pointer); } if (album) { d->guardedPointers.insert(album, pointer); } } void AlbumManager::invalidateGuardedPointers(Album* album) { if (!album) { return; } QMultiHash::iterator it = d->guardedPointers.find(album); for (; it != d->guardedPointers.end() && it.key() == album; ++it) { if (it.value()) { *(it.value()) = 0; } } } PAlbum* AlbumManager::createPAlbum(const QString& albumRootPath, const QString& name, const QString& caption, const QDate& date, const QString& category, QString& errMsg) { CollectionLocation location = CollectionManager::instance()->locationForAlbumRootPath(albumRootPath); return createPAlbum(location, name, caption, date, category, errMsg); } PAlbum* AlbumManager::createPAlbum(const CollectionLocation& location, const QString& name, const QString& caption, const QDate& date, const QString& category, QString& errMsg) { if (location.isNull() || !location.isAvailable()) { errMsg = i18n("The collection location supplied is invalid or currently not available."); return 0; } PAlbum* album = d->albumRootAlbumHash.value(location.id()); if (!album) { errMsg = i18n("No album for collection location: Internal error"); return 0; } return createPAlbum(album, name, caption, date, category, errMsg); } PAlbum* AlbumManager::createPAlbum(PAlbum* parent, const QString& name, const QString& caption, const QDate& date, const QString& category, QString& errMsg) { if (!parent) { errMsg = i18n("No parent found for album."); return 0; } // sanity checks if (name.isEmpty()) { errMsg = i18n("Album name cannot be empty."); return 0; } if (name.contains(QLatin1String("/"))) { errMsg = i18n("Album name cannot contain '/'."); return 0; } if (parent->isRoot()) { errMsg = i18n("createPAlbum does not accept the root album as parent."); return 0; } QString albumPath = parent->isAlbumRoot() ? QString(QLatin1Char('/') + name) : QString(parent->albumPath() + QLatin1Char('/') + name); int albumRootId = parent->albumRootId(); // first check if we have a sibling album with the same name PAlbum* child = static_cast(parent->m_firstChild); while (child) { if (child->albumRootId() == albumRootId && child->albumPath() == albumPath) { errMsg = i18n("An existing album has the same name."); return 0; } child = static_cast(child->m_next); } CoreDbUrl url = parent->databaseUrl(); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + name); QUrl fileUrl = url.fileUrl(); bool ret = QDir().mkdir(fileUrl.toLocalFile()); if (!ret) { errMsg = i18n("Failed to create directory '%1'", fileUrl.toString()); // TODO add tags? return 0; } ChangingDB changing(d); int id = CoreDbAccess().db()->addAlbum(albumRootId, albumPath, caption, date, category); if (id == -1) { errMsg = i18n("Failed to add album to database"); return 0; } QString parentPath; if (!parent->isAlbumRoot()) { parentPath = parent->albumPath(); } PAlbum* const album = new PAlbum(albumRootId, parentPath, name, id); album->m_caption = caption; album->m_category = category; album->m_date = date; insertPAlbum(album, parent); emit signalAlbumsUpdated(Album::PHYSICAL); return album; } bool AlbumManager::renamePAlbum(PAlbum* album, const QString& newName, QString& errMsg) { if (!album) { errMsg = i18n("No such album"); return false; } if (album == d->rootPAlbum) { errMsg = i18n("Cannot rename root album"); return false; } if (album->isAlbumRoot()) { errMsg = i18n("Cannot rename album root album"); return false; } if (newName.contains(QLatin1String("/"))) { errMsg = i18n("Album name cannot contain '/'"); return false; } // first check if we have another sibling with the same name if (hasDirectChildAlbumWithTitle(album->m_parent, newName)) { errMsg = i18n("Another album with the same name already exists.\n" "Please choose another name."); return false; } d->albumWatch->removeWatchedPAlbums(album); QString oldAlbumPath = album->albumPath(); QUrl oldUrl = album->fileUrl(); album->setTitle(newName); album->m_path = newName; QUrl newUrl = album->fileUrl(); QString newAlbumPath = album->albumPath(); // We use a private shortcut around collection scanner noticing our changes, // we rename them directly. Faster. ScanController::instance()->suspendCollectionScan(); bool ret = QDir().rename(oldUrl.toLocalFile(), newUrl.toLocalFile()); if (!ret) { ScanController::instance()->resumeCollectionScan(); errMsg = i18n("Failed to rename Album"); return false; } // now rename the album and subalbums in the database { CoreDbAccess access; ChangingDB changing(d); access.db()->renameAlbum(album->id(), album->albumRootId(), album->albumPath()); PAlbum* subAlbum = 0; AlbumIterator it(album); while ((subAlbum = static_cast(it.current())) != 0) { subAlbum->m_parentPath = newAlbumPath + subAlbum->m_parentPath.mid(oldAlbumPath.length()); access.db()->renameAlbum(subAlbum->id(), album->albumRootId(), subAlbum->albumPath()); emit signalAlbumNewPath(subAlbum); ++it; } } updateAlbumPathHash(); emit signalAlbumRenamed(album); ScanController::instance()->resumeCollectionScan(); return true; } void AlbumManager::updateAlbumPathHash() { // Update AlbumDict. basically clear it and rebuild from scratch d->albumPathHash.clear(); AlbumIterator it(d->rootPAlbum); PAlbum* subAlbum = 0; while ((subAlbum = static_cast(it.current())) != 0) { d->albumPathHash[subAlbum] = subAlbum; ++it; } } bool AlbumManager::updatePAlbumIcon(PAlbum* album, qlonglong iconID, QString& errMsg) { if (!album) { errMsg = i18n("No such album"); return false; } if (album == d->rootPAlbum) { errMsg = i18n("Cannot edit root album"); return false; } { CoreDbAccess access; ChangingDB changing(d); access.db()->setAlbumIcon(album->id(), iconID); album->m_iconId = iconID; } emit signalAlbumIconChanged(album); return true; } qlonglong AlbumManager::getItemFromAlbum(PAlbum* album, const QString& fileName) { return CoreDbAccess().db()->getItemFromAlbum(album->id(),fileName); } TAlbum* AlbumManager::createTAlbum(TAlbum* parent, const QString& name, const QString& iconkde, QString& errMsg) { if (!parent) { errMsg = i18n("No parent found for tag"); return 0; } // sanity checks if (name.isEmpty()) { errMsg = i18n("Tag name cannot be empty"); return 0; } if (name.contains(QLatin1String("/"))) { errMsg = i18n("Tag name cannot contain '/'"); return 0; } // first check if we have another album with the same name if (hasDirectChildAlbumWithTitle(parent, name)) { errMsg = i18n("Tag name already exists"); return 0; } ChangingDB changing(d); int id = CoreDbAccess().db()->addTag(parent->id(), name, iconkde, 0); if (id == -1) { errMsg = i18n("Failed to add tag to database"); return 0; } TAlbum* const album = new TAlbum(name, id, false); album->m_icon = iconkde; insertTAlbum(album, parent); TAlbum* personParentTag = findTAlbum(FaceTags::personParentTag()); if (personParentTag && personParentTag->isAncestorOf(album)) { FaceTags::ensureIsPerson(album->id()); } emit signalAlbumsUpdated(Album::TAG); return album; } AlbumList AlbumManager::findOrCreateTAlbums(const QStringList& tagPaths) { // find tag ids for tag paths in list, create if they don't exist QList tagIDs = TagsCache::instance()->getOrCreateTags(tagPaths); // create TAlbum objects for the newly created tags scanTAlbums(); AlbumList resultList; for (QList::const_iterator it = tagIDs.constBegin() ; it != tagIDs.constEnd() ; ++it) { resultList.append(findTAlbum(*it)); } return resultList; } bool AlbumManager::deleteTAlbum(TAlbum* album, QString& errMsg) { if (!album) { errMsg = i18n("No such album"); return false; } if (album == d->rootTAlbum) { errMsg = i18n("Cannot delete Root Tag"); return false; } { CoreDbAccess access; ChangingDB changing(d); access.db()->deleteTag(album->id()); Album* subAlbum = 0; AlbumIterator it(album); while ((subAlbum = it.current()) != 0) { access.db()->deleteTag(subAlbum->id()); ++it; } } removeTAlbum(album); emit signalAlbumsUpdated(Album::TAG); return true; } bool AlbumManager::hasDirectChildAlbumWithTitle(Album* parent, const QString& title) { Album* sibling = parent->m_firstChild; while (sibling) { if (sibling->title() == title) { return true; } sibling = sibling->m_next; } return false; } bool AlbumManager::renameTAlbum(TAlbum* album, const QString& name, QString& errMsg) { if (!album) { errMsg = i18n("No such album"); return false; } if (album == d->rootTAlbum) { errMsg = i18n("Cannot edit root tag"); return false; } if (name.contains(QLatin1String("/"))) { errMsg = i18n("Tag name cannot contain '/'"); return false; } // first check if we have another sibling with the same name if (hasDirectChildAlbumWithTitle(album->m_parent, name)) { errMsg = i18n("Another tag with the same name already exists.\n" "Please choose another name."); return false; } ChangingDB changing(d); CoreDbAccess().db()->setTagName(album->id(), name); album->setTitle(name); emit signalAlbumRenamed(album); return true; } bool AlbumManager::moveTAlbum(TAlbum* album, TAlbum* newParent, QString& errMsg) { if (!album) { errMsg = i18n("No such album"); return false; } if (!newParent) { errMsg = i18n("Attempt to move TAlbum to nowhere"); return false; } if (album == d->rootTAlbum) { errMsg = i18n("Cannot move root tag"); return false; } if (hasDirectChildAlbumWithTitle(newParent, album->title())) { QMessageBox msgBox(QMessageBox::Warning, qApp->applicationName(), i18n("Another tag with the same name already exists.\n" "Do you want to merge the tags?"), QMessageBox::Yes | QMessageBox::No, qApp->activeWindow()); if (msgBox.exec() == QMessageBox::Yes) { + if (album->m_firstChild) + { + errMsg = i18n("Only a tag without children can be merged!"); + return false; + } + TAlbum* const mergeTag = findTAlbum(newParent->tagPath() + QLatin1Char('/') + album->title()); if (!mergeTag) { errMsg = i18n("No such album"); return false; } int oldId = album->id(); int mergeId = mergeTag->id(); QApplication::setOverrideCursor(Qt::WaitCursor); QList imageIds = CoreDbAccess().db()->getItemIDsInTag(oldId); CoreDbOperationGroup group; group.setMaximumTime(200); foreach(qlonglong imageId, imageIds) { QList facesList = FaceTagsEditor().databaseFaces(imageId); bool foundFace = false; foreach(const FaceTagsIface& face, facesList) { if (face.tagId() == oldId) { foundFace = true; FaceTagsEditor().removeFace(face); FaceTagsEditor().add(imageId, mergeId, face.region(), false); } } if (!foundFace) { ImageInfo info(imageId); info.removeTag(oldId); info.setTag(mergeId); group.allowLift(); } } QApplication::restoreOverrideCursor(); return deleteTAlbum(album, errMsg); } else { return true; } } d->currentlyMovingAlbum = album; emit signalAlbumAboutToBeMoved(album); emit signalAlbumAboutToBeDeleted(album); if (album->parent()) { album->parent()->removeChild(album); } album->setParent(0); emit signalAlbumDeleted(album); emit signalAlbumHasBeenDeleted(reinterpret_cast(album)); emit signalAlbumAboutToBeAdded(album, newParent, newParent->lastChild()); ChangingDB changing(d); CoreDbAccess().db()->setTagParentID(album->id(), newParent->id()); album->setParent(newParent); emit signalAlbumAdded(album); emit signalAlbumMoved(album); emit signalAlbumsUpdated(Album::TAG); d->currentlyMovingAlbum = 0; TAlbum* personParentTag = findTAlbum(FaceTags::personParentTag()); if (personParentTag && personParentTag->isAncestorOf(album)) { FaceTags::ensureIsPerson(album->id()); } return true; } bool AlbumManager::updateTAlbumIcon(TAlbum* album, const QString& iconKDE, qlonglong iconID, QString& errMsg) { if (!album) { errMsg = i18n("No such tag"); return false; } if (album == d->rootTAlbum) { errMsg = i18n("Cannot edit root tag"); return false; } { CoreDbAccess access; ChangingDB changing(d); access.db()->setTagIcon(album->id(), iconKDE, iconID); album->m_icon = iconKDE; album->m_iconId = iconID; } emit signalAlbumIconChanged(album); return true; } AlbumList AlbumManager::getRecentlyAssignedTags(bool includeInternal) const { QList tagIDs = CoreDbAccess().db()->getRecentlyAssignedTags(); AlbumList resultList; for (QList::const_iterator it = tagIDs.constBegin() ; it != tagIDs.constEnd() ; ++it) { TAlbum* const album = findTAlbum(*it); if (album) { if (!includeInternal && album->isInternalTag()) { continue; } resultList.append(album); } } return resultList; } QStringList AlbumManager::tagPaths(const QList& tagIDs, bool leadingSlash, bool includeInternal) const { QStringList tagPaths; for (QList::const_iterator it = tagIDs.constBegin() ; it != tagIDs.constEnd() ; ++it) { TAlbum* album = findTAlbum(*it); if (album) { if (!includeInternal && album->isInternalTag()) { continue; } tagPaths.append(album->tagPath(leadingSlash)); } } return tagPaths; } QStringList AlbumManager::tagNames(const QList& tagIDs, bool includeInternal) const { QStringList tagNames; foreach(int id, tagIDs) { TAlbum* const album = findTAlbum(id); if (album) { if (!includeInternal && album->isInternalTag()) { continue; } tagNames << album->title(); } } return tagNames; } QHash AlbumManager::tagPaths(bool leadingSlash, bool includeInternal) const { QHash hash; AlbumIterator it(d->rootTAlbum); while (it.current()) { TAlbum* const t = (TAlbum*)(*it); if (includeInternal || !t->isInternalTag()) { hash.insert(t->id(), t->tagPath(leadingSlash)); } ++it; } return hash; } QHash AlbumManager::tagNames(bool includeInternal) const { QHash hash; AlbumIterator it(d->rootTAlbum); while (it.current()) { TAlbum* const t = (TAlbum*)(*it); if (includeInternal || !t->isInternalTag()) { hash.insert(t->id(), t->title()); } ++it; } return hash; } QList< int > AlbumManager::subTags(int tagId, bool recursive) { TAlbum* const album = this->findTAlbum(tagId); return album->childAlbumIds(recursive); } AlbumList AlbumManager::findTagsWithProperty(const QString& property) { AlbumList list; QList ids = TagsCache::instance()->tagsWithProperty(property); foreach(int id, ids) { TAlbum* const album = findTAlbum(id); if (album) { list << album; } } return list; } AlbumList AlbumManager::findTagsWithProperty(const QString& property, const QString& value) { AlbumList list; AlbumIterator it(d->rootTAlbum); while (it.current()) { if (static_cast(*it)->property(property) == value) { list << *it; } ++it; } return list; } QHash AlbumManager::albumTitles() const { QHash hash; AlbumIterator it(d->rootPAlbum); while (it.current()) { PAlbum* const a = (PAlbum*)(*it); hash.insert(a->id(), a->title()); ++it; } return hash; } SAlbum* AlbumManager::createSAlbum(const QString& name, DatabaseSearch::Type type, const QString& query) { // first iterate through all the search albums and see if there's an existing // SAlbum with same name. (Remember, SAlbums are arranged in a flat list) SAlbum* album = findSAlbum(name); ChangingDB changing(d); if (album) { updateSAlbum(album, query, name, type); return album; } int id = CoreDbAccess().db()->addSearch(type, name, query); if (id == -1) { return 0; } album = new SAlbum(name, id); emit signalAlbumAboutToBeAdded(album, d->rootSAlbum, d->rootSAlbum->lastChild()); album->setSearch(type, query); album->setParent(d->rootSAlbum); d->allAlbumsIdHash.insert(album->globalID(), album); emit signalAlbumAdded(album); return album; } bool AlbumManager::updateSAlbum(SAlbum* album, const QString& changedQuery, const QString& changedName, DatabaseSearch::Type type) { if (!album) { return false; } QString newName = changedName.isNull() ? album->title() : changedName; DatabaseSearch::Type newType = (type == DatabaseSearch::UndefinedType) ? album->searchType() : type; ChangingDB changing(d); CoreDbAccess().db()->updateSearch(album->id(), newType, newName, changedQuery); QString oldName = album->title(); album->setSearch(newType, changedQuery); album->setTitle(newName); if (oldName != album->title()) { emit signalAlbumRenamed(album); } if (!d->currentAlbums.isEmpty()) { if (d->currentAlbums.first() == album) { emit signalAlbumCurrentChanged(d->currentAlbums); } } return true; } bool AlbumManager::deleteSAlbum(SAlbum* album) { if (!album) { return false; } emit signalAlbumAboutToBeDeleted(album); ChangingDB changing(d); CoreDbAccess().db()->deleteSearch(album->id()); d->allAlbumsIdHash.remove(album->globalID()); emit signalAlbumDeleted(album); quintptr deletedAlbum = reinterpret_cast(album); delete album; emit signalAlbumHasBeenDeleted(deletedAlbum); return true; } QMap AlbumManager::getPAlbumsCount() const { return d->pAlbumsCount; } QMap AlbumManager::getTAlbumsCount() const { return d->tAlbumsCount; } QMap AlbumManager::getDAlbumsCount() const { return d->dAlbumsCount; } QMap AlbumManager::getFaceCount() const { return d->fAlbumsCount; } bool AlbumManager::isMovingAlbum(Album* album) const { return d->currentlyMovingAlbum == album; } void AlbumManager::insertPAlbum(PAlbum* album, PAlbum* parent) { if (!album) { return; } emit signalAlbumAboutToBeAdded(album, parent, parent ? parent->lastChild() : 0); if (parent) { album->setParent(parent); } d->albumPathHash[album] = album; d->allAlbumsIdHash[album->globalID()] = album; emit signalAlbumAdded(album); } void AlbumManager::removePAlbum(PAlbum* album) { if (!album) { return; } // remove all children of this album Album* child = album->m_firstChild; PAlbum* toBeRemoved = 0; while (child) { Album* next = child->m_next; toBeRemoved = static_cast(child); if (toBeRemoved) { removePAlbum(toBeRemoved); toBeRemoved = 0; } child = next; } emit signalAlbumAboutToBeDeleted(album); d->albumPathHash.remove(album); d->allAlbumsIdHash.remove(album->globalID()); CoreDbUrl url = album->databaseUrl(); if (!d->currentAlbums.isEmpty()) { if (album == d->currentAlbums.first()) { d->currentAlbums.clear(); emit signalAlbumCurrentChanged(d->currentAlbums); } } if (album->isAlbumRoot()) { d->albumRootAlbumHash.remove(album->albumRootId()); } emit signalAlbumDeleted(album); quintptr deletedAlbum = reinterpret_cast(album); delete album; emit signalAlbumHasBeenDeleted(deletedAlbum); } void AlbumManager::insertTAlbum(TAlbum* album, TAlbum* parent) { if (!album) { return; } emit signalAlbumAboutToBeAdded(album, parent, parent ? parent->lastChild() : 0); if (parent) { album->setParent(parent); } d->allAlbumsIdHash.insert(album->globalID(), album); emit signalAlbumAdded(album); } void AlbumManager::removeTAlbum(TAlbum* album) { if (!album) { return; } // remove all children of this album Album* child = album->m_firstChild; TAlbum* toBeRemoved = 0; while (child) { Album* next = child->m_next; toBeRemoved = static_cast(child); if (toBeRemoved) { removeTAlbum(toBeRemoved); toBeRemoved = 0; } child = next; } emit signalAlbumAboutToBeDeleted(album); d->allAlbumsIdHash.remove(album->globalID()); if (!d->currentAlbums.isEmpty()) { if (album == d->currentAlbums.first()) { d->currentAlbums.clear(); emit signalAlbumCurrentChanged(d->currentAlbums); } } emit signalAlbumDeleted(album); quintptr deletedAlbum = reinterpret_cast(album); delete album; emit signalAlbumHasBeenDeleted(deletedAlbum); } void AlbumManager::notifyAlbumDeletion(Album* album) { invalidateGuardedPointers(album); } void AlbumManager::slotAlbumsJobResult() { if (!d->albumListJob) { return; } if (d->albumListJob->hasErrors()) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to list albums"; // Pop-up a message about the error. DNotificationWrapper(QString(), d->albumListJob->errorsList().first(), 0, i18n("digiKam")); } d->albumListJob = 0; } void AlbumManager::slotAlbumsJobData(const QMap &albumsStatMap) { if (albumsStatMap.isEmpty()) { return; } d->pAlbumsCount = albumsStatMap; emit signalPAlbumsDirty(albumsStatMap); } void AlbumManager::slotPeopleJobResult() { if (!d->personListJob) { return; } if (d->personListJob->hasErrors()) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to list face tags"; // Pop-up a message about the error. DNotificationWrapper(QString(), d->personListJob->errorsList().first(), 0, i18n("digiKam")); } d->personListJob = 0; } void AlbumManager::slotPeopleJobData(const QMap >& facesStatMap) { if (facesStatMap.isEmpty()) { return; } // For now, we only use the sum of confirmed and unconfirmed faces d->fAlbumsCount.clear(); typedef QMap IntIntMap; foreach(const IntIntMap& counts, facesStatMap) { QMap::const_iterator it; for (it = counts.begin() ; it != counts.end() ; ++it) { d->fAlbumsCount[it.key()] += it.value(); } } emit signalFaceCountsDirty(d->fAlbumsCount); } void AlbumManager::slotTagsJobResult() { if (!d->tagListJob) { return; } if (d->tagListJob->hasErrors()) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to list face tags"; // Pop-up a message about the error. DNotificationWrapper(QString(), d->personListJob->errorsList().first(), 0, i18n("digiKam")); } d->tagListJob = 0; } void AlbumManager::slotTagsJobData(const QMap& tagsStatMap) { if (tagsStatMap.isEmpty()) { return; } d->tAlbumsCount = tagsStatMap; emit signalTAlbumsDirty(tagsStatMap); } void AlbumManager::slotDatesJobResult() { if (!d->dateListJob) { return; } if (d->dateListJob->hasErrors()) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to list dates"; // Pop-up a message about the error. DNotificationWrapper(QString(), d->dateListJob->errorsList().first(), 0, i18n("digiKam")); } d->dateListJob = 0; emit signalAllDAlbumsLoaded(); } void AlbumManager::slotDatesJobData(const QMap& datesStatMap) { if (datesStatMap.isEmpty() || !d->rootDAlbum) { return; } // insert all the DAlbums into a qmap for quick access QMap mAlbumMap; QMap yAlbumMap; AlbumIterator it(d->rootDAlbum); while (it.current()) { DAlbum* const a = (DAlbum*)(*it); if (a->range() == DAlbum::Month) { mAlbumMap.insert(a->date(), a); } else { yAlbumMap.insert(a->date().year(), a); } ++it; } QMap yearMonthMap; for (QMap::const_iterator it = datesStatMap.constBegin() ; it != datesStatMap.constEnd() ; ++it) { YearMonth yearMonth = YearMonth(it.key().date().year(), it.key().date().month()); QMap::iterator it2 = yearMonthMap.find(yearMonth); if (it2 == yearMonthMap.end()) { yearMonthMap.insert(yearMonth, *it); } else { *it2 += *it; } } int year, month; for (QMap::const_iterator iter = yearMonthMap.constBegin() ; iter != yearMonthMap.constEnd() ; ++iter) { year = iter.key().first; month = iter.key().second; QDate md(year, month, 1); // Do we already have this Month album if (mAlbumMap.contains(md)) { // already there. remove Month album from map mAlbumMap.remove(md); if (yAlbumMap.contains(year)) { // already there. remove from map yAlbumMap.remove(year); } continue; } // Check if Year Album already exist. DAlbum* yAlbum = 0; AlbumIterator it(d->rootDAlbum); while (it.current()) { DAlbum* const a = (DAlbum*)(*it); if (a->date() == QDate(year, 1, 1) && a->range() == DAlbum::Year) { yAlbum = a; break; } ++it; } // If no, create Year album. if (!yAlbum) { yAlbum = new DAlbum(QDate(year, 1, 1), false, DAlbum::Year); emit signalAlbumAboutToBeAdded(yAlbum, d->rootDAlbum, d->rootDAlbum->lastChild()); yAlbum->setParent(d->rootDAlbum); d->allAlbumsIdHash.insert(yAlbum->globalID(), yAlbum); emit signalAlbumAdded(yAlbum); } // Create Month album DAlbum* mAlbum = new DAlbum(md); emit signalAlbumAboutToBeAdded(mAlbum, yAlbum, yAlbum->lastChild()); mAlbum->setParent(yAlbum); d->allAlbumsIdHash.insert(mAlbum->globalID(), mAlbum); emit signalAlbumAdded(mAlbum); } // Now the items contained in the maps are the ones which // have been deleted. for (QMap::const_iterator it = mAlbumMap.constBegin() ; it != mAlbumMap.constEnd() ; ++it) { DAlbum* const album = it.value(); emit signalAlbumAboutToBeDeleted(album); d->allAlbumsIdHash.remove(album->globalID()); emit signalAlbumDeleted(album); quintptr deletedAlbum = reinterpret_cast(album); delete album; emit signalAlbumHasBeenDeleted(deletedAlbum); } for (QMap::const_iterator it = yAlbumMap.constBegin() ; it != yAlbumMap.constEnd() ; ++it) { DAlbum* const album = it.value(); emit signalAlbumAboutToBeDeleted(album); d->allAlbumsIdHash.remove(album->globalID()); emit signalAlbumDeleted(album); quintptr deletedAlbum = reinterpret_cast(album); delete album; emit signalAlbumHasBeenDeleted(deletedAlbum); } d->dAlbumsCount = yearMonthMap; emit signalDAlbumsDirty(yearMonthMap); emit signalDatesMapDirty(datesStatMap); } void AlbumManager::slotAlbumChange(const AlbumChangeset& changeset) { if (d->changingDB || !d->rootPAlbum) { return; } switch (changeset.operation()) { case AlbumChangeset::Added: case AlbumChangeset::Deleted: if (!d->scanPAlbumsTimer->isActive()) { d->scanPAlbumsTimer->start(); } break; case AlbumChangeset::Renamed: case AlbumChangeset::PropertiesChanged: // mark for rescan d->changedPAlbums << changeset.albumId(); if (!d->updatePAlbumsTimer->isActive()) { d->updatePAlbumsTimer->start(); } break; case AlbumChangeset::Unknown: break; } } void AlbumManager::slotTagChange(const TagChangeset& changeset) { if (d->changingDB || !d->rootTAlbum) { return; } switch (changeset.operation()) { case TagChangeset::Added: case TagChangeset::Moved: case TagChangeset::Deleted: case TagChangeset::Reparented: if (!d->scanTAlbumsTimer->isActive()) { d->scanTAlbumsTimer->start(); } break; case TagChangeset::Renamed: case TagChangeset::IconChanged: /** * @todo what happens here? */ break; case TagChangeset::PropertiesChanged: { TAlbum* tag = findTAlbum(changeset.tagId()); if (tag) { emit signalTagPropertiesChanged(tag); } break; } case TagChangeset::Unknown: break; } } void AlbumManager::slotSearchChange(const SearchChangeset& changeset) { if (d->changingDB || !d->rootSAlbum) { return; } switch (changeset.operation()) { case SearchChangeset::Added: case SearchChangeset::Deleted: if (!d->scanSAlbumsTimer->isActive()) { d->scanSAlbumsTimer->start(); } break; case SearchChangeset::Changed: if (!d->currentAlbums.isEmpty()) { Album* currentAlbum = d->currentAlbums.first(); if (currentAlbum && currentAlbum->type() == Album::SEARCH && currentAlbum->id() == changeset.searchId()) { // the pointer is the same, but the contents changed emit signalAlbumCurrentChanged(d->currentAlbums); } } break; case SearchChangeset::Unknown: break; } } void AlbumManager::slotCollectionImageChange(const CollectionImageChangeset& changeset) { if (!d->rootDAlbum) { return; } switch (changeset.operation()) { case CollectionImageChangeset::Added: case CollectionImageChangeset::Removed: case CollectionImageChangeset::RemovedAll: if (!d->scanDAlbumsTimer->isActive()) { d->scanDAlbumsTimer->start(); } if (!d->albumItemCountTimer->isActive()) { d->albumItemCountTimer->start(); } break; default: break; } } void AlbumManager::slotImageTagChange(const ImageTagChangeset& changeset) { if (!d->rootTAlbum) { return; } switch (changeset.operation()) { case ImageTagChangeset::Added: case ImageTagChangeset::Removed: case ImageTagChangeset::RemovedAll: // Add properties changed. // Reason: in people sidebar, the images are not // connected with the ImageTag table but by // ImageTagProperties entries. // Thus, the count of entries in face tags are not // updated. This adoption should fix the problem. case ImageTagChangeset::PropertiesChanged: if (!d->tagItemCountTimer->isActive()) { d->tagItemCountTimer->start(); } break; default: break; } } void AlbumManager::slotImagesDeleted(const QList& imageIds) { qCDebug(DIGIKAM_GENERAL_LOG) << "Got image deletion notification from ImageViewUtilities for " << imageIds.size() << " images."; QSet sAlbumsToUpdate; QSet deletedImages = imageIds.toSet(); QList sAlbums = findSAlbumsBySearchType(DatabaseSearch::DuplicatesSearch); foreach(SAlbum* const sAlbum, sAlbums) { // Read the search query XML and save the image ids SearchXmlReader reader(sAlbum->query()); SearchXml::Element element; QSet images; while ((element = reader.readNext()) != SearchXml::End) { if ((element == SearchXml::Field) && (reader.fieldName().compare(QLatin1String("imageid")) == 0)) { images = reader.valueToLongLongList().toSet(); } } // If the deleted images are part of the SAlbum, // mark the album as ready for deletion and the images as ready for rescan. #if QT_VERSION >= 0x050600 if (images.intersects(deletedImages)) #else if (images.intersect(deletedImages).isEmpty()) #endif { sAlbumsToUpdate.insert(sAlbum); } } if (!sAlbumsToUpdate.isEmpty()) { emit signalUpdateDuplicatesAlbums(sAlbumsToUpdate.toList(), deletedImages.toList()); } } void AlbumManager::removeWatchedPAlbums(const PAlbum* const album) { d->albumWatch->removeWatchedPAlbums(album); } void AlbumManager::addFakeConnection() { if (!d->dbFakeConnection) { // workaround for the problem mariaDB >= 10.2 and QTBUG-63108 QSqlDatabase::addDatabase(QLatin1String("QMYSQL"), QLatin1String("FakeConnection")); d->dbFakeConnection = true; } } void AlbumManager::removeFakeConnection() { if (d->dbFakeConnection) { QSqlDatabase::removeDatabase(QLatin1String("FakeConnection")); } } } // namespace Digikam