/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2005-04-21
 * Description : a kio-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 kioslave.
 * Copyright for the KDE file kioslave 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 <kglobal.h>
#include <klocale.h>
#include <kinstance.h>
#include <kfilemetainfo.h>
#include <kmimetype.h>
#include <kdebug.h>
#include <kio/global.h>
#include <kio/ioslave_defaults.h>
#include <klargefile.h>
#include <kdeversion.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"

kio_digikamalbums::kio_digikamalbums(const TQCString &pool_socket,
                                     const TQCString &app_socket)
    : SlaveBase("kio_digikamalbums", pool_socket, app_socket)
{
}

kio_digikamalbums::~kio_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 kio_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 kio_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(KIO::ERR_UNKNOWN, "Album Library Path not supplied to kioslave");
        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( KIO::ERR_ACCESS_DENIED, url.url() );
        else
            error( KIO::ERR_DOES_NOT_EXIST, url.url() );
        return;
    }

    if ( S_ISDIR( buff.st_mode ) )
    {
        error( KIO::ERR_IS_DIRECTORY, url.url() );
        return;
    }

    if ( !S_ISREG( buff.st_mode ) )
    {
        error( KIO::ERR_CANNOT_OPEN_FOR_READING, url.url() );
        return;
    }

    int fd = KDE_open( path.data(), O_RDONLY);
    if ( fd < 0 )
    {
        error( KIO::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;
    KIO::filesize_t processed_size = 0;

    while (1)
    {
        int n = ::read( fd, buffer, MAX_IPC_SIZE );
        if (n == -1)
        {
            if (errno == EINTR)
                continue;
            error( KIO::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 kio_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(KIO::ERR_UNKNOWN, "Album Library Path not supplied to kioslave");
        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(KIO::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( KIO::ERR_DIR_ALREADY_EXIST, url.url() );
        else
            error( KIO::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( KIO::ERR_WRITE_ACCESS_DENIED, url.url() );
        else
            error( KIO::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( KIO::ERR_DISK_FULL, url.url());
                    result = -1;
                }
                else
                {
                    kdWarning() << "Couldn't write. Error:" << strerror(errno) << endl;
                    error( KIO::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( KIO::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 ( KIO::testFileSystemFlag( _dest, KIO::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 kio_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(KIO::ERR_UNKNOWN, "Album Library Path not supplied to kioslave");
        return;
    }

    // check that the src and dst album library paths match
    TQString dstLibraryPath = dst.user();
    if (libraryPath != dstLibraryPath)
    {
        error(KIO::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(KIO::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(KIO::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( KIO::ERR_ACCESS_DENIED, src.url() );
        else
            error( KIO::ERR_DOES_NOT_EXIST, src.url() );
        return;
    }

    // bail out if its a directory
    if ( S_ISDIR( buff_src.st_mode ) )
    {
        error( KIO::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( KIO::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( KIO::ERR_DIR_ALREADY_EXIST, dst.url() );
            return;
        }

        // if !overwrite bail out
        if (!overwrite)
        {
            error( KIO::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( KIO::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( KIO::ERR_WRITE_ACCESS_DENIED, dst.url() );
        }
        else
        {
            error( KIO::ERR_CANNOT_OPEN_FOR_WRITING, dst.url() );
        }
        close(src_fd);
        return;
    }

    // emit the total size for copying
    totalSize( buff_src.st_size );

    KIO::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( KIO::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( KIO::ERR_DISK_FULL, dst.url());
                remove( _dst.data() );
            }
            else
            {
                kdWarning() << "Couldn't write[2]. Error:" << strerror(errno) << endl;
                error( KIO::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( KIO::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 ( KIO::testFileSystemFlag( _dst, KIO::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 kio_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(KIO::ERR_UNKNOWN, "Album Library Path not supplied to kioslave");
        return;
    }

    TQString dstLibraryPath = dst.user();
    if (libraryPath != dstLibraryPath)
    {
        error(KIO::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( KIO::ERR_ACCESS_DENIED, src.url() );
        else
            error( KIO::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( KIO::ERR_DIR_ALREADY_EXIST, dst.url() );
            return;
        }

        if (!overwrite)
        {
            error( KIO::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(KIO::ERR_UNKNOWN, i18n("Source album %1 not found in database")
                  .arg(src.url()));
            return;
        }
    }
    else
    {
        srcAlbum = findAlbum(src.directory());
        if (srcAlbum.id == -1)
        {
            error(KIO::ERR_UNKNOWN, i18n("Source album %1 not found in database")
                  .arg(src.directory()));
            return;
        }

        dstAlbum = findAlbum(dst.directory());
        if (dstAlbum.id == -1)
        {
            error(KIO::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( KIO::ERR_CANNOT_RENAME_ORIGINAL, src.path() );
            else
                error( KIO::ERR_ACCESS_DENIED, dst.path() );
        }
        else if (errno == EXDEV)
        {
            error( KIO::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( KIO::ERR_CANNOT_DELETE, src.url() );
        }
        else {
            error( KIO::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 kio_digikamalbums::stat( const KURL& url )
{
    TQString libraryPath = url.user();
    if (libraryPath.isEmpty())
    {
        error(KIO::ERR_UNKNOWN, "Album Library Path not supplied to kioslave");
        return;
    }

    KIO::UDSEntry entry;
    if (!createUDSEntry(libraryPath + url.path(), entry))
    {
        error(KIO::ERR_DOES_NOT_EXIST, url.path(-1));
        return;
    }

    statEntry(entry);
    finished();
}

void kio_digikamalbums::listDir( const KURL& url )
{
// Code duplication from file:// ioslave?
    kdDebug() << k_funcinfo << " : " << url.path() << endl;

    TQString libraryPath = url.user();
    if (libraryPath.isEmpty())
    {
        error(KIO::ERR_UNKNOWN, "Album Library Path not supplied to kioslave");
        kdWarning() << "Album Library Path not supplied to kioslave" << endl;
        return;
    }

    KDE_struct_stat stbuf;
    TQString path = libraryPath + url.path();
    if (KDE_stat(TQFile::encodeName(path), &stbuf) != 0)
    {
        error(KIO::ERR_DOES_NOT_EXIST, url.path(-1));
        return;
    }

    TQDir dir(path);
    if (!dir.isReadable())
    {
        error( KIO::ERR_CANNOT_ENTER_DIRECTORY, url.path());
        return;
    }

    const TQFileInfoList *list = dir.entryInfoList(TQDir::All|TQDir::Hidden);
    TQFileInfoListIterator it( *list );
    TQFileInfo *fi;

    KIO::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 kio_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(KIO::ERR_UNKNOWN, "Album Library Path not supplied to kioslave");
        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( KIO::ERR_ACCESS_DENIED, path );
                return;
            }
            else if ( errno == ENOSPC )
            {
                error( KIO::ERR_DISK_FULL, path );
                return;
            }
            else
            {
                error( KIO::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( KIO::ERR_CANNOT_CHMOD, path );
                else
                    finished();
            }
            else
                finished();
            return;
        }
    }

    if ( S_ISDIR( buff.st_mode ) )
    {
        error( KIO::ERR_DIR_ALREADY_EXIST, path );
        return;
    }

    error( KIO::ERR_FILE_ALREADY_EXIST, path );
}

void kio_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(KIO::ERR_UNKNOWN, "Album Library Path not supplied to kioslave");
        return;
    }

    TQCString path( TQFile::encodeName(libraryPath + url.path()));
    if ( ::chmod( path.data(), permissions ) == -1 )
        error( KIO::ERR_CANNOT_CHMOD, url.url() );
    else
        finished();
}

void kio_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(KIO::ERR_UNKNOWN, "Album Library Path not supplied to kioslave");
        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(KIO::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( KIO::ERR_ACCESS_DENIED, url.url());
            else if (errno == EISDIR)
                error( KIO::ERR_IS_DIRECTORY, url.url());
            else
                error( KIO::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(KIO::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( KIO::ERR_ACCESS_DENIED, url.url());
                return;
            }
            else
            {
                kdDebug() << "could not rmdir " << perror << endl;
                error( KIO::ERR_COULD_NOT_RMDIR, url.url() );
                return;
            }
        }

        // successful deletion. now remove album entry from the database
        delAlbum(album.id);
    }

    finished();

}

bool kio_digikamalbums::createUDSEntry(const TQString& path, KIO::UDSEntry& entry)
{
    entry.clear();

    KDE_struct_stat stbuf;
    if (KDE_stat(TQFile::encodeName(path), &stbuf) != 0)
        return false;

    KIO::UDSAtom  atom;

    atom.m_uds = KIO::UDS_FILE_TYPE;
    atom.m_long = stbuf.st_mode & S_IFMT;
    entry.append( atom );

    atom.m_uds = KIO::UDS_ACCESS;
    atom.m_long = stbuf.st_mode & 07777;
    entry.append( atom );

    atom.m_uds = KIO::UDS_SIZE;
    atom.m_long = stbuf.st_size;
    entry.append( atom );

    atom.m_uds = KIO::UDS_MODIFICATION_TIME;
    atom.m_long = stbuf.st_mtime;
    entry.append( atom );

    atom.m_uds = KIO::UDS_ACCESS_TIME;
    atom.m_long = stbuf.st_atime;
    entry.append( atom );

    atom.m_uds = KIO::UDS_NAME;
    atom.m_str = TQFileInfo(path).fileName();
    entry.append(atom);

    /*
    // If we provide the local path, a KIO::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 = KIO::UDS_LOCAL_PATH;
    atom.m_str = path;
    entry.append(atom);
#endif
    */

    return true;
}

void kio_digikamalbums::createDigikamPropsUDSEntry(KIO::UDSEntry& entry)
{
    entry.clear();

    KIO::UDSAtom  atom;

    atom.m_uds = KIO::UDS_FILE_TYPE;
    atom.m_long = S_IFREG;
    entry.append( atom );

    atom.m_uds = KIO::UDS_ACCESS;
    atom.m_long = 00666;
    entry.append( atom );

    atom.m_uds = KIO::UDS_SIZE;
    atom.m_long = 0;
    entry.append( atom );

    atom.m_uds = KIO::UDS_MODIFICATION_TIME;
    atom.m_long = TQDateTime::currentDateTime().toTime_t();
    entry.append( atom );

    atom.m_uds = KIO::UDS_ACCESS_TIME;
    atom.m_long = TQDateTime::currentDateTime().toTime_t();
    entry.append( atom );

    atom.m_uds = KIO::UDS_NAME;
    atom.m_str = ".digikam_properties";
    entry.append(atom);
}

void kio_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 kio_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 kio_digikamalbums::delAlbum(int albumID)
{
// code duplication from AlbumDB::deleteAlbum
    m_sqlDB.execSql(TQString("DELETE FROM Albums WHERE id='%1'")
                    .arg(albumID));
}

void kio_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 kio_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 kio_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 kio_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 kio_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 kio_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( KIO::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(KIO::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 kio_digikamalbums::scanAlbum(const TQString& url)
{
    scanOneAlbum(url);
    removeInvalidAlbums();
}

void kio_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 kio_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");
}

/* KIO slave registration */

extern "C"
{
    DIGIKAM_EXPORT int kdemain(int argc, char **argv)
    {
        KLocale::setMainCatalogue("digikam");
        KInstance instance( "kio_digikamalbums" );
        KGlobal::locale();

        if (argc != 4) {
            kdDebug() << "Usage: kio_digikamalbums  protocol domain-socket1 domain-socket2"
                      << endl;
            exit(-1);
        }

        kio_digikamalbums slave(argv[2], argv[3]);
        slave.dispatchLoop();

        return 0;
    }
}
