1
/* ============================================================
2
* Author: Renchi Raju <renchi@pooh.tam.uiuc.edu>
6
* Copyright 2004 by Renchi Raju
8
* This program is free software; you can redistribute it
9
* and/or modify it under the terms of the GNU General
10
* Public License as published by the Free Software Foundation;
11
* either version 2, or (at your option)
14
* This program is distributed in the hope that it will be useful,
15
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
* GNU General Public License for more details.
19
* ============================================================ */
21
#include <kinstance.h>
26
#include <klargefile.h>
28
#include <kstandarddirs.h>
29
#include <kio/global.h>
32
#include <qfileinfo.h>
44
#include <sys/types.h>
52
#include "digikamio.h"
54
kio_digikamioProtocol::kio_digikamioProtocol(const QCString &pool_socket,
55
const QCString &app_socket)
56
: SlaveBase("kio_digikamio", pool_socket, app_socket)
61
KConfig config("digikamrc");
62
config.setGroup("Album Settings");
63
m_libraryPath = config.readPathEntry("Album Path", QString::null);
64
if (m_libraryPath.isEmpty() || !QFileInfo(m_libraryPath).exists())
66
error(KIO::ERR_UNKNOWN, i18n("Digikam Library path not set correctly"));
70
m_libraryPath = QDir::cleanDirPath(m_libraryPath);
72
QString dbPath = m_libraryPath + "/digikam.db";
75
dbPath = QDir::homeDirPath() + "/.kde/share/apps/digikam/" +
76
KIO::encodeFileName(QDir::cleanDirPath(dbPath));
80
m_db = sqlite_open(QFile::encodeName(dbPath), 0, &errMsg);
83
error(KIO::ERR_UNKNOWN, i18n("Failed to open Digikam Database"));
91
kio_digikamioProtocol::~kio_digikamioProtocol()
99
void kio_digikamioProtocol::copy(const KURL& src, const KURL& dest,
100
int permissions, bool overwrite)
103
copyInternal(src, dest, permissions, overwrite, failed);
108
void kio_digikamioProtocol::copyInternal(const KURL& src, const KURL& dest,
109
int permissions, bool overwrite,
115
QCString _src( QFile::encodeName(src.path()) );
116
QCString _dest( QFile::encodeName(dest.path()) );
118
bool srcIsDir = false;
119
bool srcInLibrary = true;
120
bool destInLibrary = true;
122
KDE_struct_stat buff_src;
123
if ( KDE_stat( _src.data(), &buff_src ) == -1 )
125
if ( errno == EACCES )
126
error( KIO::ERR_ACCESS_DENIED, src.path() );
128
error( KIO::ERR_DOES_NOT_EXIST, src.path() );
133
// Is src a directory
134
srcIsDir = S_ISDIR(buff_src.st_mode);
136
// Is src and dest in library
137
KURL libURL(m_libraryPath);
138
libURL.setProtocol("digikamio");
139
srcInLibrary = libURL.isParentOf(src);
140
destInLibrary = libURL.isParentOf(dest);
144
kdWarning() << "This should not happen. "
145
<< "Destination URL not in album library Path. "
146
<< dest.prettyURL() << endl;
147
error(KIO::ERR_UNKNOWN, i18n("Destination URL not in album library Path."));
152
KDE_struct_stat buff_dest;
153
bool dest_exists = ( KDE_stat( _dest.data(), &buff_dest ) != -1 );
156
if (S_ISDIR(buff_dest.st_mode))
158
error( KIO::ERR_DIR_ALREADY_EXIST, dest.path() );
165
error( KIO::ERR_FILE_ALREADY_EXIST, dest.path() );
171
// Four possible scenarios:
172
// a. dir in library being copied to another location
173
// b. external dir being copied into library
174
// c. file from one album being copied to another album
175
// d. external file being copied into an album
178
if (srcIsDir && srcInLibrary)
180
// a. dir in library being copied to another location
182
infoMessage(i18n("Copying folder\n%1")
186
execSql( "BEGIN TRANSACTION;" );
190
// first make the directory
191
if (::mkdir(_dest.data(), buff_src.st_mode))
193
if (( errno == EACCES ) || (errno == EPERM) || (errno == EROFS))
194
error( KIO::ERR_ACCESS_DENIED, dest.path() );
195
else if (errno == EEXIST)
196
error( KIO::ERR_DIR_ALREADY_EXIST, dest.path() );
197
else if (errno == ENOSPC)
198
error( KIO::ERR_DISK_FULL, dest.path() );
200
error( KIO::ERR_COULD_NOT_MKDIR, dest.path() );
208
QString oldURL = escapeString( albumURLFromKURL(src) );
209
QString newURL = escapeString( albumURLFromKURL(dest) );
211
// delete any stale album
212
removeDirFromDB( newURL );
214
// copy the attributes of the original album to this
215
execSql( QString("INSERT INTO Albums (url, date, caption, collection) "
216
"SELECT '%1',date,caption,collection FROM Albums "
218
.arg(escapeString(newURL))
219
.arg(escapeString(oldURL)) );
223
// unlock the database
224
execSql( "COMMIT TRANSACTION;" );
232
// now read the old directory and start copying each of entries
233
QDir dir( src.path(-1) );
234
dir.setFilter( QDir::Dirs | QDir::Files | QDir::NoSymLinks );
235
dir.setSorting( QDir::DirsFirst | QDir::Name );
237
const QFileInfoList *infoList = dir.entryInfoList();
240
QFileInfoListIterator it( *infoList );
243
while ( (fi = it.current()) != 0 )
246
if (fi->fileName().startsWith("."))
250
surl.addPath( fi->fileName() );
253
durl.addPath( fi->fileName() );
255
copyInternal( surl, durl, permissions, overwrite, failed );
266
// b. external dir being copied into library
268
infoMessage(i18n("Copying folder\n%1")
273
// first make the directory
274
if (::mkdir(_dest.data(), buff_src.st_mode))
276
if (( errno == EACCES ) || (errno == EPERM) || (errno == EROFS))
277
error( KIO::ERR_ACCESS_DENIED, dest.path() );
278
else if (errno == EEXIST)
279
error( KIO::ERR_DIR_ALREADY_EXIST, dest.path() );
280
else if (errno == ENOSPC)
281
error( KIO::ERR_DISK_FULL, dest.path() );
283
error( KIO::ERR_COULD_NOT_MKDIR, dest.path() );
295
// now read the old directory and start copying each of entries
296
QDir dir( src.path(-1) );
297
dir.setFilter( QDir::Dirs | QDir::Files | QDir::NoSymLinks );
298
dir.setSorting( QDir::DirsFirst | QDir::Name );
300
const QFileInfoList *infoList = dir.entryInfoList();
303
QFileInfoListIterator it( *infoList );
305
while ( (fi = it.current()) != 0 )
308
if (fi->fileName().startsWith("."))
312
surl.addPath( fi->fileName() );
315
durl.addPath( fi->fileName() );
317
copyInternal( surl, durl, permissions, overwrite, failed );
324
else if (srcInLibrary)
326
// c. file from one album being copied to another album
328
infoMessage(i18n("Copying file\n%1")
331
// find the parent albums
332
QString oldParentURL(albumURLFromKURL(src.upURL()));
333
QString newParentURL(albumURLFromKURL(dest.upURL()));
335
// find the album ids
341
execSql( QString("SELECT id FROM Albums WHERE url = '%1'")
342
.arg(escapeString(oldParentURL)), &vals );
345
error(KIO::ERR_UNKNOWN,
346
i18n("Could not find source parent album for %1")
351
oldDirID = vals.first().toInt();
354
execSql( QString("SELECT id FROM Albums WHERE url = '%1'")
355
.arg(escapeString(newParentURL)), &vals );
358
error(KIO::ERR_UNKNOWN,
359
i18n("Could not find destination parent album for %1")
360
.arg(dest.prettyURL()));
364
newDirID = vals.first().toInt();
366
// first copy to a tmp file
367
KURL destDirURL(dest.upURL());
368
destDirURL.addPath(QString(".digikamio-%1")
371
QString tmpFile(destDirURL.path());
373
if (!copyFile(src.path(), tmpFile))
375
error(KIO::ERR_COULD_NOT_WRITE, dest.prettyURL());
376
kdWarning() << k_funcinfo << "Failed to copy file to temporary file. "
377
<< "src: " << _src << ", temp: " << tmpFile << endl;
382
// now rename to dest file
383
execSql( "BEGIN TRANSACTION;" );
385
if (::rename( QFile::encodeName(tmpFile), _dest.data()))
387
unlink( QFile::encodeName(tmpFile) );
388
error(KIO::ERR_COULD_NOT_WRITE, dest.prettyURL());
389
kdWarning() << k_funcinfo << "Failed to rename temporary file to destination file: "
395
// successful copy: now copy the file metadata
397
// first delete any stale database entries if any
398
removeFileFromDB(newDirID, dest.fileName());
400
execSql( QString("INSERT INTO Images (dirid, name, caption, datetime) "
401
"SELECT %1, '%2', caption, datetime FROM Images "
402
"WHERE dirid=%3 AND name='%4';")
404
.arg(escapeString(dest.fileName()))
406
.arg(escapeString(src.fileName())) );
408
execSql( QString("INSERT INTO ImageTags (dirid, name, tagid) "
409
"SELECT %1, '%2', tagid FROM ImageTags "
410
"WHERE dirid=%3 AND name='%4';")
412
.arg(escapeString(dest.fileName()))
414
.arg(escapeString(src.fileName())) );
416
// also set the filetime to that of the original file
418
t.actime = buff_src.st_atime;
419
t.modtime = buff_src.st_mtime;
421
if ( ::utime( _dest.data(), &t ) != 0 )
423
kdWarning() << k_funcinfo
424
<< "Failed to set datetime of destination file "
425
<< "to that of of original file" << endl;
429
execSql( "COMMIT TRANSACTION;");
435
// d. external file being copied into an album
437
infoMessage(i18n("Copying file\n%1")
440
// first copy to a tmp file
441
KURL destDirURL(dest.upURL());
442
destDirURL.addPath(QString(".digikamio-%1")
445
QString tmpFile(destDirURL.path());
447
if (!copyFile(src.path(), tmpFile))
449
error(KIO::ERR_COULD_NOT_WRITE, dest.prettyURL());
454
// now rename to the dest file
455
if (::rename( QFile::encodeName(tmpFile), _dest.data()))
457
unlink( QFile::encodeName(tmpFile) );
458
error(KIO::ERR_COULD_NOT_WRITE, dest.prettyURL());
463
// also set the filetime to that of the original file
465
t.actime = buff_src.st_atime;
466
t.modtime = buff_src.st_mtime;
468
if ( ::utime( _dest.data(), &t ) != 0 )
470
kdWarning() << k_funcinfo
471
<< "Failed to set datetime of destination file "
472
<< "to that of of original file" << endl;
477
void kio_digikamioProtocol::rename(const KURL &src, const KURL &dest,
480
QCString _src( QFile::encodeName(src.path()) );
481
QCString _dest( QFile::encodeName(dest.path()) );
483
bool srcIsDir = false;
484
bool srcInLibrary = true;
485
bool destInLibrary = true;
487
KDE_struct_stat buff_src;
488
if ( KDE_stat( _src.data(), &buff_src ) == -1 )
490
if ( errno == EACCES )
491
error( KIO::ERR_ACCESS_DENIED, src.path() );
493
error( KIO::ERR_DOES_NOT_EXIST, src.path() );
498
// Is src a directory
499
srcIsDir = S_ISDIR(buff_src.st_mode);
501
// Is src and dest in library
502
KURL libURL(m_libraryPath);
503
libURL.setProtocol("digikamio");
504
srcInLibrary = libURL.isParentOf(src);
505
destInLibrary = libURL.isParentOf(dest);
509
kdWarning() << "This should not happen. "
510
<< "Destination URL not in album library Path. "
511
<< dest.prettyURL() << endl;
512
error(KIO::ERR_UNKNOWN, i18n("Destination URL not in album library Path."));
517
KDE_struct_stat buff_dest;
518
bool dest_exists = ( KDE_stat( _dest.data(), &buff_dest ) != -1 );
521
if (S_ISDIR(buff_dest.st_mode))
523
error( KIO::ERR_DIR_ALREADY_EXIST, dest.path() );
530
error( KIO::ERR_FILE_ALREADY_EXIST, dest.path() );
536
if (srcIsDir && srcInLibrary)
538
// moving or renaming an album;
540
infoMessage(i18n("Moving folder\n%1")
544
execSql( "BEGIN TRANSACTION;" );
548
if (::rename( _src.data(), _dest.data()))
550
if (( errno == EACCES ) || (errno == EPERM))
551
error( KIO::ERR_ACCESS_DENIED, dest.path() );
552
else if (errno == EXDEV)
553
error( KIO::ERR_UNSUPPORTED_ACTION, QString::fromLatin1("rename"));
554
else if (errno == EROFS) // The file is on a read-only filesystem
555
error( KIO::ERR_CANNOT_DELETE, src.path() );
557
error( KIO::ERR_CANNOT_RENAME, src.path() );
564
// first rename the album in the database
566
QString oldURL = escapeString( albumURLFromKURL(src) );
567
QString newURL = escapeString( albumURLFromKURL(dest) );
569
// delete any stale albums left behind
570
removeDirFromDB( newURL );
573
execSql( QString("UPDATE Albums SET url = '%1' WHERE url = '%2';")
574
.arg(escapeString(newURL))
575
.arg(escapeString(oldURL)) );
577
// Now rename all the subalbums
581
execSql( QString("SELECT url FROM Albums WHERE url LIKE '%1/%'")
582
.arg(escapeString(oldURL)), &suburls );
583
for (QStringList::iterator it = suburls.begin(); it != suburls.end();
587
url.remove(0,oldURL.length());
590
url = escapeString(url);
592
// delete any stale albums left behind
593
execSql( QString("DELETE FROM Albums WHERE url = '%1'")
594
.arg(escapeString(url)) );
597
execSql( QString("UPDATE Albums SET url = '%1' WHERE url = '%2';")
598
.arg(escapeString(url))
599
.arg(escapeString(*it)) );
603
// unlock the database
604
execSql( "COMMIT TRANSACTION;" );
611
// moving an external folder into album library
612
// nothing to do here. just rename the folder
614
infoMessage(i18n("Moving folder\n%1")
617
if (::rename( _src.data(), _dest.data()))
619
if (( errno == EACCES ) || (errno == EPERM))
620
error( KIO::ERR_ACCESS_DENIED, dest.path() );
621
else if (errno == EXDEV)
622
error( KIO::ERR_UNSUPPORTED_ACTION, QString::fromLatin1("rename"));
623
else if (errno == EROFS) // The file is on a read-only filesystem
624
error( KIO::ERR_CANNOT_DELETE, src.path() );
626
error( KIO::ERR_CANNOT_RENAME, src.path() );
633
else if (srcInLibrary)
635
// moving a file within the album library
637
infoMessage(i18n("Moving file\n%1")
640
// find the parent albums
641
QString oldParentURL(albumURLFromKURL(src.upURL()));
642
QString newParentURL(albumURLFromKURL(dest.upURL()));
644
// find the album ids
650
execSql( QString("SELECT id FROM Albums WHERE url = '%1'")
651
.arg(escapeString(oldParentURL)), &vals );
654
error(KIO::ERR_UNKNOWN,
655
i18n("Could not find source parent album for %1")
660
oldDirID = vals.first().toInt();
663
execSql( QString("SELECT id FROM Albums WHERE url = '%1'")
664
.arg(escapeString(newParentURL)), &vals );
667
error(KIO::ERR_UNKNOWN,
668
i18n("Could not find destination parent album for %1")
669
.arg(dest.prettyURL()));
673
newDirID = vals.first().toInt();
676
execSql( "BEGIN TRANSACTION;" );
680
if (::rename( _src.data(), _dest.data()))
682
if (( errno == EACCES ) || (errno == EPERM))
683
error( KIO::ERR_ACCESS_DENIED, dest.path() );
684
else if (errno == EXDEV)
685
error( KIO::ERR_UNSUPPORTED_ACTION, QString::fromLatin1("rename"));
686
else if (errno == EROFS) // The file is on a read-only filesystem
687
error( KIO::ERR_CANNOT_DELETE, src.path() );
689
error( KIO::ERR_CANNOT_RENAME, src.path() );
694
QString oldFileName(src.fileName());
695
QString newFileName(dest.fileName());
699
// delete stale items
700
removeFileFromDB(newDirID, newFileName);
702
execSql( QString("UPDATE Images SET dirid=%1, name='%2' "
703
"WHERE dirid=%3 AND name='%4';")
705
.arg(escapeString(newFileName))
707
.arg(escapeString(oldFileName)) );
709
execSql( QString("UPDATE ImageTags SET dirid=%1, name='%2' "
710
"WHERE dirid=%3 AND name='%4';")
712
.arg(escapeString(newFileName))
714
.arg(escapeString(oldFileName)) );
716
// if the image is used for the tag icon, the tags.icon column
719
execSql( QString("UPDATE Tags SET icon='%1' "
721
.arg(escapeString(QDir::cleanDirPath(dest.path())))
722
.arg(escapeString(QDir::cleanDirPath(src.path()))) );
725
// unlock the database
726
execSql( "COMMIT TRANSACTION;" );
733
// external file being moved into album library
734
// nothing to do here. just rename the file
736
infoMessage(i18n("Moving file\n%1")
739
if ( ::rename( _src.data(), _dest.data()))
741
if (( errno == EACCES ) || (errno == EPERM))
742
error( KIO::ERR_ACCESS_DENIED, dest.path() );
743
else if (errno == EXDEV)
744
error( KIO::ERR_UNSUPPORTED_ACTION, QString::fromLatin1("rename"));
745
else if (errno == EROFS) // The file is on a read-only filesystem
746
error( KIO::ERR_CANNOT_DELETE, src.path() );
748
error( KIO::ERR_CANNOT_RENAME, src.path() );
758
void kio_digikamioProtocol::del(const KURL& url, bool isfile)
760
QCString path( QFile::encodeName(url.path()));
764
kdDebug() << "Deleting file "<< url.url() << endl;
766
// find the parent album
767
QString parentURL(albumURLFromKURL(url.upURL()));
774
execSql( QString("SELECT id FROM Albums WHERE url = '%1'")
775
.arg(escapeString(parentURL)), &vals );
778
error(KIO::ERR_UNKNOWN,
779
i18n("Could not find source parent album for %1")
783
dirID = vals.first().toInt();
785
execSql( "BEGIN TRANSACTION;" );
787
if (::unlink(path.data()) == 0)
789
// delete the item from the database
790
execSql( QString("DELETE FROM Images "
791
"WHERE dirid=%1 AND name='%2';")
793
.arg(escapeString(url.fileName())) );
795
execSql( QString("DELETE FROM ImageTags "
796
"WHERE dirid=%1 AND name='%2';")
798
.arg(escapeString(url.fileName())) );
802
if ((errno == EACCES) || (errno == EPERM))
803
error( KIO::ERR_ACCESS_DENIED, url.path());
804
else if (errno == EISDIR)
805
error( KIO::ERR_IS_DIRECTORY, url.path());
807
error( KIO::ERR_CANNOT_DELETE, url.path() );
810
execSql( "COMMIT TRANSACTION;" );
814
kdDebug() << "Deleting folder not supported yet" << endl;
816
error( KIO::ERR_COULD_NOT_RMDIR, url.path() );
822
void kio_digikamioProtocol::stat(const KURL& url)
824
QCString path( QFile::encodeName(url.path(-1)));
828
atom.m_uds = KIO::UDS_NAME;
829
atom.m_str = url.fileName();
830
entry.append( atom );
834
KDE_struct_stat buff;
836
if (KDE_stat( path.data(), &buff ))
838
error( KIO::ERR_DOES_NOT_EXIST, url.path(-1) );
842
type = buff.st_mode & S_IFMT; // extract file type
843
access = buff.st_mode & 07777; // extract permissions
845
atom.m_uds = KIO::UDS_FILE_TYPE;
847
entry.append( atom );
849
atom.m_uds = KIO::UDS_ACCESS;
850
atom.m_long = access;
851
entry.append( atom );
853
atom.m_uds = KIO::UDS_SIZE;
854
atom.m_long = buff.st_size;
855
entry.append( atom );
857
atom.m_uds = KIO::UDS_MODIFICATION_TIME;
858
atom.m_long = buff.st_mtime;
859
entry.append( atom );
861
atom.m_uds = KIO::UDS_ACCESS_TIME;
862
atom.m_long = buff.st_atime;
863
entry.append( atom );
870
QString kio_digikamioProtocol::albumURLFromKURL(const KURL& kurl)
872
QString url(kurl.path(-1));
873
url = QDir::cleanDirPath(url);
875
url.remove(0,m_libraryPath.length());
877
if (!url.startsWith("/"))
883
void kio_digikamioProtocol::removeDirFromDB(const QString& url)
885
execSql( QString("DELETE FROM Albums WHERE url = '%1'")
886
.arg(escapeString(url)) );
889
void kio_digikamioProtocol::removeFileFromDB(int dirid, const QString& name)
891
execSql( QString("DELETE FROM Images "
892
"WHERE dirid=%1 AND name='%2';")
894
.arg(escapeString(name)) );
896
execSql( QString("DELETE FROM ImageTags "
897
"WHERE dirid=%1 AND name='%2';")
899
.arg(escapeString(name)) );
902
bool kio_digikamioProtocol::copyFile(const QString& src, const QString& dest)
907
if ( !sFile.open(IO_ReadOnly) )
909
kdWarning() << k_funcinfo << "Failed to open source file for reading: "
914
if ( !dFile.open(IO_WriteOnly) )
917
kdWarning() << k_funcinfo << "Failed to open dest file for writing: "
922
const int MAX_IPC_SIZE = (1024*32);
923
char buffer[MAX_IPC_SIZE];
926
while ((len = sFile.readBlock(buffer, MAX_IPC_SIZE)) != 0)
928
if (len == -1 || dFile.writeBlock(buffer, (Q_ULONG)len) == -1)
942
bool kio_digikamioProtocol::execSql(const QString& sql, QStringList* const values,
946
kdDebug() << "SQL-query: " << sql << endl;
949
kdWarning() << k_funcinfo << "SQLite pointer == NULL"
959
//compile SQL program to virtual machine
960
error = sqlite_compile( m_db, sql.local8Bit(), &tail, &vm, &errorStr );
962
if ( error != SQLITE_OK ) {
963
kdWarning() << k_funcinfo << "sqlite_compile error: "
965
<< " on query: " << sql << endl;
966
sqlite_freemem( errorStr );
972
const char** colName;
973
//execute virtual machine by iterating over rows
975
error = sqlite_step( vm, &number, &value, &colName );
976
if ( error == SQLITE_DONE || error == SQLITE_ERROR )
978
//iterate over columns
979
for ( int i = 0; values && i < number; i++ ) {
980
*values << QString::fromLocal8Bit( value [i] );
984
//deallocate vm resources
985
sqlite_finalize( vm, &errorStr );
987
if ( error != SQLITE_DONE ) {
988
kdWarning() << k_funcinfo << "sqlite_step error: "
990
<< " on query: " << sql << endl;
997
QString kio_digikamioProtocol::escapeString(QString str) const
999
str.replace( "'", "''" );
1003
/* KIO slave registration */
1007
int kdemain(int argc, char **argv)
1009
KLocale::setMainCatalogue("digikam");
1010
KInstance instance( "kio_digikamio" );
1011
( void ) KGlobal::locale();
1013
kdDebug() << "*** kio_digikamio started ***" << endl;
1016
kdDebug() << "Usage: kio_digikamio protocol domain-socket1 domain-socket2"
1021
kio_digikamioProtocol slave(argv[2], argv[3]);
1022
slave.dispatchLoop();
1024
kdDebug() << "*** kio_digikamio finished ***" << endl;