/* ============================================================ * * 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 * * 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 * Copyright (C) 2000-2002 David Faure * Copyright (C) 2000-2002 Waldo Bastian * * 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 #include #include #include #include #include } // C++ includes. #include #include #include #include // TQt includes. #include #include #include #include #include // KDE includes. #include #include #include #include #include #include #include #include #include #include // LibKDcraw includes. #include #include #if KDCRAW_VERSION < 0x000106 #include #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 makeFilterList( const TQString &filter ) { TQValueList 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& filters, const TQString &fileName ) { TQValueList::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 regex = makeFilterList(filter); TQByteArray ba; if (folders) // Special mode to stats all album items { TQMap 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::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(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::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 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; } }