Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
digikam/digikam/digikam/albummanager.cpp

1679 lignes
42 KiB

/* ============================================================
*
* 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>
// TQt includes.
#include <tqfile.h>
#include <tqdir.h>
#include <tqdict.h>
#include <tqintdict.h>
#include <tqcstring.h>
#include <tqtextcodec.h>
#include <tqdatetime.h>
// KDE includes.
#include <tdeconfig.h>
#include <tdelocale.h>
#include <tdeversion.h>
#include <tdemessagebox.h>
#include <kstandarddirs.h>
#include <tdeio/netaccess.h>
#include <tdeio/global.h>
#include <tdeio/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 TQDict<PAlbum> PAlbumDict;
typedef TQIntDict<Album> AlbumIntDict;
typedef TQValueList<TQDateTime> 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;
TDEIO::TransferJob *albumListJob;
TDEIO::TransferJob *dateListJob;
TDEIO::TransferJob *tagListJob;
PAlbum *rootPAlbum;
TAlbum *rootTAlbum;
DAlbum *rootDAlbum;
SAlbum *rootSAlbum;
PAlbumDict pAlbumDict;
AlbumIntDict albumIntDict;
Album *currentAlbum;
AlbumDB *db;
AlbumItemHandler *itemHandler;
TQValueList<TQDateTime> buildDirectoryModList(const TQFileInfo &dbFile)
{
// retrieve modification dates of all files in the database-file dir
TQValueList<TQDateTime> 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", TDEIO::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.
TDEConfig* config = TDEGlobal::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 -------------------------------------
TDEConfig* config = TDEGlobal::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, TQ_SIGNAL(dirty(const TQString&)),
this, TQ_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<TQString, 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 = 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 tdeioslave
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 TDEIO::TransferJob(u, TDEIO::CMD_SPECIAL,
ba, TQByteArray(), false);
d->albumListJob->addMetaData("folders", "yes");
connect(d->albumListJob, TQ_SIGNAL(result(TDEIO::Job*)),
this, TQ_SLOT(slotAlbumsJobResult(TDEIO::Job*)));
connect(d->albumListJob, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
this, TQ_SLOT(slotAlbumsJobData(TDEIO::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<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
{
TQIntDict<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
TQIntDictIterator<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 tdeioslave
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 TDEIO::TransferJob(u, TDEIO::CMD_SPECIAL,
ba, TQByteArray(), false);
d->tagListJob->addMetaData("folders", "yes");
connect(d->tagListJob, TQ_SIGNAL(result(TDEIO::Job*)),
this, TQ_SLOT(slotTagsJobResult(TDEIO::Job*)));
connect(d->tagListJob, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
this, TQ_SLOT(slotTagsJobData(TDEIO::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<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") == 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 tdeioslave
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 TDEIO::TransferJob(u, TDEIO::CMD_SPECIAL,
ba, TQByteArray(), false);
d->dateListJob->addMetaData("folders", "yes");
connect(d->dateListJob, TQ_SIGNAL(result(TDEIO::Job*)),
this, TQ_SLOT(slotDatesJobResult(TDEIO::Job*)));
connect(d->dateListJob, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
this, TQ_SLOT(slotDatesJobData(TDEIO::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<TAlbum *>(*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(TDEIO::Job* job)
{
d->albumListJob = 0;
if (job->error())
{
DWarning() << k_funcinfo << "Failed to list albums" << endl;
return;
}
}
void AlbumManager::slotAlbumsJobData(TDEIO::Job*, const TQByteArray& data)
{
if (data.isEmpty())
return;
TQMap<int, int> albumsStatMap;
TQDataStream ds(data, IO_ReadOnly);
ds >> albumsStatMap;
emit signalPAlbumsDirty(albumsStatMap);
}
void AlbumManager::slotTagsJobResult(TDEIO::Job* job)
{
d->tagListJob = 0;
if (job->error())
{
DWarning() << k_funcinfo << "Failed to list tags" << endl;
return;
}
}
void AlbumManager::slotTagsJobData(TDEIO::Job*, const TQByteArray& data)
{
if (data.isEmpty())
return;
TQMap<int, int> tagsStatMap;
TQDataStream ds(data, IO_ReadOnly);
ds >> tagsStatMap;
emit signalTAlbumsDirty(tagsStatMap);
}
void AlbumManager::slotDatesJobResult(TDEIO::Job* job)
{
d->dateListJob = 0;
if (job->error())
{
DWarning() << k_funcinfo << "Failed to list dates" << endl;
return;
}
emit signalAllDAlbumsLoaded();
}
void AlbumManager::slotDatesJobData(TDEIO::Job*, const TQByteArray& data)
{
if (data.isEmpty())
return;
// insert all the DAlbums into a qmap for quick access
TQMap<TQDate, DAlbum*> mAlbumMap;
TQMap<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;
}
TQMap<TQDateTime, int> datesStatMap;
TQDataStream ds(data, IO_ReadOnly);
ds >> datesStatMap;
TQMap<YearMonth, int> yearMonthMap;
for ( TQMap<TQDateTime, int>::iterator it = datesStatMap.begin();
it != datesStatMap.end(); ++it )
{
TQMap<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 ( TQMap<YearMonth, int>::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 it2(d->rootDAlbum);
while (it2.current())
{
DAlbum* a = (DAlbum*)(*it2);
if (a->date() == TQDate(year, 1, 1) && a->range() == DAlbum::Year)
{
yAlbum = a;
break;
}
++it2;
}
// 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<TQDate, DAlbum*>::iterator it = mAlbumMap.begin();
it != mAlbumMap.end(); ++it)
{
DAlbum* album = it.data();
emit signalAlbumDeleted(album);
d->albumIntDict.remove(album->globalID());
delete album;
}
for (TQMap<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 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<TQDateTime> 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