• Skip to content
  • Skip to link menu
KDE 4.4 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

digikam

albummanager.cpp

Go to the documentation of this file.
00001 /* ============================================================
00002  *
00003  * This file is a part of digiKam project
00004  * http://www.digikam.org
00005  *
00006  * Date        : 2004-06-15
00007  * Description : Albums manager interface.
00008  *
00009  * Copyright (C) 2004 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
00010  * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
00011  * Copyright (C) 2006-2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
00012  *
00013  * This program is free software; you can redistribute it
00014  * and/or modify it under the terms of the GNU General
00015  * Public License as published by the Free Software Foundation;
00016  * either version 2, or (at your option)
00017  * any later version.
00018  *
00019  * This program is distributed in the hope that it will be useful,
00020  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00021  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00022  * GNU General Public License for more details.
00023  *
00024  * ============================================================ */
00025 
00026 #include "albummanager.moc"
00027 
00028 // C ANSI includes
00029 
00030 extern "C"
00031 {
00032 #include <sys/types.h>
00033 #include <sys/stat.h>
00034 #include <unistd.h>
00035 }
00036 
00037 // C++ includes
00038 
00039 #include <clocale>
00040 #include <cstdlib>
00041 #include <cstdio>
00042 #include <cerrno>
00043 
00044 // Qt includes
00045 
00046 #include <QApplication>
00047 #include <QByteArray>
00048 #include <QComboBox>
00049 #include <QDBusConnection>
00050 #include <QDBusInterface>
00051 #include <QDBusReply>
00052 #include <QDir>
00053 #include <QFile>
00054 #include <QGroupBox>
00055 #include <QHash>
00056 #include <QList>
00057 #include <QLabel>
00058 #include <QMultiHash>
00059 #include <QRadioButton>
00060 #include <QTextCodec>
00061 #include <QTimer>
00062 
00063 // KDE includes
00064 
00065 #include <kconfig.h>
00066 #include <klocale.h>
00067 #include <kdeversion.h>
00068 #include <kmessagebox.h>
00069 #include <kstandarddirs.h>
00070 #include <kio/netaccess.h>
00071 #include <kio/global.h>
00072 #include <kio/job.h>
00073 #include <kdirwatch.h>
00074 #include <kconfiggroup.h>
00075 #include <kdebug.h>
00076 
00077 // Local includes
00078 
00079 #include "album.h"
00080 #include "albumdb.h"
00081 #include "albumsettings.h"
00082 #include "collectionmanager.h"
00083 #include "collectionlocation.h"
00084 #include "databaseaccess.h"
00085 #include "databaseurl.h"
00086 #include "databaseparameters.h"
00087 #include "databasethumbnailinfoprovider.h"
00088 #include "databasewatch.h"
00089 #include "dio.h"
00090 #include "imagelister.h"
00091 #include "scancontroller.h"
00092 #include "setup.h"
00093 #include "thumbnailloadthread.h"
00094 #include "upgradedb_sqlite2tosqlite3.h"
00095 #include "config-digikam.h"
00096 
00097 namespace Digikam
00098 {
00099 
00100 class PAlbumPath
00101 {
00102 public:
00103 
00104     PAlbumPath()
00105         : albumRootId(-1)
00106     {
00107     }
00108 
00109     PAlbumPath(int albumRootId, const QString& albumPath)
00110        : albumRootId(albumRootId), albumPath(albumPath)
00111     {
00112     }
00113 
00114     PAlbumPath(PAlbum *album)
00115     {
00116         if (album->isRoot())
00117         {
00118             albumRootId = -1;
00119         }
00120         else
00121         {
00122             albumRootId = album->albumRootId();
00123             albumPath   = album->albumPath();
00124         }
00125     }
00126 
00127     bool operator==(const PAlbumPath& other) const
00128     {
00129         return other.albumRootId == albumRootId && other.albumPath == albumPath;
00130     }
00131 
00132     int     albumRootId;
00133     QString albumPath;
00134 };
00135 
00136 uint qHash(const PAlbumPath& id)
00137 {
00138     return ::qHash(id.albumRootId) ^ ::qHash(id.albumPath);
00139 }
00140 
00141 class AlbumManagerPriv
00142 {
00143 
00144 public:
00145 
00146     AlbumManagerPriv()
00147     {
00148         changed            = false;
00149         hasPriorizedDbPath = false;
00150         dateListJob        = 0;
00151         albumListJob       = 0;
00152         tagListJob         = 0;
00153         dirWatch           = 0;
00154         rootPAlbum         = 0;
00155         rootTAlbum         = 0;
00156         rootDAlbum         = 0;
00157         rootSAlbum         = 0;
00158         currentAlbum       = 0;
00159         changingDB         = false;
00160         scanPAlbumsTimer   = 0;
00161         scanTAlbumsTimer   = 0;
00162         scanSAlbumsTimer   = 0;
00163         scanDAlbumsTimer   = 0;
00164         updatePAlbumsTimer = 0;
00165         albumItemCountTimer= 0;
00166         tagItemCountTimer  = 0;
00167     }
00168 
00169     bool                        changed;
00170     bool                        hasPriorizedDbPath;
00171 
00172     QString                     dbPath;
00173 
00174     QList<QDateTime>            dbPathModificationDateList;
00175     QList<QString>              dirWatchBlackList;
00176 
00177     KIO::TransferJob*           albumListJob;
00178     KIO::TransferJob*           dateListJob;
00179     KIO::TransferJob*           tagListJob;
00180 
00181     KDirWatch*                  dirWatch;
00182 
00183     PAlbum*                     rootPAlbum;
00184     TAlbum*                     rootTAlbum;
00185     DAlbum*                     rootDAlbum;
00186     SAlbum*                     rootSAlbum;
00187 
00188     QHash<int,Album *>          allAlbumsIdHash;
00189     QHash<PAlbumPath, PAlbum*>  albumPathHash;
00190     QHash<int, PAlbum*>         albumRootAlbumHash;
00191 
00192     QMultiHash<Album*, Album**> guardedPointers;
00193 
00194     Album*                      currentAlbum;
00195 
00196     bool                        changingDB;
00197     QTimer*                     scanPAlbumsTimer;
00198     QTimer*                     scanTAlbumsTimer;
00199     QTimer*                     scanSAlbumsTimer;
00200     QTimer*                     scanDAlbumsTimer;
00201     QTimer*                     updatePAlbumsTimer;
00202     QTimer*                     albumItemCountTimer;
00203     QTimer*                     tagItemCountTimer;
00204     QSet<int>                   changedPAlbums;
00205 
00206 
00207     QList<QDateTime> buildDirectoryModList(const QFileInfo& dbFile)
00208     {
00209         // retrieve modification dates
00210         QList<QDateTime> modList;
00211         QFileInfoList fileInfoList = dbFile.dir().entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
00212 
00213         // build list
00214         foreach (const QFileInfo& info, fileInfoList)
00215         {
00216             // ignore digikam4.db and journal and other temporary files
00217             if (!dirWatchBlackList.contains(info.fileName()))
00218             {
00219                 modList << info.lastModified();
00220             }
00221         }
00222         return modList;
00223     }
00224 
00225     QString labelForAlbumRootAlbum(const CollectionLocation& location)
00226     {
00227         QString label = location.label();
00228         if (label.isEmpty())
00229             label = location.albumRootPath();
00230         return label;
00231     }
00232 };
00233 
00234 class ChangingDB
00235 {
00236 public:
00237 
00238     ChangingDB(AlbumManagerPriv *d)
00239         : d(d)
00240     {
00241         d->changingDB = true;
00242     }
00243     ~ChangingDB()
00244     {
00245         d->changingDB = false;
00246     }
00247     AlbumManagerPriv* const d;
00248 };
00249 
00250 class AlbumManagerCreator { public: AlbumManager object; };
00251 K_GLOBAL_STATIC(AlbumManagerCreator, creator)
00252 
00253 // A friend-class shortcut to circumvent accessing this from within the destructor
00254 AlbumManager *AlbumManager::internalInstance = 0;
00255 
00256 AlbumManager* AlbumManager::instance()
00257 {
00258     return &creator->object;
00259 }
00260 
00261 AlbumManager::AlbumManager()
00262             : d(new AlbumManagerPriv)
00263 {
00264     internalInstance = this;
00265 
00266     // these operations are pretty fast, no need for long queuing
00267     d->scanPAlbumsTimer = new QTimer(this);
00268     d->scanPAlbumsTimer->setInterval(50);
00269     d->scanPAlbumsTimer->setSingleShot(true);
00270     connect(d->scanPAlbumsTimer, SIGNAL(timeout()), this, SLOT(scanPAlbums()));
00271 
00272     d->scanTAlbumsTimer = new QTimer(this);
00273     d->scanTAlbumsTimer->setInterval(50);
00274     d->scanTAlbumsTimer->setSingleShot(true);
00275     connect(d->scanTAlbumsTimer, SIGNAL(timeout()), this, SLOT(scanTAlbums()));
00276 
00277     d->scanSAlbumsTimer = new QTimer(this);
00278     d->scanSAlbumsTimer->setInterval(50);
00279     d->scanSAlbumsTimer->setSingleShot(true);
00280     connect(d->scanSAlbumsTimer, SIGNAL(timeout()), this, SLOT(scanSAlbums()));
00281 
00282     d->updatePAlbumsTimer = new QTimer(this);
00283     d->updatePAlbumsTimer->setInterval(50);
00284     d->updatePAlbumsTimer->setSingleShot(true);
00285     connect(d->updatePAlbumsTimer, SIGNAL(timeout()), this, SLOT(updateChangedPAlbums()));
00286 
00287     // this operation is much more expensive than the other scan methods
00288     d->scanDAlbumsTimer = new QTimer(this);
00289     d->scanDAlbumsTimer->setInterval(5000);
00290     d->scanDAlbumsTimer->setSingleShot(true);
00291     connect(d->scanDAlbumsTimer, SIGNAL(timeout()), this, SLOT(scanDAlbums()));
00292 
00293     // moderately expensive
00294     d->albumItemCountTimer = new QTimer(this);
00295     d->albumItemCountTimer->setInterval(1000);
00296     d->albumItemCountTimer->setSingleShot(true);
00297     connect(d->albumItemCountTimer, SIGNAL(timeout()), this, SLOT(getAlbumItemsCount()));
00298 
00299     // more expensive
00300     d->tagItemCountTimer = new QTimer(this);
00301     d->tagItemCountTimer->setInterval(2500);
00302     d->tagItemCountTimer->setSingleShot(true);
00303     connect(d->tagItemCountTimer, SIGNAL(timeout()), this, SLOT(getTagItemsCount()));
00304 }
00305 
00306 AlbumManager::~AlbumManager()
00307 {
00308     delete d->rootPAlbum;
00309     delete d->rootTAlbum;
00310     delete d->rootDAlbum;
00311     delete d->rootSAlbum;
00312 
00313     internalInstance = 0;
00314     delete d;
00315 }
00316 
00317 void AlbumManager::cleanUp()
00318 {
00319     // This is what we prefer to do before KApplication destruction
00320 
00321     if (d->dateListJob)
00322     {
00323         d->dateListJob->kill();
00324         d->dateListJob = 0;
00325     }
00326 
00327     if (d->albumListJob)
00328     {
00329         d->albumListJob->kill();
00330         d->albumListJob = 0;
00331     }
00332 
00333     if (d->tagListJob)
00334     {
00335         d->tagListJob->kill();
00336         d->tagListJob = 0;
00337     }
00338 
00339     delete d->dirWatch;
00340     d->dirWatch = 0;
00341 }
00342 
00343 bool AlbumManager::databaseEqual(const QString& dbPath) const
00344 {
00345     return d->dbPath == dbPath;
00346 }
00347 
00348 static bool moveToBackup(const QFileInfo& info)
00349 {
00350     if (info.exists())
00351     {
00352         QFileInfo backup(info.dir(), info.fileName() + "-backup-" + QDateTime::currentDateTime().toString(Qt::ISODate));
00353         KIO::Job *job = KIO::file_move(info.filePath(), backup.filePath(), -1, KIO::Overwrite | KIO::HideProgressInfo);
00354         if (!KIO::NetAccess::synchronousRun(job, 0))
00355         {
00356             KMessageBox::error(0, i18n("Failed to backup the existing database file (\"%1\")."
00357                                        "Refusing to replace file without backup, using the existing file.",
00358                                         info.filePath()));
00359             return false;
00360         }
00361     }
00362     return true;
00363 }
00364 
00365 static bool copyToNewLocation(const QFileInfo& oldFile, const QFileInfo& newFile, const QString otherMessage = QString())
00366 {
00367     QString message = otherMessage;
00368     if (message.isNull())
00369         message = i18n("Failed to copy the old database file (\"%1\") "
00370                        "to its new location (\"%2\"). "
00371                        "Starting with an empty database.",
00372                        oldFile.filePath(), newFile.filePath());
00373 
00374     KIO::Job *job = KIO::file_copy(oldFile.filePath(), newFile.filePath(), -1, KIO::Overwrite /*| KIO::HideProgressInfo*/);
00375     if (!KIO::NetAccess::synchronousRun(job, 0))
00376     {
00377         KMessageBox::error(0, message);
00378         return false;
00379     }
00380     return true;
00381 }
00382 
00383 void AlbumManager::checkDatabaseDirsAfterFirstRun(const QString& dbPath, const QString& albumPath)
00384 {
00385     // for bug #193522
00386     QDir newDir(dbPath);
00387     QDir albumDir(albumPath);
00388     DatabaseParameters newParams = DatabaseParameters::parametersForSQLiteDefaultFile(newDir.path());
00389     QFileInfo digikam4DB(newParams.SQLiteDatabaseFile());
00390 
00391     if (!digikam4DB.exists())
00392     {
00393         QFileInfo digikam3DB(newDir, "digikam3.db");
00394         QFileInfo digikamVeryOldDB(newDir, "digikam.db");
00395 
00396         if (digikam3DB.exists() || digikamVeryOldDB.exists())
00397         {
00398             KGuiItem startFresh(i18n("Create New Database"), "document-new");
00399             KGuiItem upgrade(i18n("Upgrade Database"), "view-refresh");
00400             int result = KMessageBox::warningYesNo(0,
00401                                 i18n("<p>You have chosen the folder \"%1\" as the place to store the database. "
00402                                      "A database file from an older version of digiKam is found in this folder.</p> "
00403                                      "<p>Would you like to upgrade the old database file - confirming "
00404                                      "that this database file was indeed created for the pictures located in the folder \"%2\" - "
00405                                      "or ignore the old file and start with a new database?</p> ",
00406                                     newDir.path(), albumDir.path()),
00407                                 i18n("Database Folder"),
00408                                 upgrade, startFresh);
00409 
00410             if (result == KMessageBox::Yes)
00411             {
00412                 // SchemaUpdater expects Album Path to point to the album root of the 0.9 db file.
00413                 // Restore this situation.
00414                 KSharedConfigPtr config = KGlobal::config();
00415                 KConfigGroup group = config->group("Album Settings");
00416                 group.writeEntry("Album Path", albumDir.path());
00417                 group.sync();
00418             }
00419             else if (result == KMessageBox::No)
00420             {
00421                 moveToBackup(digikam3DB);
00422                 moveToBackup(digikamVeryOldDB);
00423             }
00424         }
00425     }
00426 }
00427 
00428 void AlbumManager::changeDatabase(const QString& dbPath)
00429 {
00430     if (d->dbPath == dbPath)
00431         return;
00432 
00433     // if there is no file at the new place, copy old one
00434     DatabaseParameters params = DatabaseAccess::parameters();
00435     if (params.isSQLite())
00436     {
00437         QDir oldDir(d->dbPath);
00438         QDir newDir(dbPath);
00439         QFileInfo oldFile(params.SQLiteDatabaseFile());
00440         QFileInfo newFile(newDir, oldFile.fileName());
00441         if (!newFile.exists())
00442         {
00443             QFileInfo digikam3DB(newDir, "digikam3.db");
00444             QFileInfo digikamVeryOldDB(newDir, "digikam.db");
00445 
00446             if (digikam3DB.exists() || digikamVeryOldDB.exists())
00447             {
00448                 KGuiItem copyCurrent(i18n("Copy Current Database"), "edit-copy");
00449                 KGuiItem startFresh(i18n("Create New Database"), "document-new");
00450                 KGuiItem upgrade(i18n("Upgrade Database"), "view-refresh");
00451                 int result = KMessageBox::warningYesNoCancel(0,
00452                                     i18n("<p>You have chosen the folder \"%1\" as the new place to store the database. "
00453                                          "A database file from an older version of digiKam is found in this folder.</p> "
00454                                         "<p>Would you like to upgrade the old database file, start with a new database, "
00455                                         "or copy the current database to this location and continue using it?</p> ",
00456                                         newDir.path()),
00457                                     i18n("New database folder"),
00458                                     upgrade, startFresh, copyCurrent);
00459 
00460                 if (result == KMessageBox::Yes)
00461                 {
00462                     // SchemaUpdater expects Album Path to point to the album root of the 0.9 db file.
00463                     // Restore this situation.
00464                     KSharedConfigPtr config = KGlobal::config();
00465                     KConfigGroup group = config->group("Album Settings");
00466                     group.writeEntry("Album Path", newDir.path());
00467                     group.sync();
00468                 }
00469                 else if (result == KMessageBox::No)
00470                 {
00471                     moveToBackup(digikam3DB);
00472                     moveToBackup(digikamVeryOldDB);
00473                 }
00474                 else if (result == KMessageBox::Cancel)
00475                 {
00476                     copyToNewLocation(oldFile, newFile, i18n("Failed to copy the old database file (\"%1\") "
00477                                                              "to its new location (\"%2\"). "
00478                                                              "Trying to upgrade old databases.",
00479                                                              oldFile.filePath(), newFile.filePath()));
00480                 }
00481             }
00482             else
00483             {
00484 
00485                 KGuiItem copyCurrent(i18n("Copy Current Database"), "edit-copy");
00486                 KGuiItem startFresh(i18n("Create New Database"), "document-new");
00487                 int result = KMessageBox::warningYesNo(0,
00488                                     i18n("<p>You have chosen the folder \"%1\" as the new place to store the database.</p>"
00489                                         "<p>Would you like to copy the current database to this location "
00490                                         "and continue using it, or start with a new database?</p> ",
00491                                         newDir.path()),
00492                                     i18n("New database folder"),
00493                                     startFresh, copyCurrent);
00494 
00495                 if (result == KMessageBox::No)
00496                 {
00497                     copyToNewLocation(oldFile, newFile);
00498                 }
00499             }
00500         }
00501         else
00502         {
00503             KGuiItem replaceItem(i18n("Copy Current Database"), "edit-copy");
00504             KGuiItem useExistingItem(i18n("Use Existing File"), "document-open");
00505             int result = KMessageBox::warningYesNo(0,
00506                                 i18n("<p>You have chosen the folder \"%1\" as the new place to store the database. "
00507                                      "There is already a database file in this location.</p> "
00508                                      "<p>Would you like to use this existing file as the new database, or remove it "
00509                                      "and copy the current database to this place?</p> ",
00510                                       newDir.path()),
00511                                 i18n("New database folder"),
00512                                 replaceItem, useExistingItem);
00513             if (result == KMessageBox::Yes)
00514             {
00515                 // first backup
00516                 if (moveToBackup(newFile))
00517                 {
00518                     // then copy
00519                    copyToNewLocation(oldFile, newFile);
00520                 }
00521             }
00522         }
00523     }
00524 
00525     if (setDatabase(dbPath, false))
00526     {
00527         QApplication::setOverrideCursor(Qt::WaitCursor);
00528         startScan();
00529         QApplication::restoreOverrideCursor();
00530         ScanController::instance()->completeCollectionScan();
00531     }
00532 }
00533 
00534 bool AlbumManager::setDatabase(const QString& dbPath, bool priority, const QString& suggestedAlbumRoot)
00535 {
00536     if (dbPath.isEmpty())
00537         return false;
00538 
00539     // This is to ensure that the setup does not overrule the command line.
00540     // Replace with a better solution?
00541     if (priority)
00542     {
00543         d->hasPriorizedDbPath = true;
00544     }
00545     else if (d->hasPriorizedDbPath && !d->dbPath.isNull())
00546     {
00547         // ignore change without priority
00548         // true means, don't exit()
00549         return true;
00550     }
00551 
00552     if (d->dbPath == dbPath)
00553         return true;
00554 
00555     // shutdown possibly running collection scans. Must call resumeCollectionScan further down.
00556     ScanController::instance()->cancelAllAndSuspendCollectionScan();
00557     QApplication::setOverrideCursor(Qt::WaitCursor);
00558 
00559     d->dbPath  = dbPath;
00560     d->changed = true;
00561 
00562     disconnect(CollectionManager::instance(), 0, this, 0);
00563     if (DatabaseAccess::databaseWatch())
00564         disconnect(DatabaseAccess::databaseWatch(), 0, this, 0);
00565     d->dbPathModificationDateList.clear();
00566 
00567     if (d->dateListJob)
00568     {
00569         d->dateListJob->kill();
00570         d->dateListJob = 0;
00571     }
00572 
00573     if (d->albumListJob)
00574     {
00575         d->albumListJob->kill();
00576         d->albumListJob = 0;
00577     }
00578 
00579     if (d->tagListJob)
00580     {
00581         d->tagListJob->kill();
00582         d->tagListJob = 0;
00583     }
00584 
00585     delete d->dirWatch;
00586     d->dirWatch = 0;
00587 
00588     QDBusConnection::sessionBus().disconnect(QString(), QString(), "org.kde.KDirNotify", "FileMoved", 0, 0);
00589     QDBusConnection::sessionBus().disconnect(QString(), QString(), "org.kde.KDirNotify", "FilesAdded", 0, 0);
00590     QDBusConnection::sessionBus().disconnect(QString(), QString(), "org.kde.KDirNotify", "FilesRemoved", 0, 0);
00591 
00592     d->currentAlbum = 0;
00593     emit signalAlbumCurrentChanged(0);
00594     emit signalAlbumsCleared();
00595 
00596     d->albumPathHash.clear();
00597     d->allAlbumsIdHash.clear();
00598     d->albumRootAlbumHash.clear();
00599 
00600     // deletes all child albums as well
00601     delete d->rootPAlbum;
00602     delete d->rootTAlbum;
00603     delete d->rootDAlbum;
00604     delete d->rootSAlbum;
00605 
00606     d->rootPAlbum = 0;
00607     d->rootTAlbum = 0;
00608     d->rootDAlbum = 0;
00609     d->rootSAlbum = 0;
00610 
00611     // -- Database initialization -------------------------------------------------
00612 
00613     DatabaseAccess::setParameters(DatabaseParameters::parametersForSQLiteDefaultFile(d->dbPath),
00614                                   DatabaseAccess::MainApplication);
00615 
00616     // still suspended from above
00617     ScanController::instance()->resumeCollectionScan();
00618 
00619     ScanController::Advice advice = ScanController::instance()->databaseInitialization();
00620 
00621     QApplication::restoreOverrideCursor();
00622 
00623     switch (advice)
00624     {
00625         case ScanController::Success:
00626             break;
00627         case ScanController::ContinueWithoutDatabase:
00628         {
00629             QString errorMsg = DatabaseAccess().lastError();
00630             if (errorMsg.isEmpty())
00631             {
00632                 KMessageBox::error(0, i18n("<p>Failed to open the database. "
00633                                         "</p><p>You cannot use digiKam without a working database. "
00634                                         "digiKam will attempt to start now, but it will <b>not</b> be functional. "
00635                                         "Please check the database settings in the <b>configuration menu</b>.</p>"
00636                                         ));
00637             }
00638             else
00639             {
00640                 KMessageBox::error(0, i18n("<p>Failed to open the database. Error message from database:</p>"
00641                                            "<p><b>%1</b></p>"
00642                                            "</p><p>You cannot use digiKam without a working database. "
00643                                            "digiKam will attempt to start now, but it will <b>not</b> be functional. "
00644                                            "Please check the database settings in the <b>configuration menu</b>.</p>",
00645                                            errorMsg));
00646             }
00647             return true;
00648         }
00649         case ScanController::AbortImmediately:
00650             return false;
00651     }
00652 
00653     // -- Locale Checking ---------------------------------------------------------
00654 
00655     QString currLocale(QTextCodec::codecForLocale()->name());
00656     QString dbLocale = DatabaseAccess().db()->getSetting("Locale");
00657 
00658     // guilty until proven innocent
00659     bool localeChanged = true;
00660 
00661     if (dbLocale.isNull())
00662     {
00663         kDebug() << "No locale found in database";
00664 
00665         // Copy an existing locale from the settings file (used < 0.8)
00666         // to the database.
00667         KSharedConfig::Ptr config = KGlobal::config();
00668         KConfigGroup group = config->group("General Settings");
00669         if (group.hasKey("Locale"))
00670         {
00671             kDebug() << "Locale found in configfile";
00672             dbLocale = group.readEntry("Locale", QString());
00673 
00674             // this hack is necessary, as we used to store the entire
00675             // locale info LC_ALL (for eg: en_US.UTF-8) earlier,
00676             // we now save only the encoding (UTF-8)
00677 
00678             QString oldConfigLocale = ::setlocale(0, 0);
00679 
00680             if (oldConfigLocale == dbLocale)
00681             {
00682                 dbLocale = currLocale;
00683                 localeChanged = false;
00684                 DatabaseAccess().db()->setSetting("Locale", dbLocale);
00685             }
00686         }
00687         else
00688         {
00689             kDebug() << "No locale found in config file";
00690             dbLocale = currLocale;
00691 
00692             localeChanged = false;
00693             DatabaseAccess().db()->setSetting("Locale",dbLocale);
00694         }
00695     }
00696     else
00697     {
00698         if (dbLocale == currLocale)
00699             localeChanged = false;
00700     }
00701 
00702     if (localeChanged)
00703     {
00704         // TODO it would be better to replace all yes/no confirmation dialogs with ones that has custom
00705         // buttons that denote the actions directly, i.e.:  ["Ignore and Continue"]  ["Adjust locale"]
00706         int result =
00707             KMessageBox::warningYesNo(0,
00708                                       i18n("Your locale has changed since this "
00709                                            "album was last opened.\n"
00710                                            "Old Locale : %1, New Locale : %2\n"
00711                                            "If you have recently changed your locale, you need not be concerned.\n"
00712                                            "Please note that if you switched to a locale "
00713                                            "that does not support some of the filenames in your collection, "
00714                                            "these files may no longer be found in the collection. "
00715                                            "If you are sure that you want to "
00716                                            "continue, click 'Yes'. "
00717                                            "Otherwise, click 'No' and correct your "
00718                                            "locale setting before restarting digiKam.",
00719                                            dbLocale, currLocale));
00720         if (result != KMessageBox::Yes)
00721             exit(0);
00722 
00723         DatabaseAccess().db()->setSetting("Locale",currLocale);
00724     }
00725 
00726     // -- UUID Checking ---------------------------------------------------------
00727 
00728     QList<CollectionLocation> disappearedLocations = CollectionManager::instance()->checkHardWiredLocations();
00729     foreach (const CollectionLocation &loc, disappearedLocations)
00730     {
00731         QString locDescription;
00732         QStringList candidateIds, candidateDescriptions;
00733         CollectionManager::instance()->migrationCandidates(loc, &locDescription, &candidateIds, &candidateDescriptions);
00734         kDebug() << "Migration candidates for" << locDescription << ":" << candidateIds << candidateDescriptions;
00735 
00736         KDialog *dialog = new KDialog;
00737 
00738         QWidget *widget = new QWidget;
00739         QGridLayout *mainLayout = new QGridLayout;
00740         mainLayout->setColumnStretch(1, 1);
00741 
00742         QLabel *deviceIconLabel = new QLabel;
00743         deviceIconLabel->setPixmap(KIconLoader::global()->loadIcon("drive-harddisk", KIconLoader::NoGroup, KIconLoader::SizeHuge));
00744         mainLayout->addWidget(deviceIconLabel, 0, 0);
00745 
00746         QLabel *mainLabel = new QLabel(
00747                 i18n("<p>The collection </p><p><b>%1</b><br/>(%2)</p><p> is currently not found on your system.<br/> "
00748                      "Please choose the most appropriate option to handle this situation:</p>",
00749                       loc.label(), locDescription));
00750         mainLabel->setWordWrap(true);
00751         mainLayout->addWidget(mainLabel, 0, 1);
00752 
00753         QGroupBox *groupBox = new QGroupBox;
00754         mainLayout->addWidget(groupBox, 1, 0, 1, 2);
00755 
00756         QGridLayout *layout = new QGridLayout;
00757         layout->setColumnStretch(1, 1);
00758 
00759         QRadioButton *migrateButton = 0;
00760         QComboBox *migrateChoices = 0;
00761         if (!candidateIds.isEmpty())
00762         {
00763             migrateButton = new QRadioButton;
00764             QLabel *migrateLabel = new QLabel(
00765                     i18n("<p>The collection is still available, but the identifier changed.<br/>"
00766                         "This can be caused by restoring a backup, changing the partition layout "
00767                         "or the file system settings.<br/>"
00768                         "The collection is now located at this place:</p>"));
00769             migrateLabel->setWordWrap(true);
00770 
00771             migrateChoices = new QComboBox;
00772             for (int i=0; i<candidateIds.size(); ++i)
00773                 migrateChoices->addItem(candidateDescriptions[i], candidateIds[i]);
00774 
00775             layout->addWidget(migrateButton, 0, 0, Qt::AlignTop);
00776             layout->addWidget(migrateLabel, 0, 1);
00777             layout->addWidget(migrateChoices, 1, 1);
00778         }
00779 
00780         QRadioButton *isRemovableButton = new QRadioButton;
00781         QLabel *isRemovableLabel = new QLabel(
00782                 i18n("The collection is located on a storage device which is not always attached. "
00783                      "Mark the collection as a removable collection."));
00784         isRemovableLabel->setWordWrap(true);
00785         layout->addWidget(isRemovableButton, 2, 0, Qt::AlignTop);
00786         layout->addWidget(isRemovableLabel, 2, 1);
00787 
00788         QRadioButton *solveManuallyButton = new QRadioButton;
00789         QLabel *solveManuallyLabel = new QLabel(
00790                 i18n("Take no action now. I would like to solve the problem "
00791                      "later using the setup dialog"));
00792         solveManuallyLabel->setWordWrap(true);
00793         layout->addWidget(solveManuallyButton, 3, 0, Qt::AlignTop);
00794         layout->addWidget(solveManuallyLabel, 3, 1);
00795 
00796         groupBox->setLayout(layout);
00797 
00798         widget->setLayout(mainLayout);
00799         dialog->setCaption(i18n("Collection not found"));
00800         dialog->setMainWidget(widget);
00801         dialog->setButtons(KDialog::Ok);
00802 
00803         // Default option: If there is only one candidate, default to migration.
00804         // Otherwise default to do nothing now.
00805         if (migrateButton && candidateIds.size() == 1)
00806             migrateButton->setChecked(true);
00807         else
00808             solveManuallyButton->setChecked(true);
00809 
00810         if (dialog->exec())
00811         {
00812             if (migrateButton && migrateButton->isChecked())
00813             {
00814                 CollectionManager::instance()->migrateToVolume(loc, migrateChoices->itemData(migrateChoices->currentIndex()).toString());
00815             }
00816             else if (isRemovableButton->isChecked())
00817             {
00818                 CollectionManager::instance()->changeType(loc, CollectionLocation::TypeVolumeRemovable);
00819             }
00820         }
00821 
00822         delete dialog;
00823     }
00824 
00825     // -- ---------------------------------------------------------
00826 
00827     // measures to filter out KDirWatch signals caused by database operations
00828     d->dirWatchBlackList.clear();
00829     DatabaseParameters params = DatabaseAccess::parameters();
00830     if (params.isSQLite())
00831     {
00832         QFileInfo dbFile(params.SQLiteDatabaseFile());
00833         d->dbPathModificationDateList = d->buildDirectoryModList(dbFile);
00834         d->dirWatchBlackList << dbFile.fileName() << dbFile.fileName() + "-journal";
00835     }
00836 
00837     // check that we have one album root
00838     if (CollectionManager::instance()->allLocations().isEmpty())
00839     {
00840         if (suggestedAlbumRoot.isEmpty())
00841             Setup::execSinglePage(Setup::CollectionsPage);
00842         else
00843         {
00844             CollectionManager::instance()->addLocation(suggestedAlbumRoot);
00845             // Not needed? See bug #188959
00846             //ScanController::instance()->completeCollectionScan();
00847         }
00848     }
00849 
00850     // -- ---------------------------------------------------------
00851 
00852 #ifdef USE_THUMBS_DB
00853     QApplication::setOverrideCursor(Qt::WaitCursor);
00854 
00855     // Initialize thumbnail database
00856     QFileInfo thumbFile(d->dbPath, "thumbnails-digikam.db");
00857     ThumbnailLoadThread::initializeThumbnailDatabase(thumbFile.filePath(), new DatabaseThumbnailInfoProvider());
00858     d->dirWatchBlackList << "thumbnails-digikam.db" << "thumbnails-digikam.db-journal";
00859 
00860     QApplication::restoreOverrideCursor();
00861 #endif
00862 
00863     // -- ---------------------------------------------------------
00864 
00865 #ifdef HAVE_NEPOMUK
00866     if (checkNepomukService())
00867     {
00868         QDBusInterface serviceInterface("org.kde.nepomuk.services.digikamnepomukservice",
00869                                         "/digikamnepomukservice", "org.kde.digikam.DigikamNepomukService");
00870         kDebug() << "nepomuk service available" << serviceInterface.isValid();
00871         if (serviceInterface.isValid())
00872         {
00873             DatabaseParameters parameters = DatabaseAccess::parameters();
00874             KUrl url;
00875             parameters.insertInUrl(url);
00876             serviceInterface.call(QDBus::NoBlock, "setDatabase", url.url());
00877         }
00878     }
00879 #endif // HAVE_NEPOMUK
00880 
00881     return true;
00882 }
00883 
00884 bool AlbumManager::checkNepomukService()
00885 {
00886     bool hasNepomuk = false;
00887 
00888 #ifdef HAVE_NEPOMUK
00889     QDBusInterface serviceInterface("org.kde.nepomuk.services.digikamnepomukservice",
00890                                     "/digikamnepomukservice", "org.kde.digikam.DigikamNepomukService");
00891 
00892     // already running? (normal)
00893     if (serviceInterface.isValid())
00894         return true;
00895 
00896     // start service
00897     QDBusInterface nepomukInterface("org.kde.NepomukServer",
00898                                     "/servicemanager", "org.kde.nepomuk.ServiceManager");
00899     if (!nepomukInterface.isValid())
00900     {
00901         kDebug() << "Nepomuk server is not reachable. Cannot start Digikam Nepomuk Service";
00902         return false;
00903     }
00904 
00905     QDBusReply<QStringList> availableServicesReply = nepomukInterface.call("availableServices");
00906     if (!availableServicesReply.isValid() || !availableServicesReply.value().contains("digikamnepomukservice"))
00907     {
00908         kDebug() << "digikamnepomukservice is not available in NepomukServer";
00909         return false;
00910     }
00911 
00912     /*
00913     QEventLoop loop;
00914 
00915     if (!connect(&nepomukInterface, SIGNAL(serviceInitialized(const QString &)),
00916                  &loop, SLOT(quit())))
00917     {
00918         kDebug() << "Could not connect to Nepomuk server signal";
00919         return false;
00920     }
00921 
00922     QTimer::singleShot(1000, &loop, SLOT(quit()));
00923     */
00924 
00925     kDebug() << "Trying to start up digikamnepomukservice";
00926     nepomukInterface.call(QDBus::NoBlock, "startService", "digikamnepomukservice");
00927 
00928     /*
00929     // wait (at most 1sec) for service to start up
00930     loop.exec();
00931     */
00932     hasNepomuk = true;
00933 #endif // HAVE_NEPOMUK
00934 
00935     return hasNepomuk;
00936 }
00937 
00938 void AlbumManager::startScan()
00939 {
00940     if (!d->changed)
00941         return;
00942     d->changed = false;
00943 
00944     // create dir watch
00945     d->dirWatch = new KDirWatch(this);
00946     connect(d->dirWatch, SIGNAL(dirty(const QString&)),
00947             this, SLOT(slotDirWatchDirty(const QString&)));
00948 
00949     KDirWatch::Method m = d->dirWatch->internalMethod();
00950     QString mName("FAM");
00951     if (m == KDirWatch::DNotify)
00952         mName = QString("DNotify");
00953     else if (m == KDirWatch::Stat)
00954         mName = QString("Stat");
00955     else if (m == KDirWatch::INotify)
00956         mName = QString("INotify");
00957     kDebug() << "KDirWatch method = " << mName;
00958 
00959     // connect to KDirNotify
00960 
00961     QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KDirNotify", "FileMoved",
00962                                           this, SLOT(slotKioFileMoved(const QString&, const QString&)));
00963     QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KDirNotify", "FilesAdded",
00964                                           this, SLOT(slotKioFilesAdded(const QString&)));
00965     QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KDirNotify", "FilesRemoved",
00966                                           this, SLOT(slotKioFilesDeleted(const QStringList&)));
00967 
00968     // create root albums
00969     d->rootPAlbum = new PAlbum(i18n("My Albums"));
00970     insertPAlbum(d->rootPAlbum, 0);
00971 
00972     d->rootTAlbum = new TAlbum(i18n("My Tags"), 0, true);
00973     insertTAlbum(d->rootTAlbum, 0);
00974 
00975     d->rootSAlbum = new SAlbum(i18n("My Searches"), 0, true);
00976 
00977     d->rootDAlbum = new DAlbum(QDate(), true);
00978 
00979     // create albums for album roots
00980     QList<CollectionLocation> locations = CollectionManager::instance()->allAvailableLocations();
00981     foreach(const CollectionLocation& location, locations)
00982         addAlbumRoot(location);
00983 
00984     // listen to location status changes
00985     connect(CollectionManager::instance(), SIGNAL(locationStatusChanged(const CollectionLocation &, int)),
00986             this, SLOT(slotCollectionLocationStatusChanged(const CollectionLocation &, int)));
00987     connect(CollectionManager::instance(), SIGNAL(locationPropertiesChanged(const CollectionLocation &)),
00988             this, SLOT(slotCollectionLocationPropertiesChanged(const CollectionLocation &)));
00989 
00990     // reload albums
00991     refresh();
00992 
00993     // listen to album database changes
00994     connect(DatabaseAccess::databaseWatch(), SIGNAL(albumChange(const AlbumChangeset &)),
00995             this, SLOT(slotAlbumChange(const AlbumChangeset &)));
00996     connect(DatabaseAccess::databaseWatch(), SIGNAL(tagChange(const TagChangeset &)),
00997             this, SLOT(slotTagChange(const TagChangeset &)));
00998     connect(DatabaseAccess::databaseWatch(), SIGNAL(searchChange(const SearchChangeset &)),
00999             this, SLOT(slotSearchChange(const SearchChangeset &)));
01000     // listen to collection image changes
01001     connect(DatabaseAccess::databaseWatch(), SIGNAL(collectionImageChange(const CollectionImageChangeset &)),
01002             this, SLOT(slotCollectionImageChange(const CollectionImageChangeset &)));
01003     connect(DatabaseAccess::databaseWatch(), SIGNAL(imageTagChange(const ImageTagChangeset &)),
01004             this, SLOT(slotImageTagChange(const ImageTagChangeset &)));
01005 
01006     emit signalAllAlbumsLoaded();
01007 }
01008 
01009 void AlbumManager::slotCollectionLocationStatusChanged(const CollectionLocation& location, int oldStatus)
01010 {
01011     // not before initialization
01012     if (!d->rootPAlbum)
01013         return;
01014 
01015     if (location.status() == CollectionLocation::LocationAvailable
01016         && oldStatus != CollectionLocation::LocationAvailable)
01017     {
01018         addAlbumRoot(location);
01019         // New albums have possibly appeared
01020         scanPAlbums();
01021     }
01022     else if (oldStatus == CollectionLocation::LocationAvailable
01023              && location.status() != CollectionLocation::LocationAvailable)
01024     {
01025         removeAlbumRoot(location);
01026         // Albums have possibly disappeared
01027         scanPAlbums();
01028     }
01029 }
01030 
01031 void AlbumManager::slotCollectionLocationPropertiesChanged(const CollectionLocation& location)
01032 {
01033     PAlbum *album = d->albumRootAlbumHash.value(location.id());
01034     if (album)
01035     {
01036         QString newLabel = d->labelForAlbumRootAlbum(location);
01037         kDebug() << newLabel << album->title();
01038         if (album->title() != newLabel)
01039         {
01040             album->setTitle(newLabel);
01041             emit signalAlbumRenamed(album);
01042         }
01043     }
01044 }
01045 
01046 void AlbumManager::addAlbumRoot(const CollectionLocation& location)
01047 {
01048     if (!d->dirWatch->contains(location.albumRootPath()))
01049         d->dirWatch->addDir(location.albumRootPath(), KDirWatch::WatchSubDirs);
01050 
01051     PAlbum *album = d->albumRootAlbumHash.value(location.id());
01052     if (!album)
01053     {
01054         // Create a PAlbum for the Album Root.
01055         QString label = d->labelForAlbumRootAlbum(location);
01056         album = new PAlbum(location.id(), label);
01057 
01058         // insert album root created into hash
01059         d->albumRootAlbumHash.insert(location.id(), album);
01060     }
01061 }
01062 
01063 void AlbumManager::removeAlbumRoot(const CollectionLocation& location)
01064 {
01065     d->dirWatch->removeDir(location.albumRootPath());
01066     // retrieve and remove from hash
01067     PAlbum *album = d->albumRootAlbumHash.take(location.id());
01068     if (album)
01069     {
01070         // delete album and all its children
01071         removePAlbum(album);
01072     }
01073 }
01074 
01075 void AlbumManager::refresh()
01076 {
01077     scanPAlbums();
01078     scanTAlbums();
01079     scanSAlbums();
01080     scanDAlbums();
01081 }
01082 
01083 void AlbumManager::prepareItemCounts()
01084 {
01085     // There is no way to find out if any data we had collected
01086     // previously is still valid - recompute
01087     scanDAlbums();
01088     getAlbumItemsCount();
01089     getTagItemsCount();
01090 }
01091 
01092 void AlbumManager::scanPAlbums()
01093 {
01094     d->scanPAlbumsTimer->stop();
01095 
01096     // first insert all the current normal PAlbums into a map for quick lookup
01097     QHash<int, PAlbum *> oldAlbums;
01098     AlbumIterator it(d->rootPAlbum);
01099     while (it.current())
01100     {
01101         PAlbum* a = (PAlbum*)(*it);
01102         oldAlbums[a->id()] = a;
01103         ++it;
01104     }
01105 
01106     // scan db and get a list of all albums
01107     QList<AlbumInfo> currentAlbums = DatabaseAccess().db()->scanAlbums();
01108 
01109     // sort by relative path so that parents are created before children
01110     qSort(currentAlbums);
01111 
01112     QList<AlbumInfo> newAlbums;
01113 
01114     // go through all the Albums and see which ones are already present
01115     foreach (const AlbumInfo& info, currentAlbums)
01116     {
01117         // check that location of album is available
01118         if (CollectionManager::instance()->locationForAlbumRootId(info.albumRootId).isAvailable())
01119         {
01120             if (oldAlbums.contains(info.id))
01121                 oldAlbums.remove(info.id);
01122             else
01123                 newAlbums << info;
01124         }
01125     }
01126 
01127     // now oldAlbums contains all the deleted albums and
01128     // newAlbums contains all the new albums
01129 
01130     // delete old albums, informing all frontends
01131 
01132     // The albums have to be removed with children being removed first,
01133     // removePAlbum takes care of that.
01134     // So we only feed it the albums from oldAlbums topmost in hierarchy.
01135     QSet<PAlbum *> topMostOldAlbums;
01136     foreach (PAlbum *album, oldAlbums)
01137     {
01138         if (!album->parent() || !oldAlbums.contains(album->parent()->id()))
01139             topMostOldAlbums << album;
01140     }
01141 
01142     foreach(PAlbum *album, topMostOldAlbums)
01143     {
01144         // this might look like there is memory leak here, since removePAlbum
01145         // doesn't delete albums and looks like child Albums don't get deleted.
01146         // But when the parent album gets deleted, the children are also deleted.
01147         removePAlbum(album);
01148     }
01149 
01150     // sort by relative path so that parents are created before children
01151     qSort(newAlbums);
01152 
01153     // create all new albums
01154     foreach (const AlbumInfo& info, newAlbums)
01155     {
01156         if (info.relativePath.isEmpty())
01157             continue;
01158 
01159         PAlbum *album, *parent;
01160         if (info.relativePath == "/")
01161         {
01162             // Albums that represent the root directory of an album root
01163             // We have them as here new albums first time after their creation
01164 
01165             parent = d->rootPAlbum;
01166             album  = d->albumRootAlbumHash.value(info.albumRootId);
01167 
01168             if (!album)
01169             {
01170                 kError() << "Did not find album root album in hash";
01171                 continue;
01172             }
01173 
01174             // it has been created from the collection location
01175             // with album root id, parentPath "/" and a name, but no album id yet.
01176             album->m_id = info.id;
01177         }
01178         else
01179         {
01180             // last section, no slash
01181             QString name = info.relativePath.section('/', -1, -1);
01182             // all but last sections, leading slash, no trailing slash
01183             QString parentPath = info.relativePath.section('/', 0, -2);
01184 
01185             if (parentPath.isEmpty())
01186                 parent = d->albumRootAlbumHash.value(info.albumRootId);
01187             else
01188                 parent = d->albumPathHash.value(PAlbumPath(info.albumRootId, parentPath));
01189 
01190             if (!parent)
01191             {
01192                 kError() <<  "Could not find parent with url: "
01193                               << parentPath << " for: " << info.relativePath;
01194                 continue;
01195             }
01196 
01197             // Create the new album
01198             album       = new PAlbum(info.albumRootId, parentPath, name, info.id);
01199         }
01200 
01201         album->m_caption  = info.caption;
01202         album->m_category = info.category;
01203         album->m_date     = info.date;
01204 
01205         if (info.iconAlbumRootId)
01206         {
01207             QString albumRootPath = CollectionManager::instance()->albumRootPath(info.iconAlbumRootId);
01208             if (!albumRootPath.isNull())
01209                 album->m_icon = albumRootPath + info.iconRelativePath;
01210         }
01211 
01212         insertPAlbum(album, parent);
01213     }
01214 
01215     getAlbumItemsCount();
01216 }
01217 
01218 void AlbumManager::updateChangedPAlbums()
01219 {
01220     d->updatePAlbumsTimer->stop();
01221 
01222     // scan db and get a list of all albums
01223     QList<AlbumInfo> currentAlbums = DatabaseAccess().db()->scanAlbums();
01224 
01225     bool needScanPAlbums = false;
01226 
01227     // Find the AlbumInfo for each id in changedPAlbums
01228     foreach (int id, d->changedPAlbums)
01229     {
01230         foreach (const AlbumInfo& info, currentAlbums)
01231         {
01232             if (info.id == id)
01233             {
01234                 d->changedPAlbums.remove(info.id);
01235 
01236                 PAlbum *album = findPAlbum(info.id);
01237                 if (album)
01238                 {
01239                     // Renamed?
01240                     if (info.relativePath != "/")
01241                     {
01242                         // Handle rename of album name
01243                         // last section, no slash
01244                         QString name = info.relativePath.section('/', -1, -1);
01245                         QString parentPath = info.relativePath;
01246                         parentPath.chop(name.length());
01247                         if (parentPath != album->m_parentPath || info.albumRootId != album->albumRootId())
01248                         {
01249                             // Handle actual move operations: trigger ScanPAlbums
01250                             needScanPAlbums = true;
01251                             removePAlbum(album);
01252                             break;
01253                         }
01254                         else if (name != album->title())
01255                         {
01256                             album->setTitle(name);
01257                             updateAlbumPathHash();
01258                             emit signalAlbumRenamed(album);
01259                         }
01260                     }
01261 
01262                     // Update caption, collection, date
01263                     album->m_caption = info.caption;
01264                     album->m_category  = info.category;
01265                     album->m_date    = info.date;
01266 
01267                     // Icon changed?
01268                     QString icon;
01269                     if (info.iconAlbumRootId)
01270                     {
01271                         QString albumRootPath = CollectionManager::instance()->albumRootPath(info.iconAlbumRootId);
01272                         if (!albumRootPath.isNull())
01273                             icon = albumRootPath + info.iconRelativePath;
01274                     }
01275                     if (icon != album->m_icon)
01276                     {
01277                         album->m_icon = icon;
01278                         emit signalAlbumIconChanged(album);
01279                     }
01280                 }
01281             }
01282         }
01283     }
01284 
01285     if (needScanPAlbums)
01286         scanPAlbums();
01287 }
01288 
01289 void AlbumManager::getAlbumItemsCount()
01290 {
01291     d->albumItemCountTimer->stop();
01292 
01293     if (!AlbumSettings::instance()->getShowFolderTreeViewItemsCount())
01294         return;
01295 
01296     // List albums using kioslave
01297 
01298     if (d->albumListJob)
01299     {
01300         d->albumListJob->kill();
01301         d->albumListJob = 0;
01302     }
01303 
01304     DatabaseUrl u = DatabaseUrl::albumUrl();
01305 
01306     d->albumListJob = ImageLister::startListJob(u);
01307     d->albumListJob->addMetaData("folders", "true");
01308 
01309     connect(d->albumListJob, SIGNAL(result(KJob*)),
01310             this, SLOT(slotAlbumsJobResult(KJob*)));
01311 
01312     connect(d->albumListJob, SIGNAL(data(KIO::Job*, const QByteArray&)),
01313             this, SLOT(slotAlbumsJobData(KIO::Job*, const QByteArray&)));
01314 }
01315 
01316 void AlbumManager::scanTAlbums()
01317 {
01318     d->scanTAlbumsTimer->stop();
01319 
01320     // list TAlbums directly from the db
01321     // first insert all the current TAlbums into a map for quick lookup
01322     typedef QMap<int, TAlbum*> TagMap;
01323     TagMap tmap;
01324 
01325     tmap.insert(0, d->rootTAlbum);
01326 
01327     AlbumIterator it(d->rootTAlbum);
01328     while (it.current())
01329     {
01330         TAlbum* t = (TAlbum*)(*it);
01331         tmap.insert(t->id(), t);
01332         ++it;
01333     }
01334 
01335     // Retrieve the list of tags from the database
01336     TagInfo::List tList = DatabaseAccess().db()->scanTags();
01337 
01338     // sort the list. needed because we want the tags can be read in any order,
01339     // but we want to make sure that we are ensure to find the parent TAlbum
01340     // for a new TAlbum
01341 
01342     {
01343         QHash<int, TAlbum*> tagHash;
01344 
01345         // insert items into a dict for quick lookup
01346         for (TagInfo::List::const_iterator iter = tList.constBegin(); iter != tList.constEnd(); ++iter)
01347         {
01348             TagInfo info  = *iter;
01349             TAlbum* album = new TAlbum(info.name, info.id);
01350             if (info.icon.isNull())
01351             {
01352                 // album image icon
01353                 QString albumRootPath = CollectionManager::instance()->albumRootPath(info.iconAlbumRootId);
01354                 album->m_icon         = albumRootPath + info.iconRelativePath;
01355             }
01356             else
01357             {
01358                 // system icon
01359                 album->m_icon = info.icon;
01360             }
01361             album->m_pid = info.pid;
01362             tagHash.insert(info.id, album);
01363         }
01364         tList.clear();
01365 
01366         // also add root tag
01367         TAlbum* rootTag = new TAlbum("root", 0, true);
01368         tagHash.insert(0, rootTag);
01369 
01370         // build tree
01371         for (QHash<int, TAlbum*>::const_iterator iter = tagHash.constBegin();
01372              iter != tagHash.constEnd(); ++iter )
01373         {
01374             TAlbum* album = *iter;
01375             if (album->m_id == 0)
01376                 continue;
01377 
01378             TAlbum* parent = tagHash.value(album->m_pid);
01379             if (parent)
01380             {
01381                 album->setParent(parent);
01382             }
01383             else
01384             {
01385                 kWarning() << "Failed to find parent tag for tag "
01386                                 << album->m_title
01387                                 << " with pid "
01388                                 << album->m_pid;
01389             }
01390         }
01391 
01392         // now insert the items into the list. becomes sorted
01393         AlbumIterator it(rootTag);
01394         while (it.current())
01395         {
01396             TagInfo info;
01397             TAlbum* album = (TAlbum*)it.current();
01398             info.id       = album->m_id;
01399             info.pid      = album->m_pid;
01400             info.name     = album->m_title;
01401             info.icon     = album->m_icon;
01402             tList.append(info);
01403             ++it;
01404         }
01405 
01406         // this will also delete all child albums
01407         delete rootTag;
01408     }
01409 
01410     for (TagInfo::List::const_iterator it = tList.constBegin(); it != tList.constEnd(); ++it)
01411     {
01412         TagInfo info = *it;
01413 
01414         // check if we have already added this tag
01415         if (tmap.contains(info.id))
01416             continue;
01417 
01418         // Its a new album. Find the parent of the album
01419         TagMap::iterator iter = tmap.find(info.pid);
01420         if (iter == tmap.end())
01421         {
01422             kWarning() << "Failed to find parent tag for tag "
01423                             << info.name
01424                             << " with pid "
01425                             << info.pid;
01426             continue;
01427         }
01428 
01429         TAlbum* parent = iter.value();
01430 
01431         // Create the new TAlbum
01432         TAlbum* album = new TAlbum(info.name, info.id, false);
01433         album->m_icon = info.icon;
01434         insertTAlbum(album, parent);
01435 
01436         // also insert it in the map we are doing lookup of parent tags
01437         tmap.insert(info.id, album);
01438     }
01439 
01440     getTagItemsCount();
01441 }
01442 
01443 void AlbumManager::getTagItemsCount()
01444 {
01445     d->tagItemCountTimer->stop();
01446 
01447     if (!AlbumSettings::instance()->getShowFolderTreeViewItemsCount())
01448         return;
01449 
01450     // List tags using kioslave
01451 
01452     if (d->tagListJob)
01453     {
01454         d->tagListJob->kill();
01455         d->tagListJob = 0;
01456     }
01457 
01458     DatabaseUrl u = DatabaseUrl::fromTagIds(QList<int>());
01459 
01460     d->tagListJob = ImageLister::startListJob(u);
01461     d->tagListJob->addMetaData("folders", "true");
01462 
01463     connect(d->tagListJob, SIGNAL(result(KJob*)),
01464             this, SLOT(slotTagsJobResult(KJob*)));
01465 
01466     connect(d->tagListJob, SIGNAL(data(KIO::Job*, const QByteArray&)),
01467             this, SLOT(slotTagsJobData(KIO::Job*, const QByteArray&)));
01468 }
01469 
01470 void AlbumManager::scanSAlbums()
01471 {
01472     d->scanSAlbumsTimer->stop();
01473 
01474     // list SAlbums directly from the db
01475     // first insert all the current SAlbums into a map for quick lookup
01476     QMap<int, SAlbum*> oldSearches;
01477 
01478     AlbumIterator it(d->rootSAlbum);
01479     while (it.current())
01480     {
01481         SAlbum* search = (SAlbum*)(*it);
01482         oldSearches[search->id()] = search;
01483         ++it;
01484     }
01485 
01486     // scan db and get a list of all albums
01487     QList<SearchInfo> currentSearches = DatabaseAccess().db()->scanSearches();
01488 
01489     QList<SearchInfo> newSearches;
01490 
01491     // go through all the Albums and see which ones are already present
01492     foreach (const SearchInfo& info, currentSearches)
01493     {
01494         if (oldSearches.contains(info.id))
01495         {
01496             SAlbum *album = oldSearches[info.id];
01497             if (info.name != album->title()
01498                 || info.type != album->type()
01499                 || info.query != album->query())
01500             {
01501                 QString oldName = album->title();
01502 
01503                 album->setSearch(info.type, info.query);
01504                 album->setTitle(info.name);
01505                 if (oldName != album->title())
01506                     emit signalAlbumRenamed(album);
01507                 emit signalSearchUpdated(album);
01508             }
01509 
01510             oldSearches.remove(info.id);
01511         }
01512         else
01513             newSearches << info;
01514     }
01515 
01516     // remove old albums that have been deleted
01517     foreach (SAlbum *album, oldSearches)
01518     {
01519         emit signalAlbumAboutToBeDeleted(album);
01520         d->allAlbumsIdHash.remove(album->globalID());
01521         emit signalAlbumDeleted(album);
01522         delete album;
01523         emit signalAlbumHasBeenDeleted(album);
01524     }
01525 
01526     // add new albums
01527     foreach (const SearchInfo& info, newSearches)
01528     {
01529         SAlbum* album = new SAlbum(info.name, info.id);
01530         emit signalAlbumAboutToBeAdded(album, d->rootSAlbum, d->rootSAlbum->lastChild());
01531         album->setSearch(info.type, info.query);
01532         album->setParent(d->rootSAlbum);
01533         d->allAlbumsIdHash[album->globalID()] = album;
01534         emit signalAlbumAdded(album);
01535     }
01536 }
01537 
01538 void AlbumManager::scanDAlbums()
01539 {
01540     d->scanDAlbumsTimer->stop();
01541 
01542     // List dates using kioslave:
01543     // The kioslave has a special mode listing the dates
01544     // for which there are images in the DB.
01545 
01546     if (d->dateListJob)
01547     {
01548         d->dateListJob->kill();
01549         d->dateListJob = 0;
01550     }
01551 
01552     DatabaseUrl u = DatabaseUrl::dateUrl();
01553 
01554     d->dateListJob = ImageLister::startListJob(u);
01555     d->dateListJob->addMetaData("folders", "true");
01556 
01557     connect(d->dateListJob, SIGNAL(result(KJob*)),
01558             this, SLOT(slotDatesJobResult(KJob*)));
01559 
01560     connect(d->dateListJob, SIGNAL(data(KIO::Job*, const QByteArray&)),
01561             this, SLOT(slotDatesJobData(KIO::Job*, const QByteArray&)));
01562 }
01563 
01564 AlbumList AlbumManager::allPAlbums() const
01565 {
01566     AlbumList list;
01567     if (d->rootPAlbum)
01568         list.append(d->rootPAlbum);
01569 
01570     AlbumIterator it(d->rootPAlbum);
01571     while (it.current())
01572     {
01573         list.append(*it);
01574         ++it;
01575     }
01576 
01577     return list;
01578 }
01579 
01580 AlbumList AlbumManager::allTAlbums() const
01581 {
01582     AlbumList list;
01583     if (d->rootTAlbum)
01584         list.append(d->rootTAlbum);
01585 
01586     AlbumIterator it(d->rootTAlbum);
01587     while (it.current())
01588     {
01589         list.append(*it);
01590         ++it;
01591     }
01592 
01593     return list;
01594 }
01595 
01596 AlbumList AlbumManager::allSAlbums() const
01597 {
01598     AlbumList list;
01599     if (d->rootSAlbum)
01600         list.append(d->rootSAlbum);
01601 
01602     AlbumIterator it(d->rootSAlbum);
01603     while (it.current())
01604     {
01605         list.append(*it);
01606         ++it;
01607     }
01608 
01609     return list;
01610 }
01611 
01612 AlbumList AlbumManager::allDAlbums() const
01613 {
01614     AlbumList list;
01615     if (d->rootDAlbum)
01616         list.append(d->rootDAlbum);
01617 
01618     AlbumIterator it(d->rootDAlbum);
01619     while (it.current())
01620     {
01621         list.append(*it);
01622         ++it;
01623     }
01624 
01625     return list;
01626 }
01627 
01628 void AlbumManager::setCurrentAlbum(Album *album)
01629 {
01630     d->currentAlbum = album;
01631     emit signalAlbumCurrentChanged(album);
01632 }
01633 
01634 Album* AlbumManager::currentAlbum() const
01635 {
01636     return d->currentAlbum;
01637 }
01638 
01639 PAlbum* AlbumManager::findPAlbum(const KUrl& url) const
01640 {
01641     CollectionLocation location = CollectionManager::instance()->locationForUrl(url);
01642     if (location.isNull())
01643         return 0;
01644     return d->albumPathHash.value(PAlbumPath(location.id(), CollectionManager::instance()->album(location, url)));
01645 }
01646 
01647 PAlbum* AlbumManager::findPAlbum(int id) const
01648 {
01649     if (!d->rootPAlbum)
01650         return 0;
01651 
01652     int gid = d->rootPAlbum->globalID() + id;
01653 
01654     return (PAlbum*)(d->allAlbumsIdHash.value(gid));
01655 }
01656 
01657 TAlbum* AlbumManager::findTAlbum(int id) const
01658 {
01659     if (!d->rootTAlbum)
01660         return 0;
01661 
01662     int gid = d->rootTAlbum->globalID() + id;
01663 
01664     return (TAlbum*)(d->allAlbumsIdHash.value(gid));
01665 }
01666 
01667 SAlbum* AlbumManager::findSAlbum(int id) const
01668 {
01669     if (!d->rootSAlbum)
01670         return 0;
01671 
01672     int gid = d->rootSAlbum->globalID() + id;
01673 
01674     return (SAlbum*)(d->allAlbumsIdHash.value(gid));
01675 }
01676 
01677 DAlbum* AlbumManager::findDAlbum(int id) const
01678 {
01679     if (!d->rootDAlbum)
01680         return 0;
01681 
01682     int gid = d->rootDAlbum->globalID() + id;
01683 
01684     return (DAlbum*)(d->allAlbumsIdHash.value(gid));
01685 }
01686 
01687 Album* AlbumManager::findAlbum(int gid) const
01688 {
01689     return d->allAlbumsIdHash.value(gid);
01690 }
01691 
01692 TAlbum* AlbumManager::findTAlbum(const QString& tagPath) const
01693 {
01694     // handle gracefully with or without leading slash
01695     bool withLeadingSlash = tagPath.startsWith('/');
01696     AlbumIterator it(d->rootTAlbum);
01697     while (it.current())
01698     {
01699         TAlbum *talbum = static_cast<TAlbum *>(*it);
01700         if (talbum->tagPath(withLeadingSlash) == tagPath)
01701             return talbum;
01702         ++it;
01703     }
01704     return 0;
01705 
01706 }
01707 
01708 SAlbum* AlbumManager::findSAlbum(const QString& name) const
01709 {
01710     for (Album* album = d->rootSAlbum->firstChild(); album; album = album->next())
01711     {
01712         if (album->title() == name)
01713             return (SAlbum*)album;
01714     }
01715     return 0;
01716 }
01717 
01718 void AlbumManager::addGuardedPointer(Album *album, Album **pointer)
01719 {
01720     if (album)
01721         d->guardedPointers.insert(album, pointer);
01722 }
01723 
01724 void AlbumManager::removeGuardedPointer(Album *album, Album **pointer)
01725 {
01726     if (album)
01727         d->guardedPointers.remove(album, pointer);
01728 }
01729 
01730 void AlbumManager::changeGuardedPointer(Album *oldAlbum, Album *album, Album **pointer)
01731 {
01732     if (oldAlbum)
01733         d->guardedPointers.remove(oldAlbum, pointer);
01734     if (album)
01735         d->guardedPointers.insert(album, pointer);
01736 }
01737 
01738 void AlbumManager::invalidateGuardedPointers(Album *album)
01739 {
01740     if (!album)
01741         return;
01742     QMultiHash<Album*, Album**>::iterator it = d->guardedPointers.find(album);
01743     for( ; it != d->guardedPointers.end() && it.key() == album; ++it)
01744     {
01745         if (it.value())
01746             *(it.value()) = 0;
01747     }
01748 }
01749 
01750 PAlbum* AlbumManager::createPAlbum(const QString& albumRootPath, const QString& name,
01751                                    const QString& caption, const QDate& date,
01752                                    const QString& category,
01753                                    QString& errMsg)
01754 {
01755     CollectionLocation location = CollectionManager::instance()->locationForAlbumRootPath(albumRootPath);
01756     return createPAlbum(location, name, caption, date, category, errMsg);
01757 }
01758 
01759 PAlbum* AlbumManager::createPAlbum(const CollectionLocation& location, const QString& name,
01760                                    const QString& caption, const QDate& date,
01761                                    const QString& category,
01762                                    QString& errMsg)
01763 {
01764     if (location.isNull() || !location.isAvailable())
01765     {
01766         errMsg = i18n("The collection location supplied is invalid or currently not available.");
01767         return 0;
01768     }
01769 
01770     PAlbum *album = d->albumRootAlbumHash.value(location.id());
01771 
01772     if (!album)
01773     {
01774         errMsg = "No album for collection location: Internal error";
01775         return 0;
01776     }
01777 
01778     return createPAlbum(album, name, caption, date, category, errMsg);
01779 }
01780 
01781 
01782 PAlbum* AlbumManager::createPAlbum(PAlbum*        parent,
01783                                    const QString& name,
01784                                    const QString& caption,
01785                                    const QDate&   date,
01786                                    const QString& category,
01787                                    QString&       errMsg)
01788 {
01789     if (!parent)
01790     {
01791         errMsg = i18n("No parent found for album.");
01792         return 0;
01793     }
01794 
01795     // sanity checks
01796     if (name.isEmpty())
01797     {
01798         errMsg = i18n("Album name cannot be empty.");
01799         return 0;
01800     }
01801 
01802     if (name.contains("/"))
01803     {
01804         errMsg = i18n("Album name cannot contain '/'.");
01805         return 0;
01806     }
01807 
01808     if (parent->isRoot())
01809     {
01810         errMsg = i18n("createPAlbum does not accept the root album as parent.");
01811         return 0;
01812     }
01813 
01814     QString albumPath = parent->isAlbumRoot() ? ('/' + name) : (parent->albumPath() + '/' + name);
01815     int albumRootId   = parent->albumRootId();
01816 
01817     // first check if we have a sibling album with the same name
01818     PAlbum *child = (PAlbum *)parent->m_firstChild;
01819     while (child)
01820     {
01821         if (child->albumRootId() == albumRootId && child->albumPath() == albumPath)
01822         {
01823             errMsg = i18n("An existing album has the same name.");
01824             return 0;
01825         }
01826         child = (PAlbum *)child->m_next;
01827     }
01828 
01829     DatabaseUrl url = parent->databaseUrl();
01830     url.addPath(name);
01831     KUrl fileUrl    = url.fileUrl();
01832 
01833     if (!KIO::NetAccess::mkdir(fileUrl, qApp->activeWindow()))
01834     {
01835         errMsg = i18n("Failed to create directory,");
01836         return 0;
01837     }
01838 
01839     ChangingDB changing(d);
01840     int id = DatabaseAccess().db()->addAlbum(albumRootId, albumPath, caption, date, category);
01841 
01842     if (id == -1)
01843     {
01844         errMsg = i18n("Failed to add album to database");
01845         return 0;
01846     }
01847 
01848     QString parentPath;
01849     if (!parent->isAlbumRoot())
01850         parentPath = parent->albumPath();
01851 
01852     PAlbum *album    = new PAlbum(albumRootId, parentPath, name, id);
01853     album->m_caption = caption;
01854     album->m_category  = category;
01855     album->m_date    = date;
01856 
01857     insertPAlbum(album, parent);
01858 
01859     return album;
01860 }
01861 
01862 bool AlbumManager::renamePAlbum(PAlbum* album, const QString& newName,
01863                                 QString& errMsg)
01864 {
01865     if (!album)
01866     {
01867         errMsg = i18n("No such album");
01868         return false;
01869     }
01870 
01871     if (album == d->rootPAlbum)
01872     {
01873         errMsg = i18n("Cannot rename root album");
01874         return false;
01875     }
01876 
01877     if (album->isAlbumRoot())
01878     {
01879         errMsg = i18n("Cannot rename album root album");
01880         return false;
01881     }
01882 
01883     if (newName.contains("/"))
01884     {
01885         errMsg = i18n("Album name cannot contain '/'");
01886         return false;
01887     }
01888 
01889     // first check if we have another sibling with the same name
01890     Album *sibling = album->m_parent->m_firstChild;
01891     while (sibling)
01892     {
01893         if (sibling->title() == newName)
01894         {
01895             errMsg = i18n("Another album with the same name already exists.\n"
01896                           "Please choose another name.");
01897             return false;
01898         }
01899         sibling = sibling->m_next;
01900     }
01901 
01902     QString oldAlbumPath = album->albumPath();
01903 
01904     KUrl oldUrl = album->fileUrl();
01905     album->setTitle(newName);
01906     album->m_path = newName;
01907     KUrl newUrl = album->fileUrl();
01908 
01909     QString newAlbumPath = album->albumPath();
01910 
01911     // We use a private shortcut around collection scanner noticing our changes,
01912     // we rename them directly. Faster.
01913     ScanController::instance()->suspendCollectionScan();
01914 
01915     KIO::Job *job = KIO::rename(oldUrl, newUrl, KIO::HideProgressInfo);
01916     if (!KIO::NetAccess::synchronousRun(job, 0))
01917     {
01918         errMsg = i18n("Failed to rename Album");
01919         return false;
01920     }
01921 
01922     // now rename the album and subalbums in the database
01923 
01924     {
01925         DatabaseAccess access;
01926         ChangingDB changing(d);
01927         access.db()->renameAlbum(album->id(), album->albumRootId(), album->albumPath());
01928 
01929         PAlbum* subAlbum = 0;
01930         AlbumIterator it(album);
01931         while ((subAlbum = static_cast<PAlbum*>(it.current())) != 0)
01932         {
01933             subAlbum->m_parentPath = newAlbumPath + subAlbum->m_parentPath.mid(oldAlbumPath.length());
01934             access.db()->renameAlbum(subAlbum->id(), album->albumRootId(), subAlbum->albumPath());
01935             ++it;
01936         }
01937     }
01938 
01939     updateAlbumPathHash();
01940     emit signalAlbumRenamed(album);
01941 
01942     ScanController::instance()->resumeCollectionScan();
01943 
01944     return true;
01945 }
01946 
01947 void AlbumManager::updateAlbumPathHash()
01948 {
01949     // Update AlbumDict. basically clear it and rebuild from scratch
01950     {
01951         d->albumPathHash.clear();
01952         AlbumIterator it(d->rootPAlbum);
01953         PAlbum* subAlbum = 0;
01954         while ((subAlbum = (PAlbum*)it.current()) != 0)
01955         {
01956             d->albumPathHash[subAlbum] = subAlbum;
01957             ++it;
01958         }
01959     }
01960 
01961 }
01962 
01963 bool AlbumManager::updatePAlbumIcon(PAlbum *album, qlonglong iconID, QString& errMsg)
01964 {
01965     if (!album)
01966     {
01967         errMsg = i18n("No such album");
01968         return false;
01969     }
01970 
01971     if (album == d->rootPAlbum)
01972     {
01973         errMsg = i18n("Cannot edit root album");
01974         return false;
01975     }
01976 
01977     {
01978         DatabaseAccess access;
01979         ChangingDB changing(d);
01980         access.db()->setAlbumIcon(album->id(), iconID);
01981         QString iconRelativePath;
01982         int iconAlbumRootId;
01983         if (access.db()->getAlbumIcon(album->id(), &iconAlbumRootId, &iconRelativePath))
01984         {
01985             QString albumRootPath = CollectionManager::instance()->albumRootPath(iconAlbumRootId);
01986             album->m_icon = albumRootPath + iconRelativePath;
01987         }
01988         else
01989             album->m_icon.clear();
01990     }
01991 
01992     emit signalAlbumIconChanged(album);
01993 
01994     return true;
01995 }
01996 
01997 TAlbum* AlbumManager::createTAlbum(TAlbum* parent, const QString& name,
01998                                    const QString& iconkde, QString& errMsg)
01999 {
02000     if (!parent)
02001     {
02002         errMsg = i18n("No parent found for tag");
02003         return 0;
02004     }
02005 
02006     // sanity checks
02007     if (name.isEmpty())
02008     {
02009         errMsg = i18n("Tag name cannot be empty");
02010         return 0;
02011     }
02012 
02013     if (name.contains("/"))
02014     {
02015         errMsg = i18n("Tag name cannot contain '/'");
02016         return 0;
02017     }
02018 
02019     // first check if we have another album with the same name
02020     Album *child = parent->m_firstChild;
02021     while (child)
02022     {
02023         if (child->title() == name)
02024         {
02025             errMsg = i18n("Tag name already exists");
02026             return 0;
02027         }
02028         child = child->m_next;
02029     }
02030 
02031     ChangingDB changing(d);
02032     int id = DatabaseAccess().db()->addTag(parent->id(), name, iconkde, 0);
02033     if (id == -1)
02034     {
02035         errMsg = i18n("Failed to add tag to database");
02036         return 0;
02037     }
02038 
02039     TAlbum *album = new TAlbum(name, id, false);
02040     album->m_icon = iconkde;
02041 
02042     insertTAlbum(album, parent);
02043 
02044     return album;
02045 }
02046 
02047 AlbumList AlbumManager::findOrCreateTAlbums(const QStringList& tagPaths)
02048 {
02049     // find tag ids for tag paths in list, create if they don't exist
02050     QList<int> tagIDs = DatabaseAccess().db()->getTagsFromTagPaths(tagPaths, true);
02051 
02052     // create TAlbum objects for the newly created tags
02053     scanTAlbums();
02054 
02055     AlbumList resultList;
02056 
02057     for (QList<int>::const_iterator it = tagIDs.constBegin() ; it != tagIDs.constEnd() ; ++it)
02058     {
02059         resultList.append(findTAlbum(*it));
02060     }
02061 
02062     return resultList;
02063 }
02064 
02065 bool AlbumManager::deleteTAlbum(TAlbum* album, QString& errMsg)
02066 {
02067     if (!album)
02068     {
02069         errMsg = i18n("No such album");
02070         return false;
02071     }
02072 
02073     if (album == d->rootTAlbum)
02074     {
02075         errMsg = i18n("Cannot delete Root Tag");
02076         return false;
02077     }
02078 
02079     {
02080         DatabaseAccess access;
02081         ChangingDB changing(d);
02082         access.db()->deleteTag(album->id());
02083 
02084         Album* subAlbum = 0;
02085         AlbumIterator it(album);
02086         while ((subAlbum = it.current()) != 0)
02087         {
02088             access.db()->deleteTag(subAlbum->id());
02089             ++it;
02090         }
02091     }
02092 
02093     removeTAlbum(album);
02094 
02095     return true;
02096 }
02097 
02098 bool AlbumManager::renameTAlbum(TAlbum* album, const QString& name,
02099                                 QString& errMsg)
02100 {
02101     if (!album)
02102     {
02103         errMsg = i18n("No such album");
02104         return false;
02105     }
02106 
02107     if (album == d->rootTAlbum)
02108     {
02109         errMsg = i18n("Cannot edit root tag");
02110         return false;
02111     }
02112 
02113     if (name.contains("/"))
02114     {
02115         errMsg = i18n("Tag name cannot contain '/'");
02116         return false;
02117     }
02118 
02119     // first check if we have another sibling with the same name
02120     Album *sibling = album->m_parent->m_firstChild;
02121     while (sibling)
02122     {
02123         if (sibling->title() == name)
02124         {
02125             errMsg = i18n("Another tag with the same name already exists.\n"
02126                           "Please choose another name.");
02127             return false;
02128         }
02129         sibling = sibling->m_next;
02130     }
02131 
02132     ChangingDB changing(d);
02133     DatabaseAccess().db()->setTagName(album->id(), name);
02134     album->setTitle(name);
02135     emit signalAlbumRenamed(album);
02136 
02137     return true;
02138 }
02139 
02140 bool AlbumManager::moveTAlbum(TAlbum* album, TAlbum *newParent, QString& errMsg)
02141 {
02142     if (!album)
02143     {
02144         errMsg = i18n("No such album");
02145         return false;
02146     }
02147 
02148     if (album == d->rootTAlbum)
02149     {
02150         errMsg = i18n("Cannot move root tag");
02151         return false;
02152     }
02153 
02154     ChangingDB changing(d);
02155     DatabaseAccess().db()->setTagParentID(album->id(), newParent->id());
02156     if (album->parent())
02157         album->parent()->removeChild(album);
02158     album->setParent(newParent);
02159 
02160     emit signalTAlbumMoved(album, newParent);
02161 
02162     return true;
02163 }
02164 
02165 bool AlbumManager::updateTAlbumIcon(TAlbum* album, const QString& iconKDE,
02166                                     qlonglong iconID, QString& errMsg)
02167 {
02168     if (!album)
02169     {
02170         errMsg = i18n("No such tag");
02171         return false;
02172     }
02173 
02174     if (album == d->rootTAlbum)
02175     {
02176         errMsg = i18n("Cannot edit root tag");
02177         return false;
02178     }
02179 
02180     {
02181         DatabaseAccess access;
02182         ChangingDB changing(d);
02183         access.db()->setTagIcon(album->id(), iconKDE, iconID);
02184         QString albumRelativePath, iconKDE;
02185         int albumRootId;
02186         if (access.db()->getTagIcon(album->id(), &albumRootId, &albumRelativePath, &iconKDE))
02187         {
02188             if (iconKDE.isEmpty())
02189             {
02190                 QString albumRootPath = CollectionManager::instance()->albumRootPath(albumRootId);
02191                 album->m_icon = albumRootPath + albumRelativePath;
02192             }
02193             else
02194             {
02195                 album->m_icon = iconKDE;
02196             }
02197         }
02198         else
02199             album->m_icon.clear();
02200     }
02201 
02202     emit signalAlbumIconChanged(album);
02203 
02204     return true;
02205 }
02206 
02207 AlbumList AlbumManager::getRecentlyAssignedTags() const
02208 {
02209     QList<int> tagIDs = DatabaseAccess().db()->getRecentlyAssignedTags();
02210 
02211     AlbumList resultList;
02212 
02213     for (QList<int>::const_iterator it = tagIDs.constBegin() ; it != tagIDs.constEnd() ; ++it)
02214     {
02215         resultList.append(findTAlbum(*it));
02216     }
02217 
02218     return resultList;
02219 }
02220 
02221 QStringList AlbumManager::tagPaths(const QList<int>& tagIDs, bool leadingSlash) const
02222 {
02223     QStringList tagPaths;
02224 
02225     for (QList<int>::const_iterator it = tagIDs.constBegin(); it != tagIDs.constEnd(); ++it)
02226     {
02227         TAlbum *album = findTAlbum(*it);
02228         if (album)
02229         {
02230             tagPaths.append(album->tagPath(leadingSlash));
02231         }
02232     }
02233 
02234     return tagPaths;
02235 }
02236 
02237 QStringList AlbumManager::tagNames(const QList<int>& tagIDs) const
02238 {
02239     QStringList tagNames;
02240 
02241     foreach(int id, tagIDs)
02242     {
02243         TAlbum *album = findTAlbum(id);
02244         if (album)
02245         {
02246             tagNames << album->title();
02247         }
02248     }
02249 
02250     return tagNames;
02251 }
02252 
02253 QHash<int, QString> AlbumManager::tagPaths(bool leadingSlash) const
02254 {
02255     QHash<int, QString> hash;
02256     AlbumIterator it(d->rootTAlbum);
02257     while (it.current())
02258     {
02259         TAlbum* t = (TAlbum*)(*it);
02260         hash.insert(t->id(), t->tagPath(leadingSlash));
02261         ++it;
02262     }
02263     return hash;
02264 }
02265 
02266 QHash<int, QString> AlbumManager::tagNames() const
02267 {
02268     QHash<int, QString> hash;
02269     AlbumIterator it(d->rootTAlbum);
02270     while (it.current())
02271     {
02272         TAlbum* t = (TAlbum*)(*it);
02273         hash.insert(t->id(), t->title());
02274         ++it;
02275     }
02276     return hash;
02277 }
02278 
02279 QHash<int, QString> AlbumManager::albumTitles() const
02280 {
02281     QHash<int, QString> hash;
02282     AlbumIterator it(d->rootPAlbum);
02283     while (it.current())
02284     {
02285         PAlbum* a = (PAlbum*)(*it);
02286         hash.insert(a->id(), a->title());
02287         ++it;
02288     }
02289     return hash;
02290 }
02291 
02292 SAlbum* AlbumManager::createSAlbum(const QString& name, DatabaseSearch::Type type, const QString& query)
02293 {
02294     // first iterate through all the search albums and see if there's an existing
02295     // SAlbum with same name. (Remember, SAlbums are arranged in a flat list)
02296     SAlbum *album = findSAlbum(name);
02297     ChangingDB changing(d);
02298     if (album)
02299     {
02300         album->setSearch(type, query);
02301         DatabaseAccess access;
02302         access.db()->updateSearch(album->id(), album->m_type, name, query);
02303         return album;
02304     }
02305 
02306     int id = DatabaseAccess().db()->addSearch(type, name, query);
02307 
02308     if (id == -1)
02309         return 0;
02310 
02311     album = new SAlbum(name, id);
02312     emit signalAlbumAboutToBeAdded(album, d->rootSAlbum, d->rootSAlbum->lastChild());
02313     album->setSearch(type, query);
02314     album->setParent(d->rootSAlbum);
02315 
02316     d->allAlbumsIdHash.insert(album->globalID(), album);
02317     emit signalAlbumAdded(album);
02318 
02319     return album;
02320 }
02321 
02322 bool AlbumManager::updateSAlbum(SAlbum* album, const QString& changedQuery,
02323                                 const QString& changedName, DatabaseSearch::Type type)
02324 {
02325     if (!album)
02326         return false;
02327 
02328     QString newName = changedName.isNull() ? album->title() : changedName;
02329     DatabaseSearch::Type newType = (type == DatabaseSearch::UndefinedType) ? album->type() : type;
02330 
02331     ChangingDB changing(d);
02332     DatabaseAccess().db()->updateSearch(album->id(), newType, newName, changedQuery);
02333 
02334     QString oldName = album->title();
02335 
02336     album->setSearch(newType, changedQuery);
02337     album->setTitle(newName);
02338     if (oldName != album->title())
02339         emit signalAlbumRenamed(album);
02340 
02341     return true;
02342 }
02343 
02344 bool AlbumManager::deleteSAlbum(SAlbum* album)
02345 {
02346     if (!album)
02347         return false;
02348 
02349     emit signalAlbumAboutToBeDeleted(album);
02350 
02351     ChangingDB changing(d);
02352     DatabaseAccess().db()->deleteSearch(album->id());
02353 
02354     d->allAlbumsIdHash.remove(album->globalID());
02355     emit signalAlbumDeleted(album);
02356     delete album;
02357     emit signalAlbumHasBeenDeleted(album);
02358 
02359     return true;
02360 }
02361 
02362 void AlbumManager::insertPAlbum(PAlbum *album, PAlbum *parent)
02363 {
02364     if (!album)
02365         return;
02366 
02367     emit signalAlbumAboutToBeAdded(album, parent, parent ? parent->lastChild() : 0);
02368 
02369     if (parent)
02370         album->setParent(parent);
02371 
02372     d->albumPathHash[album]  = album;
02373     d->allAlbumsIdHash[album->globalID()] = album;
02374 
02375     emit signalAlbumAdded(album);
02376 }
02377 
02378 void AlbumManager::removePAlbum(PAlbum *album)
02379 {
02380     if (!album)
02381         return;
02382 
02383     // remove all children of this album
02384     Album* child = album->m_firstChild;
02385     while (child)
02386     {
02387         Album *next = child->m_next;
02388         removePAlbum((PAlbum*)child);
02389         child = next;
02390     }
02391 
02392     emit signalAlbumAboutToBeDeleted(album);
02393     d->albumPathHash.remove(album);
02394     d->allAlbumsIdHash.remove(album->globalID());
02395 
02396     DatabaseUrl url = album->databaseUrl();
02397 
02398     if (album == d->currentAlbum)
02399     {
02400         d->currentAlbum = 0;
02401         emit signalAlbumCurrentChanged(0);
02402     }
02403 
02404     emit signalAlbumDeleted(album);
02405     delete album;
02406     emit signalAlbumHasBeenDeleted(album);
02407 }
02408 
02409 void AlbumManager::insertTAlbum(TAlbum *album, TAlbum *parent)
02410 {
02411     if (!album)
02412         return;
02413 
02414     emit signalAlbumAboutToBeAdded(album, parent, parent ? parent->lastChild() : 0);
02415 
02416     if (parent)
02417         album->setParent(parent);
02418 
02419     d->allAlbumsIdHash.insert(album->globalID(), album);
02420 
02421     emit signalAlbumAdded(album);
02422 }
02423 
02424 void AlbumManager::removeTAlbum(TAlbum *album)
02425 {
02426     if (!album)
02427         return;
02428 
02429     // remove all children of this album
02430     Album* child = album->m_firstChild;
02431     while (child)
02432     {
02433         Album *next = child->m_next;
02434         removeTAlbum((TAlbum*)child);
02435         child = next;
02436     }
02437 
02438     emit signalAlbumAboutToBeDeleted(album);
02439     d->allAlbumsIdHash.remove(album->globalID());
02440 
02441     if (album == d->currentAlbum)
02442     {
02443         d->currentAlbum = 0;
02444         emit signalAlbumCurrentChanged(0);
02445     }
02446 
02447     emit signalAlbumDeleted(album);
02448     delete album;
02449     emit signalAlbumHasBeenDeleted(album);
02450 }
02451 
02452 void AlbumManager::notifyAlbumDeletion(Album *album)
02453 {
02454     invalidateGuardedPointers(album);
02455 }
02456 
02457 void AlbumManager::slotAlbumsJobResult(KJob* job)
02458 {
02459     d->albumListJob = 0;
02460 
02461     if (job->error())
02462     {
02463         kWarning() << k_funcinfo << "Failed to list albums";
02464         return;
02465     }
02466 }
02467 
02468 void AlbumManager::slotAlbumsJobData(KIO::Job*, const QByteArray& data)
02469 {
02470     if (data.isEmpty())
02471         return;
02472 
02473     QMap<int, int> albumsStatMap;
02474     QByteArray di(data);
02475     QDataStream ds(&di, QIODevice::ReadOnly);
02476     ds >> albumsStatMap;
02477 
02478     emit signalPAlbumsDirty(albumsStatMap);
02479 }
02480 
02481 void AlbumManager::slotTagsJobResult(KJob* job)
02482 {
02483     d->tagListJob = 0;
02484 
02485     if (job->error())
02486     {
02487         kWarning() << k_funcinfo << "Failed to list tags";
02488         return;
02489     }
02490 }
02491 
02492 void AlbumManager::slotTagsJobData(KIO::Job*, const QByteArray& data)
02493 {
02494     if (data.isEmpty())
02495         return;
02496 
02497     QMap<int, int> tagsStatMap;
02498     QByteArray di(data);
02499     QDataStream ds(&di, QIODevice::ReadOnly);
02500     ds >> tagsStatMap;
02501 
02502     emit signalTAlbumsDirty(tagsStatMap);
02503 }
02504 
02505 void AlbumManager::slotDatesJobResult(KJob* job)
02506 {
02507     d->dateListJob = 0;
02508 
02509     if (job->error())
02510     {
02511         kWarning() << "Failed to list dates";
02512         return;
02513     }
02514 
02515     emit signalAllDAlbumsLoaded();
02516 }
02517 
02518 void AlbumManager::slotDatesJobData(KIO::Job*, const QByteArray& data)
02519 {
02520     if (data.isEmpty())
02521         return;
02522 
02523     // insert all the DAlbums into a qmap for quick access
02524     QMap<QDate, DAlbum*> mAlbumMap;
02525     QMap<int, DAlbum*>   yAlbumMap;
02526 
02527     AlbumIterator it(d->rootDAlbum);
02528     while (it.current())
02529     {
02530         DAlbum* a = (DAlbum*)(*it);
02531         if (a->range() == DAlbum::Month)
02532             mAlbumMap.insert(a->date(), a);
02533         else
02534             yAlbumMap.insert(a->date().year(), a);
02535         ++it;
02536     }
02537 
02538     QMap<QDateTime, int> datesStatMap;
02539     QByteArray di(data);
02540     QDataStream ds(&di, QIODevice::ReadOnly);
02541     ds >> datesStatMap;
02542 
02543     QMap<YearMonth, int> yearMonthMap;
02544     for (QMap<QDateTime, int>::const_iterator it = datesStatMap.constBegin(); it != datesStatMap.constEnd(); ++it)
02545     {
02546         YearMonth yearMonth = YearMonth(it.key().date().year(), it.key().date().month());
02547 
02548         QMap<YearMonth, int>::iterator it2 = yearMonthMap.find(yearMonth);
02549         if ( it2 == yearMonthMap.end() )
02550         {
02551             yearMonthMap.insert( yearMonth, *it );
02552         }
02553         else
02554         {
02555             *it2 += *it;
02556         }
02557     }
02558 
02559     int year, month;
02560     for (QMap<YearMonth, int>::const_iterator iter = yearMonthMap.constBegin();
02561          iter != yearMonthMap.constEnd(); ++iter)
02562     {
02563         year  = iter.key().first;
02564         month = iter.key().second;
02565 
02566         QDate md(year, month, 1);
02567 
02568         // Do we already have this Month album
02569         if (mAlbumMap.contains(md))
02570         {
02571             // already there. remove Month album from map
02572             mAlbumMap.remove(md);
02573 
02574             if (yAlbumMap.contains(year))
02575             {
02576                 // already there. remove from map
02577                 yAlbumMap.remove(year);
02578             }
02579 
02580             continue;
02581         }
02582 
02583         // Check if Year Album already exist.
02584         DAlbum *yAlbum = 0;
02585         AlbumIterator it(d->rootDAlbum);
02586         while (it.current())
02587         {
02588             DAlbum* a = (DAlbum*)(*it);
02589             if (a->date() == QDate(year, 1, 1) && a->range() == DAlbum::Year)
02590             {
02591                 yAlbum = a;
02592                 break;
02593             }
02594             ++it;
02595         }
02596 
02597         // If no, create Year album.
02598         if (!yAlbum)
02599         {
02600             yAlbum = new DAlbum(QDate(year, 1, 1), false, DAlbum::Year);
02601             emit signalAlbumAboutToBeAdded(yAlbum, d->rootDAlbum, d->rootDAlbum->lastChild());
02602             yAlbum->setParent(d->rootDAlbum);
02603             d->allAlbumsIdHash.insert(yAlbum->globalID(), yAlbum);
02604             emit signalAlbumAdded(yAlbum);
02605         }
02606 
02607         // Create Month album
02608         DAlbum *mAlbum = new DAlbum(md);
02609         emit signalAlbumAboutToBeAdded(mAlbum, yAlbum, yAlbum->lastChild());
02610         mAlbum->setParent(yAlbum);
02611         d->allAlbumsIdHash.insert(mAlbum->globalID(), mAlbum);
02612         emit signalAlbumAdded(mAlbum);
02613     }
02614 
02615     // Now the items contained in the maps are the ones which
02616     // have been deleted.
02617     for (QMap<QDate, DAlbum*>::const_iterator it = mAlbumMap.constBegin();
02618          it != mAlbumMap.constEnd(); ++it)
02619     {
02620         DAlbum* album = it.value();
02621         emit signalAlbumAboutToBeDeleted(album);
02622         d->allAlbumsIdHash.remove(album->globalID());
02623         emit signalAlbumDeleted(album);
02624         delete album;
02625         emit signalAlbumHasBeenDeleted(album);
02626     }
02627 
02628     for (QMap<int, DAlbum*>::const_iterator it = yAlbumMap.constBegin();
02629          it != yAlbumMap.constEnd(); ++it)
02630     {
02631         DAlbum* album = it.value();
02632         emit signalAlbumAboutToBeDeleted(album);
02633         d->allAlbumsIdHash.remove(album->globalID());
02634         emit signalAlbumDeleted(album);
02635         delete album;
02636         emit signalAlbumHasBeenDeleted(album);
02637     }
02638 
02639     emit signalDAlbumsDirty(yearMonthMap);
02640     emit signalDatesMapDirty(datesStatMap);
02641 }
02642 
02643 void AlbumManager::slotAlbumChange(const AlbumChangeset& changeset)
02644 {
02645     if (d->changingDB || !d->rootPAlbum)
02646         return;
02647 
02648     switch(changeset.operation())
02649     {
02650         case AlbumChangeset::Added:
02651         case AlbumChangeset::Deleted:
02652             if (!d->scanPAlbumsTimer->isActive())
02653                 d->scanPAlbumsTimer->start();
02654             break;
02655         case AlbumChangeset::Renamed:
02656         case AlbumChangeset::PropertiesChanged:
02657             // mark for rescan
02658             d->changedPAlbums << changeset.albumId();
02659             if (!d->updatePAlbumsTimer->isActive())
02660                 d->updatePAlbumsTimer->start();
02661             break;
02662         case AlbumChangeset::Unknown:
02663             break;
02664     }
02665 }
02666 
02667 void AlbumManager::slotTagChange(const TagChangeset& changeset)
02668 {
02669     if (d->changingDB || !d->rootTAlbum)
02670         return;
02671 
02672     switch(changeset.operation())
02673     {
02674         case TagChangeset::Added:
02675         case TagChangeset::Deleted:
02676         case TagChangeset::Reparented:
02677             if (!d->scanTAlbumsTimer->isActive())
02678                 d->scanTAlbumsTimer->start();
02679             break;
02680         case TagChangeset::Renamed:
02681         case TagChangeset::IconChanged:
02682             //TODO
02683             break;
02684         case TagChangeset::Unknown:
02685             break;
02686     }
02687 }
02688 
02689 void AlbumManager::slotSearchChange(const SearchChangeset& changeset)
02690 {
02691     if (d->changingDB || !d->rootSAlbum)
02692         return;
02693 
02694     switch(changeset.operation())
02695     {
02696         case SearchChangeset::Added:
02697         case SearchChangeset::Deleted:
02698             if (!d->scanSAlbumsTimer->isActive())
02699                 d->scanSAlbumsTimer->start();
02700             break;
02701         case SearchChangeset::Changed:
02702             break;
02703         case SearchChangeset::Unknown:
02704             break;
02705     }
02706 }
02707 
02708 void AlbumManager::slotCollectionImageChange(const CollectionImageChangeset& changeset)
02709 {
02710     if (!d->rootDAlbum)
02711         return;
02712 
02713     switch (changeset.operation())
02714     {
02715         case CollectionImageChangeset::Added:
02716         case CollectionImageChangeset::Removed:
02717         case CollectionImageChangeset::RemovedAll:
02718             if (!d->scanDAlbumsTimer->isActive())
02719                 d->scanDAlbumsTimer->start();
02720             if (!d->albumItemCountTimer->isActive())
02721                 d->albumItemCountTimer->start();
02722             break;
02723         default:
02724             break;
02725     }
02726 }
02727 
02728 void AlbumManager::slotImageTagChange(const ImageTagChangeset& changeset)
02729 {
02730     if (!d->rootTAlbum)
02731         return;
02732     switch (changeset.operation())
02733     {
02734         case ImageTagChangeset::Added:
02735         case ImageTagChangeset::Removed:
02736         case ImageTagChangeset::RemovedAll:
02737             if (!d->tagItemCountTimer->isActive())
02738                 d->tagItemCountTimer->start();
02739             break;
02740         default:
02741             break;
02742     }
02743 }
02744 
02745 void AlbumManager::slotNotifyFileChange(const QString& path)
02746 {
02747     //kDebug() << "Detected file change at" << path;
02748     ScanController::instance()->scheduleCollectionScanRelaxed(path);
02749 }
02750 
02751 void AlbumManager::slotDirWatchDirty(const QString& path)
02752 {
02753     // Filter out dirty signals triggered by changes on the database file
02754     foreach (const QString &bannedFile, d->dirWatchBlackList)
02755         if (path.endsWith(bannedFile))
02756             return;
02757 
02758     DatabaseParameters params = DatabaseAccess::parameters();
02759     if (params.isSQLite())
02760     {
02761         QDir dir(path);
02762         QFileInfo dbFile(params.SQLiteDatabaseFile());
02763 
02764         // Workaround for broken KDirWatch in KDE 4.2.4
02765         if (path.startsWith(dbFile.filePath()))
02766             return;
02767 
02768         // is the signal for the directory containing the database file?
02769         if (dbFile.dir() == dir)
02770         {
02771             // retrieve modification dates
02772             QList<QDateTime> modList = d->buildDirectoryModList(dbFile);
02773 
02774             // check for equality
02775             if (modList == d->dbPathModificationDateList)
02776             {
02777                 //kDebug() << "Filtering out db-file-triggered dir watch signal";
02778                 // we can skip the signal
02779                 return;
02780             }
02781 
02782             // set new list
02783             d->dbPathModificationDateList = modList;
02784         }
02785     }
02786 
02787     kDebug() << "KDirWatch detected change at" << path;
02788 
02789     slotNotifyFileChange(path);
02790 }
02791 
02792 void AlbumManager::slotKioFileMoved(const QString& urlFrom, const QString& urlTo)
02793 {
02794     kDebug() << urlFrom << urlTo;
02795     handleKioNotification(KUrl(urlFrom));
02796     handleKioNotification(KUrl(urlTo));
02797 }
02798 
02799 void AlbumManager::slotKioFilesAdded(const QString& url)
02800 {
02801     kDebug() << url;
02802     handleKioNotification(KUrl(url));
02803 }
02804 
02805 void AlbumManager::slotKioFilesDeleted(const QStringList& urls)
02806 {
02807     kDebug() << urls;
02808     foreach (const QString& url, urls)
02809         handleKioNotification(KUrl(url));
02810 }
02811 
02812 void AlbumManager::handleKioNotification(const KUrl& url)
02813 {
02814     if (url.isLocalFile())
02815     {
02816         QString path = url.directory();
02817         //kDebug() << path << !CollectionManager::instance()->albumRootPath(path).isEmpty();
02818         // check path is in our collection
02819         if (CollectionManager::instance()->albumRootPath(path).isNull())
02820             return;
02821 
02822         kDebug() << "KDirNotify detected file change at" << path;
02823 
02824         slotNotifyFileChange(path);
02825     }
02826     else
02827     {
02828         DatabaseUrl dbUrl(url);
02829         if (dbUrl.isAlbumUrl())
02830         {
02831             QString path = dbUrl.fileUrl().directory();
02832             kDebug() << "KDirNotify detected file change at" << path;
02833             slotNotifyFileChange(path);
02834         }
02835     }
02836 }
02837 
02838 
02839 }  // namespace Digikam

digikam

Skip menu "digikam"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members

API Reference

Skip menu "API Reference"
  • digikam
Generated for API Reference by doxygen 1.5.9-20090814
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal