summaryrefslogtreecommitdiffstats
path: root/src/tdeioslave/digikamalbums.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tdeioslave/digikamalbums.cpp')
-rw-r--r--src/tdeioslave/digikamalbums.cpp1969
1 files changed, 1969 insertions, 0 deletions
diff --git a/src/tdeioslave/digikamalbums.cpp b/src/tdeioslave/digikamalbums.cpp
new file mode 100644
index 00000000..c16080af
--- /dev/null
+++ b/src/tdeioslave/digikamalbums.cpp
@@ -0,0 +1,1969 @@
+/* ============================================================
+ *
+ * This file is a part of digiKam project
+ * http://www.digikam.org
+ *
+ * Date : 2005-04-21
+ * Description : a tdeio-slave to process file operations on
+ * digiKam albums.
+ *
+ * Copyright (C) 2005 by Renchi Raju <[email protected]>
+ *
+ * Lots of the file io code is copied from KDE file tdeioslave.
+ * Copyright for the KDE file tdeioslave follows:
+ * Copyright (C) 2000-2002 Stephan Kulow <[email protected]>
+ * Copyright (C) 2000-2002 David Faure <[email protected]>
+ * Copyright (C) 2000-2002 Waldo Bastian <[email protected]>
+ *
+ * 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.
+ *
+ * ============================================================ */
+
+#define MAX_IPC_SIZE (1024*32)
+
+// C Ansi includes.
+
+extern "C"
+{
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <utime.h>
+}
+
+// C++ includes.
+
+#include <cstdlib>
+#include <cstdio>
+#include <ctime>
+#include <cerrno>
+
+// TQt includes.
+
+#include <tqfile.h>
+#include <tqfileinfo.h>
+#include <tqdatastream.h>
+#include <tqregexp.h>
+#include <tqdir.h>
+
+// KDE includes.
+
+#include <tdeglobal.h>
+#include <tdelocale.h>
+#include <kinstance.h>
+#include <tdefilemetainfo.h>
+#include <kmimetype.h>
+#include <kdebug.h>
+#include <tdeio/global.h>
+#include <tdeio/ioslave_defaults.h>
+#include <klargefile.h>
+#include <tdeversion.h>
+
+// LibKDcraw includes.
+
+#include <libkdcraw/version.h>
+#include <libkdcraw/kdcraw.h>
+
+#if KDCRAW_VERSION < 0x000106
+#include <libkdcraw/dcrawbinary.h>
+#endif
+
+// Local includes.
+
+#include "dmetadata.h"
+#include "sqlitedb.h"
+#include "digikam_export.h"
+#include "digikamalbums.h"
+
+tdeio_digikamalbums::tdeio_digikamalbums(const TQCString &pool_socket,
+ const TQCString &app_socket)
+ : SlaveBase("tdeio_digikamalbums", pool_socket, app_socket)
+{
+}
+
+tdeio_digikamalbums::~tdeio_digikamalbums()
+{
+}
+
+static TQValueList<TQRegExp> makeFilterList( const TQString &filter )
+{
+ TQValueList<TQRegExp> regExps;
+ if ( filter.isEmpty() )
+ return regExps;
+
+ TQChar sep( ';' );
+ int i = filter.find( sep, 0 );
+ if ( i == -1 && filter.find( ' ', 0 ) != -1 )
+ sep = TQChar( ' ' );
+
+ TQStringList list = TQStringList::split( sep, filter );
+ TQStringList::Iterator it = list.begin();
+ while ( it != list.end() ) {
+ regExps << TQRegExp( (*it).stripWhiteSpace(), false, true );
+ ++it;
+ }
+ return regExps;
+}
+
+static bool matchFilterList( const TQValueList<TQRegExp>& filters,
+ const TQString &fileName )
+{
+ TQValueList<TQRegExp>::ConstIterator rit = filters.begin();
+ while ( rit != filters.end() ) {
+ if ( (*rit).exactMatch(fileName) )
+ return true;
+ ++rit;
+ }
+ return false;
+}
+
+void tdeio_digikamalbums::special(const TQByteArray& data)
+{
+ bool folders = (metaData("folders") == "yes");
+
+ TQString libraryPath;
+ KURL kurl;
+ TQString url;
+ TQString urlWithTrailingSlash;
+ TQString filter;
+ int getDimensions;
+ int scan = 0;
+ int recurseAlbums;
+ int recurseTags;
+
+ TQDataStream ds(data, IO_ReadOnly);
+ ds >> libraryPath;
+ ds >> kurl;
+ ds >> filter;
+ ds >> getDimensions;
+ ds >> recurseAlbums;
+ ds >> recurseTags;
+ if (!ds.atEnd())
+ ds >> scan;
+
+ libraryPath = TQDir::cleanDirPath(libraryPath);
+
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_sqlDB.closeDB();
+ m_sqlDB.openDB(libraryPath);
+ }
+
+ url = kurl.path();
+
+ if (scan)
+ {
+ scanAlbum(url);
+ finished();
+ return;
+ }
+
+ TQValueList<TQRegExp> regex = makeFilterList(filter);
+ TQByteArray ba;
+
+ if (folders) // Special mode to stats all album items
+ {
+ TQMap<int, int> albumsStatMap;
+ TQStringList values, allAbumIDs;
+ int albumID;
+
+ // initialize allAbumIDs with all existing albums from db to prevent
+ // wrong album image counters
+ m_sqlDB.execSql(TQString("SELECT id from Albums"), &allAbumIDs);
+
+ for ( TQStringList::iterator it = allAbumIDs.begin(); it != allAbumIDs.end(); ++it)
+ {
+ albumID = (*it).toInt();
+ albumsStatMap.insert(albumID, 0);
+ }
+
+ // now we can count the images assigned to albums
+ m_sqlDB.execSql(TQString("SELECT dirid, Images.name FROM Images "
+ "WHERE Images.dirid IN (SELECT DISTINCT id FROM Albums)"), &values);
+
+ for ( TQStringList::iterator it = values.begin(); it != values.end(); )
+ {
+ albumID = (*it).toInt();
+ ++it;
+
+ if ( matchFilterList( regex, *it ) )
+ {
+ TQMap<int, int>::iterator it2 = albumsStatMap.find(albumID);
+ if ( it2 == albumsStatMap.end() )
+ albumsStatMap.insert(albumID, 1);
+ else
+ albumsStatMap.replace(albumID, it2.data() + 1);
+ }
+
+ ++it;
+ }
+
+ TQDataStream os(ba, IO_WriteOnly);
+ os << albumsStatMap;
+ }
+ else
+ {
+ TQStringList albumvalues;
+ if (recurseAlbums)
+ {
+ // Search for albums and sub-albums:
+ // For this, get the path with a trailing "/".
+ // Otherwise albums on the same level like "Paris", "Paris 2006",
+ // would be found in addition to "Paris/*".
+ urlWithTrailingSlash = kurl.path(1);
+
+ m_sqlDB.execSql(TQString("SELECT DISTINCT id, url FROM Albums WHERE url='%1' OR url LIKE '%2\%';")
+ .arg(escapeString(url)).arg(escapeString(urlWithTrailingSlash)), &albumvalues);
+ }
+ else
+ {
+ // Search for albums
+
+ m_sqlDB.execSql(TQString("SELECT DISTINCT id, url FROM Albums WHERE url='%1';")
+ .arg(escapeString(url)), &albumvalues);
+ }
+
+ TQDataStream* os = new TQDataStream(ba, IO_WriteOnly);
+
+ TQString base;
+ TQ_LLONG id;
+ TQString name;
+ TQString date;
+ TQSize dims;
+
+ struct stat stbuf;
+
+ TQStringList values;
+ TQString albumurl;
+ int albumid;
+
+ // Loop over all albums:
+ int count = 0 ;
+ for (TQStringList::iterator albumit = albumvalues.begin(); albumit != albumvalues.end();)
+ {
+ albumid = (*albumit).toLongLong();
+ ++albumit;
+ albumurl = *albumit;
+ ++albumit;
+
+ base = libraryPath + albumurl + '/';
+
+ values.clear();
+ m_sqlDB.execSql(TQString("SELECT id, name, datetime FROM Images "
+ "WHERE dirid = %1;")
+ .arg(albumid), &values);
+
+ // Loop over all images in each album (specified by its albumid).
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ id = (*it).toLongLong();
+ ++it;
+ name = *it;
+ ++it;
+ date = *it;
+ ++it;
+
+ if (!matchFilterList(regex, name))
+ continue;
+
+ if (::stat(TQFile::encodeName(base + name), &stbuf) != 0)
+ continue;
+
+ dims = TQSize();
+ if (getDimensions)
+ {
+ TQFileInfo fileInfo(base + name);
+#if KDCRAW_VERSION < 0x000106
+ TQString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
+#else
+ TQString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
+#endif
+ TQString ext = fileInfo.extension(false).upper();
+
+ if (!ext.isEmpty() && rawFilesExt.upper().contains(ext))
+ {
+ Digikam::DMetadata metaData(base + name);
+ dims = metaData.getImageDimensions();
+ }
+ else
+ {
+ KFileMetaInfo metaInfo(base + name);
+ if (metaInfo.isValid())
+ {
+ if (metaInfo.containsGroup("Jpeg EXIF Data"))
+ {
+ dims = metaInfo.group("Jpeg EXIF Data").
+ item("Dimensions").value().toSize();
+ }
+ else if (metaInfo.containsGroup("General"))
+ {
+ dims = metaInfo.group("General").
+ item("Dimensions").value().toSize();
+ }
+ else if (metaInfo.containsGroup("Technical"))
+ {
+ dims = metaInfo.group("Technical").
+ item("Dimensions").value().toSize();
+ }
+ }
+ }
+ }
+
+ *os << id;
+ *os << albumid;
+ *os << name;
+ *os << date;
+ *os << static_cast<size_t>(stbuf.st_size);
+ *os << dims;
+
+ count++;
+
+ // Send images in batches of 200.
+ if (count > 200)
+ {
+ delete os;
+ os = 0;
+
+ SlaveBase::data(ba);
+ ba.resize(0);
+
+ count = 0;
+ os = new TQDataStream(ba, IO_WriteOnly);
+ }
+ }
+ count++;
+ }
+ }
+
+ SlaveBase::data(ba);
+
+ finished();
+}
+
+static int write_all(int fd, const char *buf, size_t len)
+{
+ while (len > 0)
+ {
+ ssize_t written = write(fd, buf, len);
+ if (written < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ }
+ buf += written;
+ len -= written;
+ }
+ return 0;
+}
+
+void tdeio_digikamalbums::get( const KURL& url )
+{
+// Code duplication from file:// ioslave
+ kdDebug() << k_funcinfo << " : " << url << endl;
+
+ // get the libraryPath
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ // no need to open the db. we don't need to read/write to it
+
+ TQCString path(TQFile::encodeName(libraryPath + url.path()));
+ KDE_struct_stat buff;
+ if ( KDE_stat( path.data(), &buff ) == -1 )
+ {
+ if ( errno == EACCES )
+ error( TDEIO::ERR_ACCESS_DENIED, url.url() );
+ else
+ error( TDEIO::ERR_DOES_NOT_EXIST, url.url() );
+ return;
+ }
+
+ if ( S_ISDIR( buff.st_mode ) )
+ {
+ error( TDEIO::ERR_IS_DIRECTORY, url.url() );
+ return;
+ }
+
+ if ( !S_ISREG( buff.st_mode ) )
+ {
+ error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.url() );
+ return;
+ }
+
+ int fd = KDE_open( path.data(), O_RDONLY);
+ if ( fd < 0 )
+ {
+ error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.url() );
+ return;
+ }
+
+ // Determine the mimetype of the file to be retrieved, and emit it.
+ // This is mandatory in all slaves (for KRun/BrowserRun to work).
+ KMimeType::Ptr mt = KMimeType::findByURL( libraryPath + url.path(), buff.st_mode,
+ true);
+ emit mimeType( mt->name() );
+
+ totalSize( buff.st_size );
+
+ char buffer[ MAX_IPC_SIZE ];
+ TQByteArray array;
+ TDEIO::filesize_t processed_size = 0;
+
+ while (1)
+ {
+ int n = ::read( fd, buffer, MAX_IPC_SIZE );
+ if (n == -1)
+ {
+ if (errno == EINTR)
+ continue;
+ error( TDEIO::ERR_COULD_NOT_READ, url.url());
+ close(fd);
+ return;
+ }
+ if (n == 0)
+ break; // Finished
+
+ array.setRawData(buffer, n);
+ data( array );
+ array.resetRawData(buffer, n);
+
+ processed_size += n;
+ processedSize( processed_size );
+ }
+
+ data( TQByteArray() );
+ close( fd );
+
+ processedSize( buff.st_size );
+ finished();
+}
+
+void tdeio_digikamalbums::put(const KURL& url, int permissions, bool overwrite, bool /*resume*/)
+{
+// Code duplication from file:// ioslave
+ kdDebug() << k_funcinfo << " : " << url.url() << endl;
+
+ // get the libraryPath
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ // open the db if needed
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_sqlDB.closeDB();
+ m_sqlDB.openDB(m_libraryPath);
+ }
+
+ // build the album list
+ buildAlbumList();
+
+ // get the parent album
+ AlbumInfo album = findAlbum(url.directory());
+ if (album.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Source album %1 not found in database")
+ .arg(url.directory()));
+ return;
+ }
+
+
+ TQString dest = libraryPath + url.path();
+ TQCString _dest( TQFile::encodeName(dest));
+
+ // check if the original file exists and we are not allowed to overwrite it
+ KDE_struct_stat buff;
+ bool origExists = (KDE_lstat( _dest.data(), &buff ) != -1);
+ if ( origExists && !overwrite)
+ {
+ if (S_ISDIR(buff.st_mode))
+ error( TDEIO::ERR_DIR_ALREADY_EXIST, url.url() );
+ else
+ error( TDEIO::ERR_FILE_ALREADY_EXIST, url.url() );
+ return;
+ }
+
+ // get the permissions we are supposed to set
+ mode_t initialPerms;
+ if (permissions != -1)
+ initialPerms = permissions | S_IWUSR | S_IRUSR;
+ else
+ initialPerms = 0666;
+
+ // open the destination file
+ int fd = KDE_open(_dest.data(), O_CREAT | O_TRUNC | O_WRONLY, initialPerms);
+ if ( fd < 0 )
+ {
+ kdWarning() << "####################### COULD NOT OPEN " << dest << endl;
+ if ( errno == EACCES )
+ error( TDEIO::ERR_WRITE_ACCESS_DENIED, url.url() );
+ else
+ error( TDEIO::ERR_CANNOT_OPEN_FOR_WRITING, url.url() );
+ return;
+ }
+
+ int result;
+
+ // Loop until we get 0 (end of data)
+ do
+ {
+ TQByteArray buffer;
+ dataReq();
+ result = readData( buffer );
+
+ if (result >= 0)
+ {
+ if (write_all( fd, buffer.data(), buffer.size()))
+ {
+ if ( errno == ENOSPC ) // disk full
+ {
+ error( TDEIO::ERR_DISK_FULL, url.url());
+ result = -1;
+ }
+ else
+ {
+ kdWarning() << "Couldn't write. Error:" << strerror(errno) << endl;
+ error( TDEIO::ERR_COULD_NOT_WRITE, url.url());
+ result = -1;
+ }
+ }
+ }
+ }
+ while ( result > 0 );
+
+ // An error occurred deal with it.
+ if (result < 0)
+ {
+ kdDebug() << "Error during 'put'. Aborting." << endl;
+
+ close(fd);
+ remove(_dest);
+ return;
+ }
+
+ // close the file
+ if ( close(fd) )
+ {
+ kdWarning() << "Error when closing file descriptor:" << strerror(errno) << endl;
+ error( TDEIO::ERR_COULD_NOT_WRITE, url.url());
+ return;
+ }
+
+ // set final permissions
+ if ( permissions != -1 )
+ {
+ if (::chmod(_dest.data(), permissions) != 0)
+ {
+ // couldn't chmod. Eat the error if the filesystem apparently doesn't support it.
+ if ( TDEIO::testFileSystemFlag( _dest, TDEIO::SupportsChmod ) )
+ warning( i18n( "Could not change permissions for\n%1" ).arg( url.url() ) );
+ }
+ }
+
+ // set modification time
+ const TQString mtimeStr = metaData( "modified" );
+ if ( !mtimeStr.isEmpty() ) {
+ TQDateTime dt = TQDateTime::fromString( mtimeStr, TQt::ISODate );
+ if ( dt.isValid() ) {
+ KDE_struct_stat dest_statbuf;
+ if (KDE_stat( _dest.data(), &dest_statbuf ) == 0) {
+ struct utimbuf utbuf;
+ utbuf.actime = dest_statbuf.st_atime; // access time, unchanged
+ utbuf.modtime = dt.toTime_t(); // modification time
+ kdDebug() << k_funcinfo << "setting modtime to " << utbuf.modtime << endl;
+ utime( _dest.data(), &utbuf );
+ }
+ }
+
+ }
+
+ // First check if the file is already in database
+ if (!findImage(album.id, url.fileName()))
+ {
+ // Now insert the file into the database
+ addImage(album.id, m_libraryPath + url.path());
+ }
+
+ // We have done our job => finish
+ finished();
+}
+
+void tdeio_digikamalbums::copy( const KURL &src, const KURL &dst, int mode, bool overwrite )
+{
+// Code duplication from file:// ioslave?
+ kdDebug() << k_funcinfo << "Src: " << src.path() << ", Dst: " << dst.path() << endl;
+
+ // get the album library path
+ TQString libraryPath = src.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ // check that the src and dst album library paths match
+ TQString dstLibraryPath = dst.user();
+ if (libraryPath != dstLibraryPath)
+ {
+ error(TDEIO::ERR_UNKNOWN,
+ TQString("Source and Destination have different Album Library Paths. ") +
+ TQString("Src: ") + src.user() +
+ TQString(", Dest: ") + dst.user());
+ return;
+ }
+
+ // open the db if needed
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_sqlDB.closeDB();
+ m_sqlDB.openDB(m_libraryPath);
+ }
+
+ // build the album list
+ buildAlbumList();
+
+ // find the src parent album
+ AlbumInfo srcAlbum = findAlbum(src.directory());
+ if (srcAlbum.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, TQString("Source album %1 not found in database")
+ .arg(src.directory()));
+ return;
+ }
+
+ // find the dst parent album
+ AlbumInfo dstAlbum = findAlbum(dst.directory());
+ if (dstAlbum.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, TQString("Destination album %1 not found in database")
+ .arg(dst.directory()));
+ return;
+ }
+
+ // if the filename is .digikam_properties, we have been asked to copy the
+ // metadata of the src album to the dst album
+ if (src.fileName() == ".digikam_properties")
+ {
+ // no duplication in AlbumDB?
+ // copy metadata of album to destination album
+ m_sqlDB.execSql( TQString("UPDATE Albums SET date='%1', caption='%2', "
+ "collection='%3', icon=%4 ")
+ .arg(srcAlbum.date.toString(TQt::ISODate),
+ escapeString(srcAlbum.caption),
+ escapeString(srcAlbum.collection),
+ TQString::number(srcAlbum.icon)) +
+ TQString( " WHERE id=%1" )
+ .arg(dstAlbum.id) );
+ finished();
+ return;
+ }
+
+ TQCString _src( TQFile::encodeName(libraryPath + src.path()));
+ TQCString _dst( TQFile::encodeName(libraryPath + dst.path()));
+
+ // stat the src file
+ KDE_struct_stat buff_src;
+ if ( KDE_stat( _src.data(), &buff_src ) == -1 )
+ {
+ if ( errno == EACCES )
+ error( TDEIO::ERR_ACCESS_DENIED, src.url() );
+ else
+ error( TDEIO::ERR_DOES_NOT_EXIST, src.url() );
+ return;
+ }
+
+ // bail out if its a directory
+ if ( S_ISDIR( buff_src.st_mode ) )
+ {
+ error( TDEIO::ERR_IS_DIRECTORY, src.url() );
+ return;
+ }
+
+ // bail out if its a socket or fifo
+ if ( S_ISFIFO( buff_src.st_mode ) || S_ISSOCK ( buff_src.st_mode ) )
+ {
+ error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, src.url() );
+ return;
+ }
+
+ // stat the dst file
+ KDE_struct_stat buff_dest;
+ bool dest_exists = ( KDE_lstat( _dst.data(), &buff_dest ) != -1 );
+ if ( dest_exists )
+ {
+ // bail out if its a directory
+ if (S_ISDIR(buff_dest.st_mode))
+ {
+ error( TDEIO::ERR_DIR_ALREADY_EXIST, dst.url() );
+ return;
+ }
+
+ // if !overwrite bail out
+ if (!overwrite)
+ {
+ error( TDEIO::ERR_FILE_ALREADY_EXIST, dst.url() );
+ return;
+ }
+
+ // If the destination is a symlink and overwrite is true,
+ // remove the symlink first to prevent the scenario where
+ // the symlink actually points to current source!
+ if (overwrite && S_ISLNK(buff_dest.st_mode))
+ {
+ remove( _dst.data() );
+ }
+ }
+
+ // now open the src file
+ int src_fd = KDE_open( _src.data(), O_RDONLY);
+ if ( src_fd < 0 )
+ {
+ error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, src.path() );
+ return;
+ }
+
+ // get the permissions we are supposed to set
+ mode_t initialMode;
+ if (mode != -1)
+ initialMode = mode | S_IWUSR;
+ else
+ initialMode = 0666;
+
+ // open the destination file
+ int dest_fd = KDE_open(_dst.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode);
+ if ( dest_fd < 0 )
+ {
+ kdDebug() << "###### COULD NOT WRITE " << dst.url() << endl;
+ if ( errno == EACCES )
+ {
+ error( TDEIO::ERR_WRITE_ACCESS_DENIED, dst.url() );
+ }
+ else
+ {
+ error( TDEIO::ERR_CANNOT_OPEN_FOR_WRITING, dst.url() );
+ }
+ close(src_fd);
+ return;
+ }
+
+ // emit the total size for copying
+ totalSize( buff_src.st_size );
+
+ TDEIO::filesize_t processed_size = 0;
+ char buffer[ MAX_IPC_SIZE ];
+ int n;
+
+ while (1)
+ {
+ // read in chunks of MAX_IPC_SIZE
+ n = ::read( src_fd, buffer, MAX_IPC_SIZE );
+
+ if (n == -1)
+ {
+ if (errno == EINTR)
+ continue;
+ error( TDEIO::ERR_COULD_NOT_READ, src.path());
+ close(src_fd);
+ close(dest_fd);
+ return;
+ }
+
+ // Finished ?
+ if (n == 0)
+ break;
+
+ // write to the destination file
+ if (write_all( dest_fd, buffer, n))
+ {
+ close(src_fd);
+ close(dest_fd);
+
+ if ( errno == ENOSPC ) // disk full
+ {
+ error( TDEIO::ERR_DISK_FULL, dst.url());
+ remove( _dst.data() );
+ }
+ else
+ {
+ kdWarning() << "Couldn't write[2]. Error:" << strerror(errno) << endl;
+ error( TDEIO::ERR_COULD_NOT_WRITE, dst.url());
+ }
+ return;
+ }
+
+ processedSize( processed_size );
+ }
+
+
+ close( src_fd );
+
+ if (close( dest_fd))
+ {
+ kdWarning() << "Error when closing file descriptor[2]:" << strerror(errno) << endl;
+ error( TDEIO::ERR_COULD_NOT_WRITE, dst.url());
+ return;
+ }
+
+ // set final permissions
+ if ( mode != -1 )
+ {
+ if (::chmod(_dst.data(), mode) != 0)
+ {
+ // Eat the error if the filesystem apparently doesn't support chmod.
+ if ( TDEIO::testFileSystemFlag( _dst, TDEIO::SupportsChmod ) )
+ warning( i18n( "Could not change permissions for\n%1" ).arg( dst.url() ) );
+ }
+ }
+
+ // copy access and modification time
+ struct utimbuf ut;
+ ut.actime = buff_src.st_atime;
+ ut.modtime = buff_src.st_mtime;
+ if ( ::utime( _dst.data(), &ut ) != 0 )
+ {
+ kdWarning() << TQString::fromLatin1("Couldn't preserve access and modification time for\n%1")
+ .arg( dst.url() ) << endl;
+ }
+
+ // now copy the metadata over
+ copyImage(srcAlbum.id, src.fileName(), dstAlbum.id, dst.fileName());
+
+ processedSize( buff_src.st_size );
+ finished();
+}
+
+void tdeio_digikamalbums::rename( const KURL& src, const KURL& dst, bool overwrite )
+{
+// Code duplication from file:// ioslave?
+ kdDebug() << k_funcinfo << "Src: " << src << ", Dst: " << dst << endl;
+
+ // if the filename is .digikam_properties fake that we renamed it
+ if (src.fileName() == ".digikam_properties")
+ {
+ finished();
+ return;
+ }
+
+ TQString libraryPath = src.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ TQString dstLibraryPath = dst.user();
+ if (libraryPath != dstLibraryPath)
+ {
+ error(TDEIO::ERR_UNKNOWN,
+ i18n("Source and Destination have different Album Library Paths.\n"
+ "Source: %1\n"
+ "Destination: %2")
+ .arg(src.user())
+ .arg(dst.user()));
+ return;
+ }
+
+ // open album db if needed
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_sqlDB.closeDB();
+ m_sqlDB.openDB(m_libraryPath);
+ }
+
+ TQCString csrc( TQFile::encodeName(libraryPath + src.path()));
+ TQCString cdst( TQFile::encodeName(libraryPath + dst.path()));
+
+ // stat the source file/folder
+ KDE_struct_stat buff_src;
+ if ( KDE_stat( csrc.data(), &buff_src ) == -1 )
+ {
+ if ( errno == EACCES )
+ error( TDEIO::ERR_ACCESS_DENIED, src.url() );
+ else
+ error( TDEIO::ERR_DOES_NOT_EXIST, src.url() );
+ return;
+ }
+
+ // stat the destination file/folder
+ KDE_struct_stat buff_dest;
+ bool dest_exists = ( KDE_stat( cdst.data(), &buff_dest ) != -1 );
+ if ( dest_exists )
+ {
+ if (S_ISDIR(buff_dest.st_mode))
+ {
+ error( TDEIO::ERR_DIR_ALREADY_EXIST, dst.url() );
+ return;
+ }
+
+ if (!overwrite)
+ {
+ error( TDEIO::ERR_FILE_ALREADY_EXIST, dst.url() );
+ return;
+ }
+ }
+
+
+ // build album list
+ buildAlbumList();
+
+ AlbumInfo srcAlbum, dstAlbum;
+
+ // check if we are renaming an album or a image
+ bool renamingAlbum = S_ISDIR(buff_src.st_mode);
+
+ if (renamingAlbum)
+ {
+ srcAlbum = findAlbum(src.path());
+ if (srcAlbum.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Source album %1 not found in database")
+ .arg(src.url()));
+ return;
+ }
+ }
+ else
+ {
+ srcAlbum = findAlbum(src.directory());
+ if (srcAlbum.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Source album %1 not found in database")
+ .arg(src.directory()));
+ return;
+ }
+
+ dstAlbum = findAlbum(dst.directory());
+ if (dstAlbum.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Destination album %1 not found in database")
+ .arg(dst.directory()));
+ return;
+ }
+ }
+
+ // actually rename the file/folder
+ if ( ::rename(csrc.data(), cdst.data()))
+ {
+ if (( errno == EACCES ) || (errno == EPERM))
+ {
+ TQFileInfo toCheck(libraryPath + src.path());
+ if (!toCheck.isWritable())
+ error( TDEIO::ERR_CANNOT_RENAME_ORIGINAL, src.path() );
+ else
+ error( TDEIO::ERR_ACCESS_DENIED, dst.path() );
+ }
+ else if (errno == EXDEV)
+ {
+ error( TDEIO::ERR_UNSUPPORTED_ACTION, i18n("This file/folder is on a different "
+ "filesystem through symlinks. "
+ "Moving/Renaming files between "
+ "them is currently unsupported "));
+ }
+ else if (errno == EROFS)
+ { // The file is on a read-only filesystem
+ error( TDEIO::ERR_CANNOT_DELETE, src.url() );
+ }
+ else {
+ error( TDEIO::ERR_CANNOT_RENAME, src.url() );
+ }
+ return;
+ }
+
+ // renaming done. now update the database
+ if (renamingAlbum)
+ {
+ renameAlbum(srcAlbum.url, dst.path());
+ }
+ else
+ {
+ renameImage(srcAlbum.id, src.fileName(),
+ dstAlbum.id, dst.fileName());
+ }
+
+ finished();
+}
+
+void tdeio_digikamalbums::stat( const KURL& url )
+{
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ TDEIO::UDSEntry entry;
+ if (!createUDSEntry(libraryPath + url.path(), entry))
+ {
+ error(TDEIO::ERR_DOES_NOT_EXIST, url.path(-1));
+ return;
+ }
+
+ statEntry(entry);
+ finished();
+}
+
+void tdeio_digikamalbums::listDir( const KURL& url )
+{
+// Code duplication from file:// ioslave?
+ kdDebug() << k_funcinfo << " : " << url.path() << endl;
+
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ kdWarning() << "Album Library Path not supplied to tdeioslave" << endl;
+ return;
+ }
+
+ KDE_struct_stat stbuf;
+ TQString path = libraryPath + url.path();
+ if (KDE_stat(TQFile::encodeName(path), &stbuf) != 0)
+ {
+ error(TDEIO::ERR_DOES_NOT_EXIST, url.path(-1));
+ return;
+ }
+
+ TQDir dir(path);
+ if (!dir.isReadable())
+ {
+ error( TDEIO::ERR_CANNOT_ENTER_DIRECTORY, url.path());
+ return;
+ }
+
+ const TQFileInfoList *list = dir.entryInfoList(TQDir::All|TQDir::Hidden);
+ TQFileInfoListIterator it( *list );
+ TQFileInfo *fi;
+
+ TDEIO::UDSEntry entry;
+ createDigikamPropsUDSEntry(entry);
+ listEntry(entry, false);
+ while ((fi = it.current()) != 0)
+ {
+ if (fi->fileName() != "." && fi->fileName() != ".." || fi->extension(true) == "digikamtempfile.tmp")
+ {
+ createUDSEntry(fi->absFilePath(), entry);
+ listEntry(entry, false);
+ }
+ ++it;
+ }
+
+ entry.clear();
+ listEntry(entry, true);
+ finished();
+}
+
+void tdeio_digikamalbums::mkdir( const KURL& url, int permissions )
+{
+// Code duplication from file:// ioslave?
+ kdDebug() << k_funcinfo << " : " << url.url() << endl;
+
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_sqlDB.closeDB();
+ m_sqlDB.openDB(m_libraryPath);
+ }
+
+ TQString path = libraryPath + url.path();
+ TQCString _path( TQFile::encodeName(path));
+
+ KDE_struct_stat buff;
+ if ( KDE_stat( _path, &buff ) == -1 )
+ {
+ if ( ::mkdir( _path.data(), 0777 /*umask will be applied*/ ) != 0 )
+ {
+ if ( errno == EACCES )
+ {
+ error( TDEIO::ERR_ACCESS_DENIED, path );
+ return;
+ }
+ else if ( errno == ENOSPC )
+ {
+ error( TDEIO::ERR_DISK_FULL, path );
+ return;
+ }
+ else
+ {
+ error( TDEIO::ERR_COULD_NOT_MKDIR, path );
+ return;
+ }
+ }
+ else
+ {
+ // code similar to AlbumDB::addAlbum
+ m_sqlDB.execSql( TQString("REPLACE INTO Albums (url, date) "
+ "VALUES('%1','%2')")
+ .arg(escapeString(url.path()),
+ TQDate::currentDate().toString(TQt::ISODate)) );
+
+ if ( permissions != -1 )
+ {
+ if ( ::chmod( _path.data(), permissions ) == -1 )
+ error( TDEIO::ERR_CANNOT_CHMOD, path );
+ else
+ finished();
+ }
+ else
+ finished();
+ return;
+ }
+ }
+
+ if ( S_ISDIR( buff.st_mode ) )
+ {
+ error( TDEIO::ERR_DIR_ALREADY_EXIST, path );
+ return;
+ }
+
+ error( TDEIO::ERR_FILE_ALREADY_EXIST, path );
+}
+
+void tdeio_digikamalbums::chmod( const KURL& url, int permissions )
+{
+// Code duplication from file:// ioslave?
+ kdDebug() << k_funcinfo << " : " << url.url() << endl;
+
+ // get the album library path
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ TQCString path( TQFile::encodeName(libraryPath + url.path()));
+ if ( ::chmod( path.data(), permissions ) == -1 )
+ error( TDEIO::ERR_CANNOT_CHMOD, url.url() );
+ else
+ finished();
+}
+
+void tdeio_digikamalbums::del( const KURL& url, bool isfile)
+{
+// Code duplication from file:// ioslave?
+ kdDebug() << k_funcinfo << " : " << url.url() << endl;
+
+ // get the album library path
+ TQString libraryPath = url.user();
+ if (libraryPath.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, "Album Library Path not supplied to tdeioslave");
+ return;
+ }
+
+ // open the db if needed
+ if (m_libraryPath != libraryPath)
+ {
+ m_libraryPath = libraryPath;
+ m_sqlDB.closeDB();
+ m_sqlDB.openDB(m_libraryPath);
+ }
+
+ // build the album list
+ buildAlbumList();
+
+ TQCString path( TQFile::encodeName(libraryPath + url.path()));
+
+ if (isfile)
+ {
+ kdDebug( ) << "Deleting file "<< url.url() << endl;
+
+ // if the filename is .digikam_properties fake that we deleted it
+ if (url.fileName() == ".digikam_properties")
+ {
+ finished();
+ return;
+ }
+
+ // find the Album to which this file belongs.
+ AlbumInfo album = findAlbum(url.directory());
+ if (album.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Source album %1 not found in database")
+ .arg(url.directory()));
+ return;
+ }
+
+ // actually delete the file
+ if ( unlink( path.data() ) == -1 )
+ {
+ if ((errno == EACCES) || (errno == EPERM))
+ error( TDEIO::ERR_ACCESS_DENIED, url.url());
+ else if (errno == EISDIR)
+ error( TDEIO::ERR_IS_DIRECTORY, url.url());
+ else
+ error( TDEIO::ERR_CANNOT_DELETE, url.url() );
+ return;
+ }
+
+ // successful deletion. now remove file entry from the database
+ delImage(album.id, url.fileName());
+ }
+ else
+ {
+ kdDebug( ) << "Deleting directory " << url.url() << endl;
+
+ // find the corresponding album entry
+ AlbumInfo album = findAlbum(url.path());
+ if (album.id == -1)
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Source album %1 not found in database")
+ .arg(url.path()));
+ return;
+ }
+
+ if ( ::rmdir( path.data() ) == -1 )
+ {
+ // TODO handle symlink delete
+
+ if ((errno == EACCES) || (errno == EPERM))
+ {
+ error( TDEIO::ERR_ACCESS_DENIED, url.url());
+ return;
+ }
+ else
+ {
+ kdDebug() << "could not rmdir " << perror << endl;
+ error( TDEIO::ERR_COULD_NOT_RMDIR, url.url() );
+ return;
+ }
+ }
+
+ // successful deletion. now remove album entry from the database
+ delAlbum(album.id);
+ }
+
+ finished();
+
+}
+
+bool tdeio_digikamalbums::createUDSEntry(const TQString& path, TDEIO::UDSEntry& entry)
+{
+ entry.clear();
+
+ KDE_struct_stat stbuf;
+ if (KDE_stat(TQFile::encodeName(path), &stbuf) != 0)
+ return false;
+
+ TDEIO::UDSAtom atom;
+
+ atom.m_uds = TDEIO::UDS_FILE_TYPE;
+ atom.m_long = stbuf.st_mode & S_IFMT;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_ACCESS;
+ atom.m_long = stbuf.st_mode & 07777;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_SIZE;
+ atom.m_long = stbuf.st_size;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_MODIFICATION_TIME;
+ atom.m_long = stbuf.st_mtime;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_ACCESS_TIME;
+ atom.m_long = stbuf.st_atime;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_NAME;
+ atom.m_str = TQFileInfo(path).fileName();
+ entry.append(atom);
+
+ /*
+ // If we provide the local path, a TDEIO::CopyJob will optimize away
+ // the use of our custom digikamalbums:/ ioslave, which breaks
+ // copying the database entry:
+ // Disabling this as a temporary solution for bug #137282
+ // This code is intended as a fix for bug #122653.
+#if KDE_IS_VERSION(3,4,0)
+ atom.m_uds = TDEIO::UDS_LOCAL_PATH;
+ atom.m_str = path;
+ entry.append(atom);
+#endif
+ */
+
+ return true;
+}
+
+void tdeio_digikamalbums::createDigikamPropsUDSEntry(TDEIO::UDSEntry& entry)
+{
+ entry.clear();
+
+ TDEIO::UDSAtom atom;
+
+ atom.m_uds = TDEIO::UDS_FILE_TYPE;
+ atom.m_long = S_IFREG;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_ACCESS;
+ atom.m_long = 00666;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_SIZE;
+ atom.m_long = 0;
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_MODIFICATION_TIME;
+ atom.m_long = TQDateTime::currentDateTime().toTime_t();
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_ACCESS_TIME;
+ atom.m_long = TQDateTime::currentDateTime().toTime_t();
+ entry.append( atom );
+
+ atom.m_uds = TDEIO::UDS_NAME;
+ atom.m_str = ".digikam_properties";
+ entry.append(atom);
+}
+
+void tdeio_digikamalbums::buildAlbumList()
+{
+// simplified from AlbumDB::scanAlbums()
+ m_albumList.clear();
+
+ TQStringList values;
+ m_sqlDB.execSql( TQString("SELECT id, url, date, caption, collection, icon "
+ "FROM Albums;"), &values );
+
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ AlbumInfo info;
+
+ info.id = (*it).toInt();
+ ++it;
+ info.url = *it;
+ ++it;
+ info.date = TQDate::fromString(*it, TQt::ISODate);
+ ++it;
+ info.caption = *it;
+ ++it;
+ info.collection = *it;
+ ++it;
+ info.icon = (*it).toLongLong();
+ ++it;
+
+ m_albumList.append(info);
+ }
+}
+
+AlbumInfo tdeio_digikamalbums::findAlbum(const TQString& url, bool addIfNotExists)
+{
+// similar to AlbumDB::getOrCreateAlbumId
+ AlbumInfo album;
+ for (TQValueList<AlbumInfo>::const_iterator it = m_albumList.begin();
+ it != m_albumList.end(); ++it)
+ {
+ if ((*it).url == url)
+ {
+ album = *it;
+ return album;
+ }
+ }
+
+ album.id = -1;
+
+ if (addIfNotExists)
+ {
+ TQFileInfo fi(m_libraryPath + url);
+ if (!fi.exists() || !fi.isDir())
+ return album;
+
+ m_sqlDB.execSql(TQString("INSERT INTO Albums (url, date) "
+ "VALUES('%1', '%2')")
+ .arg(escapeString(url),
+ fi.lastModified().date().toString(TQt::ISODate)));
+
+ album.id = m_sqlDB.lastInsertedRow();
+ album.url = url;
+ album.date = fi.lastModified().date();
+ album.icon = 0;
+
+ m_albumList.append(album);
+ }
+
+ return album;
+}
+
+void tdeio_digikamalbums::delAlbum(int albumID)
+{
+// code duplication from AlbumDB::deleteAlbum
+ m_sqlDB.execSql(TQString("DELETE FROM Albums WHERE id='%1'")
+ .arg(albumID));
+}
+
+void tdeio_digikamalbums::renameAlbum(const TQString& oldURL, const TQString& newURL)
+{
+// similar to AlbumDB::setAlbumURL, but why more extended?
+ // first update the url of the album which was renamed
+
+ m_sqlDB.execSql( TQString("UPDATE Albums SET url='%1' WHERE url='%2'")
+ .arg(escapeString(newURL),
+ escapeString(oldURL)));
+
+ // now find the list of all subalbums which need to be updated
+ TQStringList values;
+ m_sqlDB.execSql( TQString("SELECT url FROM Albums WHERE url LIKE '%1/%';")
+ .arg(oldURL), &values );
+
+ // and update their url
+ TQString newChildURL;
+ for (TQStringList::iterator it = values.begin(); it != values.end(); ++it)
+ {
+ newChildURL = *it;
+ newChildURL.replace(oldURL, newURL);
+ m_sqlDB.execSql(TQString("UPDATE Albums SET url='%1' WHERE url='%2'")
+ .arg(escapeString(newChildURL),
+ escapeString(*it)));
+ }
+}
+
+bool tdeio_digikamalbums::findImage(int albumID, const TQString& name) const
+{
+// no similar method in AlbumDB?
+ TQStringList values;
+
+ m_sqlDB.execSql( TQString("SELECT name FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(albumID)
+ .arg(escapeString(name)),
+ &values );
+
+ return !(values.isEmpty());
+}
+
+// from albuminfo.h
+class TagInfo
+{
+public:
+
+ typedef TQValueList<TagInfo> List;
+
+ int id;
+ int pid;
+ TQString name;
+ TQString icon;
+};
+
+void tdeio_digikamalbums::addImage(int albumID, const TQString& filePath)
+{
+// Code duplication: ScanLib::storeItemInDatabase, AlbumDB::addItem,
+// AlbumDB::setItemRating, AlbumDB::addItemTag, AlbumDB::addTag
+
+ // from ScanLib::storeItemInDatabase
+ TQString comment;
+ TQDateTime datetime;
+ int rating = 0;
+
+ Digikam::DMetadata metadata(filePath);
+
+ // Trying to get comments from image :
+ // In first, from standard JPEG comments, or
+ // In second, from EXIF comments tag, or
+ // In third, from IPTC comments tag.
+
+ comment = metadata.getImageComment();
+
+ // Trying to get date and time from image :
+ // In first, from EXIF date & time tags, or
+ // In second, from IPTC date & time tags.
+
+ datetime = metadata.getImageDateTime();
+
+ // Trying to get image rating from IPTC Urgency tag.
+ rating = metadata.getImageRating();
+
+ if (!datetime.isValid())
+ {
+ TQFileInfo info(filePath);
+ datetime = info.lastModified();
+ }
+
+ // Try to get image tags from IPTC keywords tags.
+ TQStringList keywordsList = metadata.getImageKeywords();
+
+ // from AlbumDB::addItem
+ m_sqlDB.execSql(TQString("REPLACE INTO Images "
+ "(dirid, name, datetime, caption) "
+ "VALUES(%1, '%2', '%3', '%4')")
+ .arg(TQString::number(albumID),
+ escapeString(TQFileInfo(filePath).fileName()),
+ datetime.toString(TQt::ISODate),
+ escapeString(comment)));
+
+ TQ_LLONG imageID = m_sqlDB.lastInsertedRow();
+
+ // from AlbumDB::setItemRating
+ if (imageID != -1 && rating != -1)
+ {
+ m_sqlDB.execSql(TQString("REPLACE INTO ImageProperties "
+ "(imageid, property, value) "
+ "VALUES(%1, '%2', '%3');")
+ .arg(imageID)
+ .arg("Rating")
+ .arg(rating) );
+ }
+
+ // Set existing tags in database or create new tags if not exist.
+
+ if ( imageID != -1 && !keywordsList.isEmpty() )
+ {
+ TQStringList keywordsList2Create;
+
+ // Create a list of the tags currently in database
+
+ TagInfo::List tagsList;
+
+ TQStringList values;
+ m_sqlDB.execSql( "SELECT id, pid, name FROM Tags;", &values );
+
+ for (TQStringList::iterator it = values.begin(); it != values.end();)
+ {
+ TagInfo info;
+
+ info.id = (*it).toInt();
+ ++it;
+ info.pid = (*it).toInt();
+ ++it;
+ info.name = *it;
+ ++it;
+ tagsList.append(info);
+ }
+
+ // For every tag in keywordsList, scan taglist to check if tag already exists.
+
+ for (TQStringList::iterator kwd = keywordsList.begin();
+ kwd != keywordsList.end(); ++kwd )
+ {
+ // split full tag "url" into list of single tag names
+ TQStringList tagHierarchy = TQStringList::split('/', *kwd);
+ if (tagHierarchy.isEmpty())
+ continue;
+
+ // last entry in list is the actual tag name
+ bool foundTag = false;
+ TQString tagName = tagHierarchy.back();
+ tagHierarchy.pop_back();
+
+ for (TagInfo::List::iterator tag = tagsList.begin();
+ tag != tagsList.end(); ++tag )
+ {
+ // There might be multiple tags with the same name, but in different
+ // hierarchies. We must check them all until we find the correct hierarchy
+ if ((*tag).name == tagName)
+ {
+ int parentID = (*tag).pid;
+
+ // Check hierarchy, from bottom to top
+ bool foundParentTag = true;
+ TQStringList::iterator parentTagName = tagHierarchy.end();
+
+ while (foundParentTag && parentTagName != tagHierarchy.begin())
+ {
+ --parentTagName;
+
+ foundParentTag = false;
+
+ for (TagInfo::List::iterator parentTag = tagsList.begin();
+ parentTag != tagsList.end(); ++parentTag )
+ {
+ // check if name is the same, and if ID is identical
+ // to the parent ID we got from the child tag
+ if ( (*parentTag).id == parentID &&
+ (*parentTag).name == (*parentTagName) )
+ {
+ parentID = (*parentTag).pid;
+ foundParentTag = true;
+ break;
+ }
+ }
+
+ // If we traversed the list without a match,
+ // foundParentTag will be false, the while loop breaks.
+ }
+
+ // If we managed to traverse the full hierarchy,
+ // we have our tag.
+ if (foundParentTag)
+ {
+ // from AlbumDB::addItemTag
+ m_sqlDB.execSql( TQString("REPLACE INTO ImageTags (imageid, tagid) "
+ "VALUES(%1, %2);")
+ .arg(imageID)
+ .arg((*tag).id) );
+ foundTag = true;
+ break;
+ }
+ }
+ }
+
+ if (!foundTag)
+ keywordsList2Create.append(*kwd);
+ }
+
+ // If tags do not exist in database, create them.
+
+ if (!keywordsList2Create.isEmpty())
+ {
+ for (TQStringList::iterator kwd = keywordsList2Create.begin();
+ kwd != keywordsList2Create.end(); ++kwd )
+ {
+ // split full tag "url" into list of single tag names
+ TQStringList tagHierarchy = TQStringList::split('/', *kwd);
+
+ if (tagHierarchy.isEmpty())
+ continue;
+
+ int parentTagID = 0;
+ int tagID = 0;
+ bool parentTagExisted = true;
+
+ // Traverse hierarchy from top to bottom
+ for (TQStringList::iterator tagName = tagHierarchy.begin();
+ tagName != tagHierarchy.end(); ++tagName)
+ {
+ tagID = 0;
+
+ // if the parent tag did not exist, we need not check if the child exists
+ if (parentTagExisted)
+ {
+ for (TagInfo::List::iterator tag = tagsList.begin();
+ tag != tagsList.end(); ++tag )
+ {
+ // find the tag with tag name according to tagHierarchy,
+ // and parent ID identical to the ID of the tag we found in
+ // the previous run.
+ if ((*tag).name == (*tagName) && (*tag).pid == parentTagID)
+ {
+ tagID = (*tag).id;
+ break;
+ }
+ }
+ }
+
+ if (tagID != 0)
+ {
+ // tag already found in DB
+ parentTagID = tagID;
+ continue;
+ }
+
+ // Tag does not yet exist in DB, add it
+ // from AlbumDB::addTag
+ m_sqlDB.execSql( TQString("INSERT INTO Tags (pid, name, icon) "
+ "VALUES( %1, '%2', 0)")
+ .arg(parentTagID)
+ .arg(escapeString(*tagName)));
+ tagID = m_sqlDB.lastInsertedRow();
+
+ if (tagID == -1)
+ {
+ // Something is wrong in database. Abort.
+ break;
+ }
+
+ // append to our list of existing tags (for following keywords)
+ TagInfo info;
+ info.id = tagID;
+ info.pid = parentTagID;
+ info.name = (*tagName);
+ tagsList.append(info);
+
+ parentTagID = tagID;
+ parentTagExisted = false;
+ }
+
+ // from AlbumDB::addItemTag
+ m_sqlDB.execSql( TQString("REPLACE INTO ImageTags (imageid, tagid) "
+ "VALUES(%1, %2);")
+ .arg(imageID)
+ .arg(tagID) );
+ }
+ }
+ }
+}
+
+void tdeio_digikamalbums::delImage(int albumID, const TQString& name)
+{
+// code duplication from AlbumDB::deleteItem
+ m_sqlDB.execSql( TQString("DELETE FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(albumID)
+ .arg(escapeString(name)) );
+}
+
+void tdeio_digikamalbums::renameImage(int oldAlbumID, const TQString& oldName,
+ int newAlbumID, const TQString& newName)
+{
+// code duplication from AlbumDB::deleteItem, AlbumDB::moveItem
+ // first delete any stale entries for the destination file
+ m_sqlDB.execSql( TQString("DELETE FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(newAlbumID)
+ .arg(escapeString(newName)) );
+
+ // now update the dirid and/or name of the file
+ m_sqlDB.execSql( TQString("UPDATE Images SET dirid=%1, name='%2' "
+ "WHERE dirid=%3 AND name='%4';")
+ .arg(TQString::number(newAlbumID),
+ escapeString(newName),
+ TQString::number(oldAlbumID),
+ escapeString(oldName)) );
+}
+
+void tdeio_digikamalbums::copyImage(int srcAlbumID, const TQString& srcName,
+ int dstAlbumID, const TQString& dstName)
+{
+// code duplication from AlbumDB::copyItem
+ // check for src == dest
+ if (srcAlbumID == dstAlbumID && srcName == dstName)
+ {
+ error( TDEIO::ERR_FILE_ALREADY_EXIST, dstName );
+ return;
+ }
+
+ // find id of src image
+ TQStringList values;
+ m_sqlDB.execSql( TQString("SELECT id FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(TQString::number(srcAlbumID), escapeString(srcName)),
+ &values);
+
+ if (values.isEmpty())
+ {
+ error(TDEIO::ERR_UNKNOWN, i18n("Source image %1 not found in database")
+ .arg(srcName));
+ return;
+ }
+
+ int srcId = values[0].toInt();
+
+ // first delete any stale entries for the destination file
+ m_sqlDB.execSql( TQString("DELETE FROM Images "
+ "WHERE dirid=%1 AND name='%2';")
+ .arg(TQString::number(dstAlbumID), escapeString(dstName)) );
+
+ // copy entry in Images table
+ m_sqlDB.execSql( TQString("INSERT INTO Images (dirid, name, caption, datetime) "
+ "SELECT %1, '%2', caption, datetime FROM Images "
+ "WHERE id=%3;")
+ .arg(TQString::number(dstAlbumID), escapeString(dstName),
+ TQString::number(srcId)) );
+
+ int dstId = m_sqlDB.lastInsertedRow();
+
+ // copy tags
+ m_sqlDB.execSql( TQString("INSERT INTO ImageTags (imageid, tagid) "
+ "SELECT %1, tagid FROM ImageTags "
+ "WHERE imageid=%2;")
+ .arg(TQString::number(dstId), TQString::number(srcId)) );
+
+ // copy properties (rating)
+ m_sqlDB.execSql( TQString("INSERT INTO ImageProperties (imageid, property, value) "
+ "SELECT %1, property, value FROM ImageProperties "
+ "WHERE imageid=%2;")
+ .arg(TQString::number(dstId), TQString::number(srcId)) );
+}
+
+void tdeio_digikamalbums::scanAlbum(const TQString& url)
+{
+ scanOneAlbum(url);
+ removeInvalidAlbums();
+}
+
+void tdeio_digikamalbums::scanOneAlbum(const TQString& url)
+{
+ TQDir dir(m_libraryPath + url);
+ if (!dir.exists() || !dir.isReadable())
+ {
+ return;
+ }
+
+ TQString subURL = url;
+ if (!url.endsWith("/"))
+ subURL += '/';
+ subURL = escapeString( subURL);
+
+ {
+ // scan albums
+
+ TQStringList currAlbumList;
+ m_sqlDB.execSql( TQString("SELECT url FROM Albums WHERE ") +
+ TQString("url LIKE '") + subURL + TQString("%' ") +
+ TQString("AND url NOT LIKE '") + subURL + TQString("%/%' "),
+ &currAlbumList );
+
+
+ const TQFileInfoList* infoList = dir.entryInfoList(TQDir::Dirs);
+ if (!infoList)
+ return;
+
+ TQFileInfoListIterator it(*infoList);
+ TQFileInfo* fi;
+
+ TQStringList newAlbumList;
+ while ((fi = it.current()) != 0)
+ {
+ ++it;
+
+ if (fi->fileName() == "." || fi->fileName() == "..")
+ {
+ continue;
+ }
+
+ TQString u = TQDir::cleanDirPath(url + '/' + fi->fileName());
+
+ if (currAlbumList.contains(u))
+ {
+ continue;
+ }
+
+ newAlbumList.append(u);
+ }
+
+ for (TQStringList::iterator it = newAlbumList.begin();
+ it != newAlbumList.end(); ++it)
+ {
+ kdDebug() << "New Album: " << *it << endl;
+
+ TQFileInfo fi(m_libraryPath + *it);
+ m_sqlDB.execSql(TQString("INSERT INTO Albums (url, date) "
+ "VALUES('%1', '%2')")
+ .arg(escapeString(*it),
+ fi.lastModified().date().toString(TQt::ISODate)));
+
+ scanAlbum(*it);
+ }
+ }
+
+ if (url != "/")
+ {
+ // scan files
+
+ TQStringList values;
+
+ m_sqlDB.execSql( TQString("SELECT id FROM Albums WHERE url='%1'")
+ .arg(escapeString(url)), &values );
+ if (values.isEmpty())
+ return;
+
+ int albumID = values.first().toInt();
+
+ TQStringList currItemList;
+ m_sqlDB.execSql( TQString("SELECT name FROM Images WHERE dirid=%1")
+ .arg(albumID), &currItemList );
+
+ const TQFileInfoList* infoList = dir.entryInfoList(TQDir::Files);
+ if (!infoList)
+ return;
+
+ TQFileInfoListIterator it(*infoList);
+ TQFileInfo* fi;
+
+ // add any new files we find to the db
+ while ((fi = it.current()) != 0)
+ {
+ ++it;
+
+ // ignore temp files we created ourselves
+ if (fi->extension(true) == "digikamtempfile.tmp")
+ {
+ continue;
+ }
+
+ if (currItemList.contains(fi->fileName()))
+ {
+ currItemList.remove(fi->fileName());
+ continue;
+ }
+
+ addImage(albumID, m_libraryPath + url + '/' + fi->fileName());
+ }
+
+ // currItemList now contains deleted file list. remove them from db
+ for (TQStringList::iterator it = currItemList.begin();
+ it != currItemList.end(); ++it)
+ {
+ delImage(albumID, *it);
+ }
+ }
+}
+
+void tdeio_digikamalbums::removeInvalidAlbums()
+{
+ TQStringList urlList;
+
+ m_sqlDB.execSql(TQString("SELECT url FROM Albums;"),
+ &urlList);
+
+ m_sqlDB.execSql("BEGIN TRANSACTION");
+
+ struct stat stbuf;
+
+ for (TQStringList::iterator it = urlList.begin();
+ it != urlList.end(); ++it)
+ {
+ if (::stat(TQFile::encodeName(m_libraryPath + *it), &stbuf) == 0)
+ continue;
+
+ kdDebug() << "Deleted Album: " << *it << endl;
+ m_sqlDB.execSql(TQString("DELETE FROM Albums WHERE url='%1'")
+ .arg(escapeString(*it)));
+ }
+
+ m_sqlDB.execSql("COMMIT TRANSACTION");
+}
+
+/* TDEIO slave registration */
+
+extern "C"
+{
+ DIGIKAM_EXPORT int kdemain(int argc, char **argv)
+ {
+ TDELocale::setMainCatalogue("digikam");
+ TDEInstance instance( "tdeio_digikamalbums" );
+ TDEGlobal::locale();
+
+ if (argc != 4) {
+ kdDebug() << "Usage: tdeio_digikamalbums protocol domain-socket1 domain-socket2"
+ << endl;
+ exit(-1);
+ }
+
+ tdeio_digikamalbums slave(argv[2], argv[3]);
+ slave.dispatchLoop();
+
+ return 0;
+ }
+}