summaryrefslogtreecommitdiffstats
path: root/digikam/digikam/albummanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'digikam/digikam/albummanager.cpp')
-rw-r--r--digikam/digikam/albummanager.cpp1678
1 files changed, 1678 insertions, 0 deletions
diff --git a/digikam/digikam/albummanager.cpp b/digikam/digikam/albummanager.cpp
new file mode 100644
index 0000000..28ee62f
--- /dev/null
+++ b/digikam/digikam/albummanager.cpp
@@ -0,0 +1,1678 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2004-06-15
+ * Description : Albums manager interface.
+ *
+ * Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
+ * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * ============================================================ */
+
+#include <config.h>
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+}
+
+// C++ includes.
+
+#include <clocale>
+#include <cstdlib>
+#include <cstdio>
+#include <cerrno>
+
+// Qt includes.
+
+#include <qfile.h>
+#include <qdir.h>
+#include <qdict.h>
+#include <qintdict.h>
+#include <qcstring.h>
+#include <qtextcodec.h>
+#include <qdatetime.h>
+
+// KDE includes.
+
+#include <kconfig.h>
+#include <klocale.h>
+#include <kdeversion.h>
+#include <kmessagebox.h>
+#include <kstandarddirs.h>
+#include <kio/netaccess.h>
+#include <kio/global.h>
+#include <kio/job.h>
+#include <kdirwatch.h>
+
+// Local includes.
+
+#include "ddebug.h"
+#include "album.h"
+#include "albumdb.h"
+#include "albumitemhandler.h"
+#include "dio.h"
+#include "albumsettings.h"
+#include "scanlib.h"
+#include "splashscreen.h"
+#include "upgradedb_sqlite2tosqlite3.h"
+#include "albummanager.h"
+#include "albummanager.moc"
+
+namespace Digikam
+{
+
+typedef QDict<PAlbum> PAlbumDict;
+typedef QIntDict<Album> AlbumIntDict;
+typedef QValueList<QDateTime> DDateList;
+
+class AlbumManagerPriv
+{
+
+public:
+
+ AlbumManagerPriv()
+ {
+ db = 0;
+ dateListJob = 0;
+ albumListJob = 0;
+ tagListJob = 0;
+ rootPAlbum = 0;
+ rootTAlbum = 0;
+ rootDAlbum = 0;
+ rootSAlbum = 0;
+ itemHandler = 0;
+ currentAlbum = 0;
+ dirWatch = 0;
+ changed = false;
+ }
+
+ bool changed;
+
+ QString libraryPath;
+ QStringList dirtyAlbums;
+
+ DDateList dbPathModificationDateList;
+
+ KDirWatch *dirWatch;
+
+ KIO::TransferJob *albumListJob;
+ KIO::TransferJob *dateListJob;
+ KIO::TransferJob *tagListJob;
+
+ PAlbum *rootPAlbum;
+ TAlbum *rootTAlbum;
+ DAlbum *rootDAlbum;
+ SAlbum *rootSAlbum;
+
+ PAlbumDict pAlbumDict;
+ AlbumIntDict albumIntDict;
+
+ Album *currentAlbum;
+ AlbumDB *db;
+ AlbumItemHandler *itemHandler;
+
+ QValueList<QDateTime> buildDirectoryModList(const QFileInfo &dbFile)
+ {
+ // retrieve modification dates of all files in the database-file dir
+ QValueList<QDateTime> modList;
+ const QFileInfoList *fileInfoList = dbFile.dir().entryInfoList(QDir::Files | QDir::Dirs );
+
+ // build list
+ QFileInfoListIterator it(*fileInfoList);
+ QFileInfo *fi;
+
+ while ( (fi = it.current()) != 0 )
+ {
+ if ( fi->fileName() != dbFile.fileName())
+ {
+ modList << fi->lastModified();
+ }
+ ++it;
+ }
+
+ return modList;
+ }
+
+};
+
+AlbumManager* AlbumManager::m_instance = 0;
+
+AlbumManager* AlbumManager::instance()
+{
+ return m_instance;
+}
+
+AlbumManager::AlbumManager()
+{
+ m_instance = this;
+
+ d = new AlbumManagerPriv;
+ d->db = new AlbumDB;
+}
+
+AlbumManager::~AlbumManager()
+{
+ if (d->dateListJob)
+ {
+ d->dateListJob->kill();
+ d->dateListJob = 0;
+ }
+
+ if (d->albumListJob)
+ {
+ d->albumListJob->kill();
+ d->albumListJob = 0;
+ }
+
+ if (d->tagListJob)
+ {
+ d->tagListJob->kill();
+ d->tagListJob = 0;
+ }
+
+ delete d->rootPAlbum;
+ delete d->rootTAlbum;
+ delete d->rootDAlbum;
+ delete d->rootSAlbum;
+
+ delete d->dirWatch;
+
+ delete d->db;
+ delete d;
+
+ m_instance = 0;
+}
+
+AlbumDB* AlbumManager::albumDB()
+{
+ return d->db;
+}
+
+void AlbumManager::setLibraryPath(const QString& path, SplashScreen *splash)
+{
+ QString cleanPath = QDir::cleanDirPath(path);
+
+ if (cleanPath == d->libraryPath)
+ return;
+
+ d->changed = true;
+
+ if (d->dateListJob)
+ {
+ d->dateListJob->kill();
+ d->dateListJob = 0;
+ }
+
+ if (d->albumListJob)
+ {
+ d->albumListJob->kill();
+ d->albumListJob = 0;
+ }
+
+ if (d->tagListJob)
+ {
+ d->tagListJob->kill();
+ d->tagListJob = 0;
+ }
+
+ delete d->dirWatch;
+ d->dirWatch = 0;
+ d->dirtyAlbums.clear();
+
+ d->currentAlbum = 0;
+ emit signalAlbumCurrentChanged(0);
+ emit signalAlbumsCleared();
+
+ d->pAlbumDict.clear();
+ d->albumIntDict.clear();
+
+ delete d->rootPAlbum;
+ delete d->rootTAlbum;
+ delete d->rootDAlbum;
+
+ d->rootPAlbum = 0;
+ d->rootTAlbum = 0;
+ d->rootDAlbum = 0;
+ d->rootSAlbum = 0;
+
+ d->libraryPath = cleanPath;
+
+ QString dbPath = cleanPath + "/digikam3.db";
+
+#ifdef NFS_HACK
+ dbPath = locateLocal("appdata", KIO::encodeFileName(QDir::cleanDirPath(dbPath)));
+#endif
+
+ d->db->setDBPath(dbPath);
+
+ // -- Locale Checking ---------------------------------------------------------
+
+ QString currLocale(QTextCodec::codecForLocale()->name());
+ QString dbLocale = d->db->getSetting("Locale");
+
+ // guilty until proven innocent
+ bool localeChanged = true;
+
+ if (dbLocale.isNull())
+ {
+ DDebug() << "No locale found in database" << endl;
+
+ // Copy an existing locale from the settings file (used < 0.8)
+ // to the database.
+ KConfig* config = KGlobal::config();
+ config->setGroup("General Settings");
+ if (config->hasKey("Locale"))
+ {
+ DDebug() << "Locale found in configfile" << endl;
+ dbLocale = config->readEntry("Locale");
+
+ // this hack is necessary, as we used to store the entire
+ // locale info LC_ALL (for eg: en_US.UTF-8) earlier,
+ // we now save only the encoding (UTF-8)
+
+ QString oldConfigLocale = ::setlocale(0, 0);
+
+ if (oldConfigLocale == dbLocale)
+ {
+ dbLocale = currLocale;
+ localeChanged = false;
+ d->db->setSetting("Locale", dbLocale);
+ }
+ }
+ else
+ {
+ DDebug() << "No locale found in config file" << endl;
+ dbLocale = currLocale;
+
+ localeChanged = false;
+ d->db->setSetting("Locale",dbLocale);
+ }
+ }
+ else
+ {
+ if (dbLocale == currLocale)
+ localeChanged = false;
+ }
+
+ if (localeChanged)
+ {
+ // TODO it would be better to replace all yes/no confirmation dialogs with ones that has custom
+ // buttons that denote the actions directly, i.e.: ["Ignore and Continue"] ["Adjust locale"]
+ int result =
+ KMessageBox::warningYesNo(0,
+ i18n("Your locale has changed since this album "
+ "was last opened.\n"
+ "Old Locale : %1, New Locale : %2\n"
+ "This can cause unexpected problems. "
+ "If you are sure that you want to "
+ "continue, click 'Yes' to work with this album. "
+ "Otherwise, click 'No' and correct your "
+ "locale setting before restarting digiKam")
+ .arg(dbLocale)
+ .arg(currLocale));
+ if (result != KMessageBox::Yes)
+ exit(0);
+
+ d->db->setSetting("Locale",currLocale);
+ }
+
+ // -- Check if we need to upgrade 0.7.x db to 0.8 db ---------------------
+
+ if (!upgradeDB_Sqlite2ToSqlite3(d->libraryPath))
+ {
+ KMessageBox::error(0, i18n("Failed to update the old Database to the new Database format\n"
+ "This error can happen if the Album Path '%1' does not exist or is write-protected.\n"
+ "If you have moved your photo collection, you need to adjust the 'Album Path' in digikam's configuration file.")
+ .arg(d->libraryPath));
+ exit(0);
+ }
+
+ // set an initial modification list to filter out KDirWatch signals
+ // caused by database operations
+ QFileInfo dbFile(dbPath);
+ d->dbPathModificationDateList = d->buildDirectoryModList(dbFile);
+
+ // -- Check if we need to do scanning -------------------------------------
+
+ KConfig* config = KGlobal::config();
+ config->setGroup("General Settings");
+ if (config->readBoolEntry("Scan At Start", true) ||
+ d->db->getSetting("Scanned").isEmpty())
+ {
+ ScanLib sLib(splash);
+ sLib.startScan();
+ }
+}
+
+QString AlbumManager::getLibraryPath() const
+{
+ return d->libraryPath;
+}
+
+void AlbumManager::startScan()
+{
+ if (!d->changed)
+ return;
+ d->changed = false;
+
+ d->dirWatch = new KDirWatch(this);
+ connect(d->dirWatch, SIGNAL(dirty(const QString&)),
+ this, SLOT(slotDirty(const QString&)));
+
+ KDirWatch::Method m = d->dirWatch->internalMethod();
+ QString mName("FAM");
+ if (m == KDirWatch::DNotify)
+ mName = QString("DNotify");
+ else if (m == KDirWatch::Stat)
+ mName = QString("Stat");
+ else if (m == KDirWatch::INotify)
+ mName = QString("INotify");
+ DDebug() << "KDirWatch method = " << mName << endl;
+
+ d->dirWatch->addDir(d->libraryPath);
+
+ d->rootPAlbum = new PAlbum(i18n("My Albums"), 0, true);
+ insertPAlbum(d->rootPAlbum);
+
+ d->rootTAlbum = new TAlbum(i18n("My Tags"), 0, true);
+ insertTAlbum(d->rootTAlbum);
+
+ d->rootSAlbum = new SAlbum(0, KURL(), true, true);
+
+ d->rootDAlbum = new DAlbum(QDate(), true);
+
+ refresh();
+
+ emit signalAllAlbumsLoaded();
+}
+
+void AlbumManager::refresh()
+{
+ scanPAlbums();
+ scanTAlbums();
+ scanSAlbums();
+ scanDAlbums();
+
+ if (!d->dirtyAlbums.empty())
+ {
+ KURL u;
+ u.setProtocol("digikamalbums");
+ u.setPath(d->dirtyAlbums.first());
+ d->dirtyAlbums.pop_front();
+
+ DIO::scan(u);
+ }
+}
+
+void AlbumManager::scanPAlbums()
+{
+ // first insert all the current PAlbums into a map for quick lookup
+ typedef QMap<QString, PAlbum*> AlbumMap;
+ AlbumMap aMap;
+
+ AlbumIterator it(d->rootPAlbum);
+ while (it.current())
+ {
+ PAlbum* a = (PAlbum*)(*it);
+ aMap.insert(a->url(), a);
+ ++it;
+ }
+
+ // scan db and get a list of all albums
+ AlbumInfo::List aList = d->db->scanAlbums();
+ qHeapSort(aList);
+
+ AlbumInfo::List newAlbumList;
+
+ // go through all the Albums and see which ones are already present
+ for (AlbumInfo::List::iterator it = aList.begin(); it != aList.end(); ++it)
+ {
+ AlbumInfo info = *it;
+ info.url = QDir::cleanDirPath(info.url);
+
+ if (!aMap.contains(info.url))
+ {
+ newAlbumList.append(info);
+ }
+ else
+ {
+ aMap.remove(info.url);
+ }
+ }
+
+ // now aMap contains all the deleted albums and
+ // newAlbumList contains all the new albums
+
+ // first inform all frontends of the deleted albums
+ for (AlbumMap::iterator it = aMap.begin(); it != aMap.end(); ++it)
+ {
+ // the albums have to be removed with children being removed first.
+ // removePAlbum takes care of that.
+ // So never delete the PAlbum using it.data(). instead check if the
+ // PAlbum is still in the Album Dict before trying to remove it.
+
+ // this might look like there is memory leak here, since removePAlbum
+ // doesn't delete albums and looks like child Albums don't get deleted.
+ // But when the parent album gets deleted, the children are also deleted.
+
+ PAlbum* album = d->pAlbumDict.find(it.key());
+ if (!album)
+ continue;
+
+ removePAlbum(album);
+ delete album;
+ }
+
+ qHeapSort(newAlbumList);
+ for (AlbumInfo::List::iterator it = newAlbumList.begin(); it != newAlbumList.end(); ++it)
+ {
+ AlbumInfo info = *it;
+ if (info.url.isEmpty() || info.url == "/")
+ continue;
+
+ // Despite its name info.url is a QString.
+ // setPath takes care for escaping characters that are valid for files but not for URLs ('#')
+ KURL u;
+ u.setPath(info.url);
+ QString name = u.fileName();
+ // Get its parent
+ QString purl = u.upURL().path(-1);
+
+ PAlbum* parent = d->pAlbumDict.find(purl);
+ if (!parent)
+ {
+ DWarning() << k_funcinfo << "Could not find parent with url: "
+ << purl << " for: " << info.url << endl;
+ continue;
+ }
+
+ // Create the new album
+ PAlbum* album = new PAlbum(name, info.id, false);
+ album->m_caption = info.caption;
+ album->m_collection = info.collection;
+ album->m_date = info.date;
+ album->m_icon = info.icon;
+
+ album->setParent(parent);
+ d->dirWatch->addDir(album->folderPath());
+
+ insertPAlbum(album);
+ }
+
+ if (!AlbumSettings::instance()->getShowFolderTreeViewItemsCount())
+ return;
+
+ // List albums using kioslave
+
+ if (d->albumListJob)
+ {
+ d->albumListJob->kill();
+ d->albumListJob = 0;
+ }
+
+ KURL u;
+ u.setProtocol("digikamalbums");
+ u.setPath("/");
+
+ QByteArray ba;
+ QDataStream ds(ba, IO_WriteOnly);
+ ds << d->libraryPath;
+ ds << KURL();
+ ds << AlbumSettings::instance()->getAllFileFilter();
+ ds << 0; // getting dimensions (not needed here)
+ ds << 0; // recursive sub-album (not needed here)
+ ds << 0; // recursive sub-tags (not needed here)
+
+ d->albumListJob = new KIO::TransferJob(u, KIO::CMD_SPECIAL,
+ ba, QByteArray(), false);
+ d->albumListJob->addMetaData("folders", "yes");
+
+ connect(d->albumListJob, SIGNAL(result(KIO::Job*)),
+ this, SLOT(slotAlbumsJobResult(KIO::Job*)));
+
+ connect(d->albumListJob, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ this, SLOT(slotAlbumsJobData(KIO::Job*, const QByteArray&)));
+}
+
+void AlbumManager::scanTAlbums()
+{
+ // list TAlbums directly from the db
+ // first insert all the current TAlbums into a map for quick lookup
+ typedef QMap<int, TAlbum*> TagMap;
+ TagMap tmap;
+
+ tmap.insert(0, d->rootTAlbum);
+
+ AlbumIterator it(d->rootTAlbum);
+ while (it.current())
+ {
+ TAlbum* t = (TAlbum*)(*it);
+ tmap.insert(t->id(), t);
+ ++it;
+ }
+
+ // Retrieve the list of tags from the database
+ TagInfo::List tList = d->db->scanTags();
+
+ // sort the list. needed because we want the tags can be read in any order,
+ // but we want to make sure that we are ensure to find the parent TAlbum
+ // for a new TAlbum
+
+ {
+ QIntDict<TAlbum> tagDict;
+ tagDict.setAutoDelete(false);
+
+ // insert items into a dict for quick lookup
+ for (TagInfo::List::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TagInfo info = *it;
+ TAlbum* album = new TAlbum(info.name, info.id);
+ album->m_icon = info.icon;
+ album->m_pid = info.pid;
+ tagDict.insert(info.id, album);
+ }
+ tList.clear();
+
+ // also add root tag
+ TAlbum* rootTag = new TAlbum("root", 0, true);
+ tagDict.insert(0, rootTag);
+
+ // build tree
+ QIntDictIterator<TAlbum> iter(tagDict);
+ for ( ; iter.current(); ++iter )
+ {
+ TAlbum* album = iter.current();
+ if (album->m_id == 0)
+ continue;
+
+ TAlbum* parent = tagDict.find(album->m_pid);
+ if (parent)
+ {
+ album->setParent(parent);
+ }
+ else
+ {
+ DWarning() << "Failed to find parent tag for tag "
+ << iter.current()->m_title
+ << " with pid "
+ << iter.current()->m_pid << endl;
+ }
+ }
+
+ // now insert the items into the list. becomes sorted
+ AlbumIterator it(rootTag);
+ while (it.current())
+ {
+ TAlbum* album = (TAlbum*)it.current();
+ TagInfo info;
+ info.id = album->m_id;
+ info.pid = album->m_pid;
+ info.name = album->m_title;
+ info.icon = album->m_icon;
+ tList.append(info);
+ ++it;
+ }
+
+ // this will also delete all child albums
+ delete rootTag;
+ }
+
+ for (TagInfo::List::iterator it = tList.begin(); it != tList.end(); ++it)
+ {
+ TagInfo info = *it;
+
+ // check if we have already added this tag
+ if (tmap.contains(info.id))
+ continue;
+
+ // Its a new album. Find the parent of the album
+ TagMap::iterator iter = tmap.find(info.pid);
+ if (iter == tmap.end())
+ {
+ DWarning() << "Failed to find parent tag for tag "
+ << info.name
+ << " with pid "
+ << info.pid << endl;
+ continue;
+ }
+
+ TAlbum* parent = iter.data();
+
+ // Create the new TAlbum
+ TAlbum* album = new TAlbum(info.name, info.id, false);
+ album->m_icon = info.icon;
+ album->setParent(parent);
+ insertTAlbum(album);
+
+ // also insert it in the map we are doing lookup of parent tags
+ tmap.insert(info.id, album);
+ }
+
+ if (!AlbumSettings::instance()->getShowFolderTreeViewItemsCount())
+ return;
+
+ // List tags using kioslave
+
+ if (d->tagListJob)
+ {
+ d->tagListJob->kill();
+ d->tagListJob = 0;
+ }
+
+ KURL u;
+ u.setProtocol("digikamtags");
+ u.setPath("/");
+
+ QByteArray ba;
+ QDataStream ds(ba, IO_WriteOnly);
+ ds << d->libraryPath;
+ ds << KURL();
+ ds << AlbumSettings::instance()->getAllFileFilter();
+ ds << 0; // getting dimensions (not needed here)
+ ds << 0; // recursive sub-album (not needed here)
+ ds << 0; // recursive sub-tags (not needed here)
+
+ d->tagListJob = new KIO::TransferJob(u, KIO::CMD_SPECIAL,
+ ba, QByteArray(), false);
+ d->tagListJob->addMetaData("folders", "yes");
+
+ connect(d->tagListJob, SIGNAL(result(KIO::Job*)),
+ this, SLOT(slotTagsJobResult(KIO::Job*)));
+
+ connect(d->tagListJob, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ this, SLOT(slotTagsJobData(KIO::Job*, const QByteArray&)));
+}
+
+void AlbumManager::scanSAlbums()
+{
+ // list SAlbums directly from the db
+ // first insert all the current SAlbums into a map for quick lookup
+ typedef QMap<int, SAlbum*> SearchMap;
+ SearchMap sMap;
+
+ AlbumIterator it(d->rootSAlbum);
+ while (it.current())
+ {
+ SAlbum* t = (SAlbum*)(*it);
+ sMap.insert(t->id(), t);
+ ++it;
+ }
+
+ // Retrieve the list of searches from the database
+ SearchInfo::List sList = d->db->scanSearches();
+
+ for (SearchInfo::List::iterator it = sList.begin(); it != sList.end(); ++it)
+ {
+ SearchInfo info = *it;
+
+ // check if we have already added this search
+ if (sMap.contains(info.id))
+ continue;
+
+ bool simple = (info.url.queryItem("1.key") == QString::fromLatin1("keyword"));
+
+ // Its a new album.
+ SAlbum* album = new SAlbum(info.id, info.url, simple, false);
+ album->setParent(d->rootSAlbum);
+ d->albumIntDict.insert(album->globalID(), album);
+ emit signalAlbumAdded(album);
+ }
+}
+
+void AlbumManager::scanDAlbums()
+{
+ // List dates using kioslave
+
+ if (d->dateListJob)
+ {
+ d->dateListJob->kill();
+ d->dateListJob = 0;
+ }
+
+ KURL u;
+ u.setProtocol("digikamdates");
+ u.setPath("/");
+
+ QByteArray ba;
+ QDataStream ds(ba, IO_WriteOnly);
+ ds << d->libraryPath;
+ ds << KURL();
+ ds << AlbumSettings::instance()->getAllFileFilter();
+ ds << 0; // getting dimensions (not needed here)
+ ds << 0; // recursive sub-album (not needed here)
+ ds << 0; // recursive sub-tags (not needed here)
+
+ d->dateListJob = new KIO::TransferJob(u, KIO::CMD_SPECIAL,
+ ba, QByteArray(), false);
+ d->dateListJob->addMetaData("folders", "yes");
+
+ connect(d->dateListJob, SIGNAL(result(KIO::Job*)),
+ this, SLOT(slotDatesJobResult(KIO::Job*)));
+
+ connect(d->dateListJob, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ this, SLOT(slotDatesJobData(KIO::Job*, const QByteArray&)));
+}
+
+AlbumList AlbumManager::allPAlbums() const
+{
+ AlbumList list;
+ if (d->rootPAlbum)
+ list.append(d->rootPAlbum);
+
+ AlbumIterator it(d->rootPAlbum);
+ while (it.current())
+ {
+ list.append(*it);
+ ++it;
+ }
+
+ return list;
+}
+
+AlbumList AlbumManager::allTAlbums() const
+{
+ AlbumList list;
+ if (d->rootTAlbum)
+ list.append(d->rootTAlbum);
+
+ AlbumIterator it(d->rootTAlbum);
+ while (it.current())
+ {
+ list.append(*it);
+ ++it;
+ }
+
+ return list;
+}
+
+AlbumList AlbumManager::allSAlbums() const
+{
+ AlbumList list;
+ if (d->rootSAlbum)
+ list.append(d->rootSAlbum);
+
+ AlbumIterator it(d->rootSAlbum);
+ while (it.current())
+ {
+ list.append(*it);
+ ++it;
+ }
+
+ return list;
+}
+
+AlbumList AlbumManager::allDAlbums() const
+{
+ AlbumList list;
+ if (d->rootDAlbum)
+ list.append(d->rootDAlbum);
+
+ AlbumIterator it(d->rootDAlbum);
+ while (it.current())
+ {
+ list.append(*it);
+ ++it;
+ }
+
+ return list;
+}
+
+void AlbumManager::setCurrentAlbum(Album *album)
+{
+ d->currentAlbum = album;
+ emit signalAlbumCurrentChanged(album);
+}
+
+Album* AlbumManager::currentAlbum() const
+{
+ return d->currentAlbum;
+}
+
+PAlbum* AlbumManager::findPAlbum(const KURL& url) const
+{
+ QString path = url.path();
+ path.remove(d->libraryPath);
+ path = QDir::cleanDirPath(path);
+
+ return d->pAlbumDict.find(path);
+}
+
+PAlbum* AlbumManager::findPAlbum(int id) const
+{
+ if (!d->rootPAlbum)
+ return 0;
+
+ int gid = d->rootPAlbum->globalID() + id;
+
+ return (PAlbum*)(d->albumIntDict.find(gid));
+}
+
+TAlbum* AlbumManager::findTAlbum(int id) const
+{
+ if (!d->rootTAlbum)
+ return 0;
+
+ int gid = d->rootTAlbum->globalID() + id;
+
+ return (TAlbum*)(d->albumIntDict.find(gid));
+}
+
+SAlbum* AlbumManager::findSAlbum(int id) const
+{
+ if (!d->rootTAlbum)
+ return 0;
+
+ int gid = d->rootSAlbum->globalID() + id;
+
+ return (SAlbum*)(d->albumIntDict.find(gid));
+}
+
+DAlbum* AlbumManager::findDAlbum(int id) const
+{
+ if (!d->rootDAlbum)
+ return 0;
+
+ int gid = d->rootDAlbum->globalID() + id;
+
+ return (DAlbum*)(d->albumIntDict.find(gid));
+}
+
+Album* AlbumManager::findAlbum(int gid) const
+{
+ return d->albumIntDict.find(gid);
+}
+
+TAlbum* AlbumManager::findTAlbum(const QString &tagPath) const
+{
+ // handle gracefully with or without leading slash
+ bool withLeadingSlash = tagPath.startsWith("/");
+ AlbumIterator it(d->rootTAlbum);
+ while (it.current())
+ {
+ TAlbum *talbum = static_cast<TAlbum *>(*it);
+ if (talbum->tagPath(withLeadingSlash) == tagPath)
+ return talbum;
+ ++it;
+ }
+ return 0;
+
+}
+
+
+PAlbum* AlbumManager::createPAlbum(PAlbum* parent,
+ const QString& name,
+ const QString& caption,
+ const QDate& date,
+ const QString& collection,
+ QString& errMsg)
+{
+ if (!parent)
+ {
+ errMsg = i18n("No parent found for album.");
+ return 0;
+ }
+
+ // sanity checks
+ if (name.isEmpty())
+ {
+ errMsg = i18n("Album name cannot be empty.");
+ return 0;
+ }
+
+ if (name.contains("/"))
+ {
+ errMsg = i18n("Album name cannot contain '/'.");
+ return 0;
+ }
+
+ // first check if we have another album with the same name
+ Album *child = parent->m_firstChild;
+ while (child)
+ {
+ if (child->title() == name)
+ {
+ errMsg = i18n("An existing album has the same name.");
+ return 0;
+ }
+ child = child->m_next;
+ }
+
+ QString path = parent->folderPath();
+ path += '/' + name;
+ path = QDir::cleanDirPath(path);
+
+ // make the directory synchronously, so that we can add the
+ // album info to the database directly
+ if (::mkdir(QFile::encodeName(path), 0777) != 0)
+ {
+ if (errno == EEXIST)
+ errMsg = i18n("Another file or folder with same name exists");
+ else if (errno == EACCES)
+ errMsg = i18n("Access denied to path");
+ else if (errno == ENOSPC)
+ errMsg = i18n("Disk is full");
+ else
+ errMsg = i18n("Unknown error"); // being lazy
+
+ return 0;
+ }
+
+ // Now insert the album properties into the database
+ path = path.remove(0, d->libraryPath.length());
+ if (!path.startsWith("/"))
+ path.prepend("/");
+
+ int id = d->db->addAlbum(path, caption, date, collection);
+ if (id == -1)
+ {
+ errMsg = i18n("Failed to add album to database");
+ return 0;
+ }
+
+ PAlbum *album = new PAlbum(name, id, false);
+ album->m_caption = caption;
+ album->m_collection = collection;
+ album->m_date = date;
+
+ album->setParent(parent);
+
+ d->dirWatch->addDir(album->folderPath());
+
+ insertPAlbum(album);
+
+ return album;
+}
+
+bool AlbumManager::renamePAlbum(PAlbum* album, const QString& newName,
+ QString& errMsg)
+{
+ if (!album)
+ {
+ errMsg = i18n("No such album");
+ return false;
+ }
+
+ if (album == d->rootPAlbum)
+ {
+ errMsg = i18n("Cannot rename root album");
+ return false;
+ }
+
+ if (newName.contains("/"))
+ {
+ errMsg = i18n("Album name cannot contain '/'");
+ return false;
+ }
+
+ // first check if we have another sibling with the same name
+ Album *sibling = album->m_parent->m_firstChild;
+ while (sibling)
+ {
+ if (sibling->title() == newName)
+ {
+ errMsg = i18n("Another album with same name exists\n"
+ "Please choose another name");
+ return false;
+ }
+ sibling = sibling->m_next;
+ }
+
+ QString oldURL = album->url();
+
+ KURL u = KURL::fromPathOrURL(album->folderPath()).upURL();
+ u.addPath(newName);
+ u.cleanPath();
+
+ if (::rename(QFile::encodeName(album->folderPath()),
+ QFile::encodeName(u.path(-1))) != 0)
+ {
+ errMsg = i18n("Failed to rename Album");
+ return false;
+ }
+
+ // now rename the album and subalbums in the database
+
+ // all we need to do is set the title of the album which is being
+ // renamed correctly and all the sub albums will automatically get
+ // their url set correctly
+
+ album->setTitle(newName);
+ d->db->setAlbumURL(album->id(), album->url());
+
+ Album* subAlbum = 0;
+ AlbumIterator it(album);
+ while ((subAlbum = it.current()) != 0)
+ {
+ d->db->setAlbumURL(subAlbum->id(), ((PAlbum*)subAlbum)->url());
+ ++it;
+ }
+
+ // Update AlbumDict. basically clear it and rebuild from scratch
+ {
+ d->pAlbumDict.clear();
+ d->pAlbumDict.insert(d->rootPAlbum->url(), d->rootPAlbum);
+ AlbumIterator it(d->rootPAlbum);
+ PAlbum* subAlbum = 0;
+ while ((subAlbum = (PAlbum*)it.current()) != 0)
+ {
+ d->pAlbumDict.insert(subAlbum->url(), subAlbum);
+ ++it;
+ }
+ }
+
+ emit signalAlbumRenamed(album);
+
+ return true;
+}
+
+bool AlbumManager::updatePAlbumIcon(PAlbum *album, Q_LLONG iconID, QString& errMsg)
+{
+ if (!album)
+ {
+ errMsg = i18n("No such album");
+ return false;
+ }
+
+ if (album == d->rootPAlbum)
+ {
+ errMsg = i18n("Cannot edit root album");
+ return false;
+ }
+
+ d->db->setAlbumIcon(album->id(), iconID);
+ album->m_icon = d->db->getAlbumIcon(album->id());
+
+ emit signalAlbumIconChanged(album);
+
+ return true;
+}
+
+TAlbum* AlbumManager::createTAlbum(TAlbum* parent, const QString& name,
+ const QString& iconkde, QString& errMsg)
+{
+ if (!parent)
+ {
+ errMsg = i18n("No parent found for tag");
+ return 0;
+ }
+
+ // sanity checks
+ if (name.isEmpty())
+ {
+ errMsg = i18n("Tag name cannot be empty");
+ return 0;
+ }
+
+ if (name.contains("/"))
+ {
+ errMsg = i18n("Tag name cannot contain '/'");
+ return 0;
+ }
+
+ // first check if we have another album with the same name
+ Album *child = parent->m_firstChild;
+ while (child)
+ {
+ if (child->title() == name)
+ {
+ errMsg = i18n("Tag name already exists");
+ return 0;
+ }
+ child = child->m_next;
+ }
+
+ int id = d->db->addTag(parent->id(), name, iconkde, 0);
+ if (id == -1)
+ {
+ errMsg = i18n("Failed to add tag to database");
+ return 0;
+ }
+
+ TAlbum *album = new TAlbum(name, id, false);
+ album->m_icon = iconkde;
+ album->setParent(parent);
+
+ insertTAlbum(album);
+
+ return album;
+}
+
+AlbumList AlbumManager::findOrCreateTAlbums(const QStringList &tagPaths)
+{
+ IntList tagIDs;
+
+ // find tag ids for tag paths in list, create if they don't exist
+ tagIDs = d->db->getTagsFromTagPaths(tagPaths);
+
+ // create TAlbum objects for the newly created tags
+ scanTAlbums();
+
+ AlbumList resultList;
+
+ for (IntList::iterator it = tagIDs.begin(); it != tagIDs.end(); ++it)
+ {
+ resultList.append(findTAlbum(*it));
+ }
+
+ return resultList;
+}
+
+bool AlbumManager::deleteTAlbum(TAlbum* album, QString& errMsg)
+{
+ if (!album)
+ {
+ errMsg = i18n("No such album");
+ return false;
+ }
+
+ if (album == d->rootTAlbum)
+ {
+ errMsg = i18n("Cannot delete Root Tag");
+ return false;
+ }
+
+ d->db->deleteTag(album->id());
+
+ Album* subAlbum = 0;
+ AlbumIterator it(album);
+ while ((subAlbum = it.current()) != 0)
+ {
+ d->db->deleteTag(subAlbum->id());
+ ++it;
+ }
+
+ removeTAlbum(album);
+
+ d->albumIntDict.remove(album->globalID());
+ delete album;
+
+ return true;
+}
+
+bool AlbumManager::renameTAlbum(TAlbum* album, const QString& name,
+ QString& errMsg)
+{
+ if (!album)
+ {
+ errMsg = i18n("No such album");
+ return false;
+ }
+
+ if (album == d->rootTAlbum)
+ {
+ errMsg = i18n("Cannot edit root tag");
+ return false;
+ }
+
+ if (name.contains("/"))
+ {
+ errMsg = i18n("Tag name cannot contain '/'");
+ return false;
+ }
+
+ // first check if we have another sibling with the same name
+ Album *sibling = album->m_parent->m_firstChild;
+ while (sibling)
+ {
+ if (sibling->title() == name)
+ {
+ errMsg = i18n("Another tag with same name exists\n"
+ "Please choose another name");
+ return false;
+ }
+ sibling = sibling->m_next;
+ }
+
+ d->db->setTagName(album->id(), name);
+ album->setTitle(name);
+ emit signalAlbumRenamed(album);
+
+ return true;
+}
+
+bool AlbumManager::moveTAlbum(TAlbum* album, TAlbum *newParent, QString &errMsg)
+{
+ if (!album)
+ {
+ errMsg = i18n("No such album");
+ return false;
+ }
+
+ if (album == d->rootTAlbum)
+ {
+ errMsg = i18n("Cannot move root tag");
+ return false;
+ }
+
+ d->db->setTagParentID(album->id(), newParent->id());
+ album->parent()->removeChild(album);
+ album->setParent(newParent);
+
+ emit signalTAlbumMoved(album, newParent);
+
+ return true;
+}
+
+bool AlbumManager::updateTAlbumIcon(TAlbum* album, const QString& iconKDE,
+ Q_LLONG iconID, QString& errMsg)
+{
+ if (!album)
+ {
+ errMsg = i18n("No such tag");
+ return false;
+ }
+
+ if (album == d->rootTAlbum)
+ {
+ errMsg = i18n("Cannot edit root tag");
+ return false;
+ }
+
+ d->db->setTagIcon(album->id(), iconKDE, iconID);
+ album->m_icon = d->db->getTagIcon(album->id());
+
+ emit signalAlbumIconChanged(album);
+
+ return true;
+}
+
+SAlbum* AlbumManager::createSAlbum(const KURL& url, bool simple)
+{
+ QString name = url.queryItem("name");
+
+ // first iterate through all the search albums and see if there's an existing
+ // SAlbum with same name. (Remember, SAlbums are arranged in a flat list)
+ for (Album* album = d->rootSAlbum->firstChild(); album; album = album->next())
+ {
+ if (album->title() == name)
+ {
+ SAlbum* sa = (SAlbum*)album;
+ sa->m_kurl = url;
+ d->db->updateSearch(sa->id(), url.queryItem("name"), url);
+ return sa;
+ }
+ }
+
+ int id = d->db->addSearch(url.queryItem("name"), url);
+ if (id == -1)
+ return 0;
+
+ SAlbum* album = new SAlbum(id, url, simple, false);
+ album->setTitle(url.queryItem("name"));
+ album->setParent(d->rootSAlbum);
+
+ d->albumIntDict.insert(album->globalID(), album);
+ emit signalAlbumAdded(album);
+
+ return album;
+}
+
+bool AlbumManager::updateSAlbum(SAlbum* album, const KURL& newURL)
+{
+ if (!album)
+ return false;
+
+ d->db->updateSearch(album->id(), newURL.queryItem("name"), newURL);
+
+ QString oldName = album->title();
+
+ album->m_kurl = newURL;
+ album->setTitle(newURL.queryItem("name"));
+ if (oldName != album->title())
+ emit signalAlbumRenamed(album);
+
+ return true;
+}
+
+bool AlbumManager::deleteSAlbum(SAlbum* album)
+{
+ if (!album)
+ return false;
+
+ emit signalAlbumDeleted(album);
+
+ d->db->deleteSearch(album->id());
+
+ d->albumIntDict.remove(album->globalID());
+ delete album;
+
+ return true;
+}
+
+void AlbumManager::insertPAlbum(PAlbum *album)
+{
+ if (!album)
+ return;
+
+ d->pAlbumDict.insert(album->url(), album);
+ d->albumIntDict.insert(album->globalID(), album);
+
+ emit signalAlbumAdded(album);
+}
+
+void AlbumManager::removePAlbum(PAlbum *album)
+{
+ if (!album)
+ return;
+
+ // remove all children of this album
+ Album* child = album->m_firstChild;
+ while (child)
+ {
+ Album *next = child->m_next;
+ removePAlbum((PAlbum*)child);
+ child = next;
+ }
+
+ d->pAlbumDict.remove(album->url());
+ d->albumIntDict.remove(album->globalID());
+
+ d->dirtyAlbums.remove(album->url());
+ d->dirWatch->removeDir(album->folderPath());
+
+ if (album == d->currentAlbum)
+ {
+ d->currentAlbum = 0;
+ emit signalAlbumCurrentChanged(0);
+ }
+
+ emit signalAlbumDeleted(album);
+}
+
+void AlbumManager::insertTAlbum(TAlbum *album)
+{
+ if (!album)
+ return;
+
+ d->albumIntDict.insert(album->globalID(), album);
+
+ emit signalAlbumAdded(album);
+}
+
+void AlbumManager::removeTAlbum(TAlbum *album)
+{
+ if (!album)
+ return;
+
+ // remove all children of this album
+ Album* child = album->m_firstChild;
+ while (child)
+ {
+ Album *next = child->m_next;
+ removeTAlbum((TAlbum*)child);
+ child = next;
+ }
+
+ d->albumIntDict.remove(album->globalID());
+
+ if (album == d->currentAlbum)
+ {
+ d->currentAlbum = 0;
+ emit signalAlbumCurrentChanged(0);
+ }
+
+ emit signalAlbumDeleted(album);
+}
+
+void AlbumManager::emitAlbumItemsSelected(bool val)
+{
+ emit signalAlbumItemsSelected(val);
+}
+
+void AlbumManager::setItemHandler(AlbumItemHandler *handler)
+{
+ d->itemHandler = handler;
+}
+
+AlbumItemHandler* AlbumManager::getItemHandler()
+{
+ return d->itemHandler;
+}
+
+void AlbumManager::refreshItemHandler(const KURL::List& itemList)
+{
+ if (itemList.empty())
+ d->itemHandler->refresh();
+ else
+ d->itemHandler->refreshItems(itemList);
+}
+
+void AlbumManager::slotAlbumsJobResult(KIO::Job* job)
+{
+ d->albumListJob = 0;
+
+ if (job->error())
+ {
+ DWarning() << k_funcinfo << "Failed to list albums" << endl;
+ return;
+ }
+}
+
+void AlbumManager::slotAlbumsJobData(KIO::Job*, const QByteArray& data)
+{
+ if (data.isEmpty())
+ return;
+
+ QMap<int, int> albumsStatMap;
+ QDataStream ds(data, IO_ReadOnly);
+ ds >> albumsStatMap;
+
+ emit signalPAlbumsDirty(albumsStatMap);
+}
+
+void AlbumManager::slotTagsJobResult(KIO::Job* job)
+{
+ d->tagListJob = 0;
+
+ if (job->error())
+ {
+ DWarning() << k_funcinfo << "Failed to list tags" << endl;
+ return;
+ }
+}
+
+void AlbumManager::slotTagsJobData(KIO::Job*, const QByteArray& data)
+{
+ if (data.isEmpty())
+ return;
+
+ QMap<int, int> tagsStatMap;
+ QDataStream ds(data, IO_ReadOnly);
+ ds >> tagsStatMap;
+
+ emit signalTAlbumsDirty(tagsStatMap);
+}
+
+void AlbumManager::slotDatesJobResult(KIO::Job* job)
+{
+ d->dateListJob = 0;
+
+ if (job->error())
+ {
+ DWarning() << k_funcinfo << "Failed to list dates" << endl;
+ return;
+ }
+
+ emit signalAllDAlbumsLoaded();
+}
+
+void AlbumManager::slotDatesJobData(KIO::Job*, const QByteArray& data)
+{
+ if (data.isEmpty())
+ return;
+
+ // insert all the DAlbums into a qmap for quick access
+ QMap<QDate, DAlbum*> mAlbumMap;
+ QMap<int, DAlbum*> yAlbumMap;
+
+ AlbumIterator it(d->rootDAlbum);
+ while (it.current())
+ {
+ DAlbum* a = (DAlbum*)(*it);
+ if (a->range() == DAlbum::Month)
+ mAlbumMap.insert(a->date(), a);
+ else
+ yAlbumMap.insert(a->date().year(), a);
+ ++it;
+ }
+
+ QMap<QDateTime, int> datesStatMap;
+ QDataStream ds(data, IO_ReadOnly);
+ ds >> datesStatMap;
+
+ QMap<YearMonth, int> yearMonthMap;
+ for ( QMap<QDateTime, int>::iterator it = datesStatMap.begin();
+ it != datesStatMap.end(); ++it )
+ {
+ QMap<YearMonth, int>::iterator it2 = yearMonthMap.find(YearMonth(it.key().date().year(), it.key().date().month()));
+ if ( it2 == yearMonthMap.end() )
+ {
+ yearMonthMap.insert( YearMonth(it.key().date().year(), it.key().date().month()), it.data() );
+ }
+ else
+ {
+ yearMonthMap.replace( YearMonth(it.key().date().year(), it.key().date().month()), it2.data() + it.data() );
+ }
+ }
+
+ int year, month;
+ for ( QMap<YearMonth, int>::iterator it = yearMonthMap.begin();
+ it != yearMonthMap.end(); ++it )
+ {
+ year = it.key().first;
+ month = it.key().second;
+
+ QDate md(year, month, 1);
+
+ // Do we already have this Month album
+ if (mAlbumMap.contains(md))
+ {
+ // already there. remove Month album from map
+ mAlbumMap.remove(md);
+
+ if (yAlbumMap.contains(year))
+ {
+ // already there. remove from map
+ yAlbumMap.remove(year);
+ }
+
+ continue;
+ }
+
+ // Check if Year Album already exist.
+ DAlbum *yAlbum = 0;
+ AlbumIterator it(d->rootDAlbum);
+ while (it.current())
+ {
+ DAlbum* a = (DAlbum*)(*it);
+ if (a->date() == QDate(year, 1, 1) && a->range() == DAlbum::Year)
+ {
+ yAlbum = a;
+ break;
+ }
+ ++it;
+ }
+
+ // If no, create Year album.
+ if (!yAlbum)
+ {
+ yAlbum = new DAlbum(QDate(year, 1, 1), false, DAlbum::Year);
+ yAlbum->setParent(d->rootDAlbum);
+ d->albumIntDict.insert(yAlbum->globalID(), yAlbum);
+ emit signalAlbumAdded(yAlbum);
+ }
+
+ // Create Month album
+ DAlbum *mAlbum = new DAlbum(md);
+ mAlbum->setParent(yAlbum);
+ d->albumIntDict.insert(mAlbum->globalID(), mAlbum);
+ emit signalAlbumAdded(mAlbum);
+ }
+
+ // Now the items contained in the maps are the ones which
+ // have been deleted.
+ for (QMap<QDate, DAlbum*>::iterator it = mAlbumMap.begin();
+ it != mAlbumMap.end(); ++it)
+ {
+ DAlbum* album = it.data();
+ emit signalAlbumDeleted(album);
+ d->albumIntDict.remove(album->globalID());
+ delete album;
+ }
+
+ for (QMap<int, DAlbum*>::iterator it = yAlbumMap.begin();
+ it != yAlbumMap.end(); ++it)
+ {
+ DAlbum* album = it.data();
+ emit signalAlbumDeleted(album);
+ d->albumIntDict.remove(album->globalID());
+ delete album;
+ }
+
+ emit signalDAlbumsDirty(yearMonthMap);
+ emit signalDatesMapDirty(datesStatMap);
+}
+
+void AlbumManager::slotDirty(const QString& path)
+{
+ DDebug() << "Noticed file change in directory " << path << endl;
+ QString url = QDir::cleanDirPath(path);
+ url = QDir::cleanDirPath(url.remove(d->libraryPath));
+
+ if (url.isEmpty())
+ url = "/";
+
+ if (d->dirtyAlbums.contains(url))
+ return;
+
+ // is the signal for the directory containing the database file?
+ if (url == "/")
+ {
+ // retrieve modification dates
+ QFileInfo dbFile(d->libraryPath);
+ QValueList<QDateTime> modList = d->buildDirectoryModList(dbFile);
+
+ // check for equality
+ if (modList == d->dbPathModificationDateList)
+ {
+ DDebug() << "Filtering out db-file-triggered dir watch signal" << endl;
+ // we can skip the signal
+ return;
+ }
+
+ // set new list
+ d->dbPathModificationDateList = modList;
+ }
+
+ d->dirtyAlbums.append(url);
+
+ if (DIO::running())
+ return;
+
+ KURL u;
+ u.setProtocol("digikamalbums");
+ u.setPath(d->dirtyAlbums.first());
+ d->dirtyAlbums.pop_front();
+
+ DIO::scan(u);
+}
+
+} // namespace Digikam