/* ============================================================ * * 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 * Copyright (C) 2006-2008 by Gilles Caulier * * 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 // C Ansi includes. extern "C" { #include #include #include } // C++ includes. #include #include #include #include // TQt includes. #include #include #include #include #include #include #include // KDE includes. #include #include #include #include #include #include #include #include #include // 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 TQDict PAlbumDict; typedef TQIntDict AlbumIntDict; typedef TQValueList 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; TQString libraryPath; TQStringList 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; TQValueList buildDirectoryModList(const TQFileInfo &dbFile) { // retrieve modification dates of all files in the database-file dir TQValueList modList; const TQFileInfoList *fileInfoList = dbFile.dir().entryInfoList(TQDir::Files | TQDir::Dirs ); // build list TQFileInfoListIterator it(*fileInfoList); TQFileInfo *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 TQString& path, SplashScreen *splash) { TQString cleanPath = TQDir::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; TQString dbPath = cleanPath + "/digikam3.db"; #ifdef NFS_HACK dbPath = locateLocal("appdata", KIO::encodeFileName(TQDir::cleanDirPath(dbPath))); #endif d->db->setDBPath(dbPath); // -- Locale Checking --------------------------------------------------------- TQString currLocale(TQTextCodec::codecForLocale()->name()); TQString 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) TQString 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 TQFileInfo 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(); } } TQString AlbumManager::getLibraryPath() const { return d->libraryPath; } void AlbumManager::startScan() { if (!d->changed) return; d->changed = false; d->dirWatch = new KDirWatch(this); connect(d->dirWatch, TQT_SIGNAL(dirty(const TQString&)), this, TQT_SLOT(slotDirty(const TQString&))); KDirWatch::Method m = d->dirWatch->internalMethod(); TQString mName("FAM"); if (m == KDirWatch::DNotify) mName = TQString("DNotify"); else if (m == KDirWatch::Stat) mName = TQString("Stat"); else if (m == KDirWatch::INotify) mName = TQString("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(TQDate(), 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 TQMap 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 = TQDir::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 TQString. // setPath takes care for escaping characters that are valid for files but not for URLs ('#') KURL u; u.setPath(info.url); TQString name = u.fileName(); // Get its parent TQString 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("/"); TQByteArray ba; TQDataStream 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, TQByteArray(), false); d->albumListJob->addMetaData("folders", "yes"); connect(d->albumListJob, TQT_SIGNAL(result(KIO::Job*)), this, TQT_SLOT(slotAlbumsJobResult(KIO::Job*))); connect(d->albumListJob, TQT_SIGNAL(data(KIO::Job*, const TQByteArray&)), this, TQT_SLOT(slotAlbumsJobData(KIO::Job*, const TQByteArray&))); } void AlbumManager::scanTAlbums() { // list TAlbums directly from the db // first insert all the current TAlbums into a map for quick lookup typedef TQMap 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 { TQIntDict 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 TQIntDictIterator 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("/"); TQByteArray ba; TQDataStream 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, TQByteArray(), false); d->tagListJob->addMetaData("folders", "yes"); connect(d->tagListJob, TQT_SIGNAL(result(KIO::Job*)), this, TQT_SLOT(slotTagsJobResult(KIO::Job*))); connect(d->tagListJob, TQT_SIGNAL(data(KIO::Job*, const TQByteArray&)), this, TQT_SLOT(slotTagsJobData(KIO::Job*, const TQByteArray&))); } void AlbumManager::scanSAlbums() { // list SAlbums directly from the db // first insert all the current SAlbums into a map for quick lookup typedef TQMap 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") == TQString::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("/"); TQByteArray ba; TQDataStream 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, TQByteArray(), false); d->dateListJob->addMetaData("folders", "yes"); connect(d->dateListJob, TQT_SIGNAL(result(KIO::Job*)), this, TQT_SLOT(slotDatesJobResult(KIO::Job*))); connect(d->dateListJob, TQT_SIGNAL(data(KIO::Job*, const TQByteArray&)), this, TQT_SLOT(slotDatesJobData(KIO::Job*, const TQByteArray&))); } 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 { TQString path = url.path(); path.remove(d->libraryPath); path = TQDir::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 TQString &tagPath) const { // handle gracefully with or without leading slash bool withLeadingSlash = tagPath.startsWith("/"); AlbumIterator it(d->rootTAlbum); while (it.current()) { TAlbum *talbum = static_cast(*it); if (talbum->tagPath(withLeadingSlash) == tagPath) return talbum; ++it; } return 0; } PAlbum* AlbumManager::createPAlbum(PAlbum* parent, const TQString& name, const TQString& caption, const TQDate& date, const TQString& collection, TQString& 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; } TQString path = parent->folderPath(); path += '/' + name; path = TQDir::cleanDirPath(path); // make the directory synchronously, so that we can add the // album info to the database directly if (::mkdir(TQFile::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 TQString& newName, TQString& 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; } TQString oldURL = album->url(); KURL u = KURL::fromPathOrURL(album->folderPath()).upURL(); u.addPath(newName); u.cleanPath(); if (::rename(TQFile::encodeName(album->folderPath()), TQFile::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, TQ_LLONG iconID, TQString& 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 TQString& name, const TQString& iconkde, TQString& 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 TQStringList &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, TQString& 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 TQString& name, TQString& 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, TQString &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 TQString& iconKDE, TQ_LLONG iconID, TQString& 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) { TQString 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); TQString 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 TQByteArray& data) { if (data.isEmpty()) return; TQMap albumsStatMap; TQDataStream 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 TQByteArray& data) { if (data.isEmpty()) return; TQMap tagsStatMap; TQDataStream 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 TQByteArray& data) { if (data.isEmpty()) return; // insert all the DAlbums into a qmap for quick access TQMap mAlbumMap; TQMap 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; } TQMap datesStatMap; TQDataStream ds(data, IO_ReadOnly); ds >> datesStatMap; TQMap yearMonthMap; for ( TQMap::iterator it = datesStatMap.begin(); it != datesStatMap.end(); ++it ) { TQMap::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 ( TQMap::iterator it = yearMonthMap.begin(); it != yearMonthMap.end(); ++it ) { year = it.key().first; month = it.key().second; TQDate 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() == TQDate(year, 1, 1) && a->range() == DAlbum::Year) { yAlbum = a; break; } ++it; } // If no, create Year album. if (!yAlbum) { yAlbum = new DAlbum(TQDate(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 (TQMap::iterator it = mAlbumMap.begin(); it != mAlbumMap.end(); ++it) { DAlbum* album = it.data(); emit signalAlbumDeleted(album); d->albumIntDict.remove(album->globalID()); delete album; } for (TQMap::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 TQString& path) { DDebug() << "Noticed file change in directory " << path << endl; TQString url = TQDir::cleanDirPath(path); url = TQDir::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 TQFileInfo dbFile(d->libraryPath); TQValueList 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