You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1970 lines
57 KiB
1970 lines
57 KiB
/* ============================================================
|
|
*
|
|
* 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 <renchi@pooh.tam.uiuc.edu>
|
|
*
|
|
* 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 <coolo@kde.org>
|
|
* Copyright (C) 2000-2002 David Faure <faure@kde.org>
|
|
* Copyright (C) 2000-2002 Waldo Bastian <bastian@kde.org>
|
|
*
|
|
* 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, Qt::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(Qt::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(Qt::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, Qt::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(Qt::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(Qt::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(Qt::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;
|
|
}
|
|
}
|