/* ============================================================
*
* This file is a part of digiKam project
* http : //www.digikam.org
*
* Date : 2005 - 04 - 21
* Description : a kio - slave to process search on digiKam albums
*
* Copyright ( C ) 2005 by Renchi Raju < renchi @ pooh . tam . uiuc . edu >
*
* 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 .
*
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
// 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 <cerrno>
# include <cstdlib>
# include <cstdio>
# include <ctime>
// TQt includes.
# include <tqfile.h>
# include <tqdatastream.h>
# include <tqtextstream.h>
# include <tqregexp.h>
# include <tqdir.h>
# include <tqvariant.h>
# include <tqmap.h>
// KDE includes.
# include <kglobal.h>
# include <klocale.h>
# include <kcalendarsystem.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>
// Local includes.
# include "digikam_export.h"
# include "digikamsearch.h"
tdeio_digikamsearch : : tdeio_digikamsearch ( const TQCString & pool_socket ,
const TQCString & app_socket )
: SlaveBase ( " tdeio_digikamsearch " , pool_socket , app_socket )
{
// build a lookup table for month names
const KCalendarSystem * cal = TDEGlobal : : locale ( ) - > calendar ( ) ;
for ( int i = 1 ; i < = 12 ; + + i )
{
m_shortMonths [ i - 1 ] = cal - > monthName ( i , 2000 , true ) . lower ( ) ;
m_longMonths [ i - 1 ] = cal - > monthName ( i , 2000 , false ) . lower ( ) ;
}
}
tdeio_digikamsearch : : ~ tdeio_digikamsearch ( )
{
}
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_digikamsearch : : special ( const TQByteArray & data )
{
TQString libraryPath ;
KURL url ;
TQString filter ;
int getDimensions ;
int listingType = 0 ;
int recurseAlbums ;
int recurseTags ;
TQDataStream ds ( data , IO_ReadOnly ) ;
ds > > libraryPath ;
ds > > url ;
ds > > filter ;
ds > > getDimensions ;
ds > > recurseAlbums ;
ds > > recurseTags ;
if ( ! ds . atEnd ( ) )
ds > > listingType ;
if ( m_libraryPath ! = libraryPath )
{
m_libraryPath = libraryPath ;
m_db . closeDB ( ) ;
m_db . openDB ( libraryPath ) ;
}
TQValueList < TQRegExp > regex = makeFilterList ( filter ) ;
TQByteArray ba ;
if ( listingType = = 0 )
{
TQString sqlQuery ;
// query head
sqlQuery = " SELECT Images.id, Images.name, Images.dirid, Images.datetime, Albums.url "
" FROM Images, Albums LEFT JOIN ImageProperties ON Images.id = Imageproperties.imageid "
" WHERE ( " ;
// query body
sqlQuery + = buildQuery ( url ) ;
// query tail
sqlQuery + = " ) " ;
sqlQuery + = " AND (Albums.id=Images.dirid); " ;
TQStringList values ;
TQString errMsg ;
if ( ! m_db . execSql ( sqlQuery , & values ) )
{
error ( TDEIO : : ERR_INTERNAL , errMsg ) ;
return ;
}
TQ_LLONG imageid ;
TQString name ;
TQString path ;
int dirid ;
TQString date ;
TQString purl ;
TQSize dims ;
struct stat stbuf ;
int count = 0 ;
TQDataStream * os = new TQDataStream ( ba , IO_WriteOnly ) ;
for ( TQStringList : : iterator it = values . begin ( ) ; it ! = values . end ( ) ; )
{
imageid = ( * it ) . toLongLong ( ) ;
+ + it ;
name = * it ;
+ + it ;
dirid = ( * it ) . toInt ( ) ;
+ + it ;
date = * it ;
+ + it ;
purl = * it ;
+ + it ;
if ( ! matchFilterList ( regex , name ) )
continue ;
path = m_libraryPath + purl + ' / ' + name ;
if ( : : stat ( TQFile : : encodeName ( path ) , & stbuf ) ! = 0 )
continue ;
dims = TQSize ( ) ;
if ( getDimensions )
{
KFileMetaInfo metaInfo ( path ) ;
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 < < imageid ;
* os < < dirid ;
* os < < name ;
* os < < date ;
* os < < static_cast < size_t > ( stbuf . st_size ) ;
* os < < dims ;
count + + ;
if ( count > 200 )
{
delete os ;
os = 0 ;
SlaveBase : : data ( ba ) ;
ba . resize ( 0 ) ;
count = 0 ;
os = new TQDataStream ( ba , IO_WriteOnly ) ;
}
}
delete os ;
}
else
{
TQString sqlQuery ;
// query head
sqlQuery = " SELECT Albums.url||'/'||Images.name "
" FROM Images, Albums LEFT JOIN ImageProperties on Images.id = ImageProperties.imageid "
" WHERE ( " ;
// query body
sqlQuery + = buildQuery ( url ) ;
// query tail
sqlQuery + = " ) " ;
sqlQuery + = " AND (Albums.id=Images.dirid) " ;
sqlQuery + = " LIMIT 500; " ;
TQStringList values ;
TQString errMsg ;
if ( ! m_db . execSql ( sqlQuery , & values , & errMsg ) )
{
error ( TDEIO : : ERR_INTERNAL , errMsg ) ;
return ;
}
TQDataStream ds ( ba , IO_WriteOnly ) ;
for ( TQStringList : : iterator it = values . begin ( ) ; it ! = values . end ( ) ; + + it )
{
if ( matchFilterList ( regex , * it ) )
{
ds < < m_libraryPath + * it ;
}
}
}
SlaveBase : : data ( ba ) ;
finished ( ) ;
}
TQString tdeio_digikamsearch : : buildQuery ( const KURL & url ) const
{
int count = url . queryItem ( " count " ) . toInt ( ) ;
if ( count < = 0 )
return TQString ( ) ;
TQMap < int , RuleType > rulesMap ;
for ( int i = 1 ; i < = count ; i + + )
{
RuleType rule ;
TQString key = url . queryItem ( TQString : : number ( i ) + " .key " ) . lower ( ) ;
TQString op = url . queryItem ( TQString : : number ( i ) + " .op " ) . lower ( ) ;
if ( key = = " album " )
{
rule . key = ALBUM ;
}
else if ( key = = " albumname " )
{
rule . key = ALBUMNAME ;
}
else if ( key = = " albumcaption " )
{
rule . key = ALBUMCAPTION ;
}
else if ( key = = " albumcollection " )
{
rule . key = ALBUMCOLLECTION ;
}
else if ( key = = " imagename " )
{
rule . key = IMAGENAME ;
}
else if ( key = = " imagecaption " )
{
rule . key = IMAGECAPTION ;
}
else if ( key = = " imagedate " )
{
rule . key = IMAGEDATE ;
}
else if ( key = = " tag " )
{
rule . key = TAG ;
}
else if ( key = = " tagname " )
{
rule . key = TAGNAME ;
}
else if ( key = = " keyword " )
{
rule . key = KEYWORD ;
}
else if ( key = = " rating " )
{
rule . key = RATING ;
}
else
{
kdWarning ( ) < < " Unknown rule type: " < < key < < " passed to tdeioslave "
< < endl ;
continue ;
}
if ( op = = " eq " )
rule . op = EQ ;
else if ( op = = " ne " )
rule . op = NE ;
else if ( op = = " lt " )
rule . op = LT ;
else if ( op = = " lte " )
rule . op = LTE ;
else if ( op = = " gt " )
rule . op = GT ;
else if ( op = = " gte " )
rule . op = GTE ;
else if ( op = = " like " )
rule . op = LIKE ;
else if ( op = = " nlike " )
rule . op = NLIKE ;
else
{
kdWarning ( ) < < " Unknown op type: " < < op < < " passed to tdeioslave "
< < endl ;
continue ;
}
rule . val = url . queryItem ( TQString : : number ( i ) + " .val " ) ;
rulesMap . insert ( i , rule ) ;
}
TQString sqlQuery ;
TQStringList strList = TQStringList : : split ( " " , url . path ( ) ) ;
for ( TQStringList : : Iterator it = strList . begin ( ) ; it ! = strList . end ( ) ; + + it )
{
bool ok ;
int num = ( * it ) . toInt ( & ok ) ;
if ( ok )
{
RuleType rule = rulesMap [ num ] ;
if ( rule . key = = KEYWORD )
{
bool exact ;
TQString possDate = possibleDate ( rule . val , exact ) ;
if ( ! possDate . isEmpty ( ) )
{
rule . key = IMAGEDATE ;
rule . val = possDate ;
if ( exact )
{
rule . op = EQ ;
}
else
{
rule . op = LIKE ;
}
sqlQuery + = subQuery ( rule . key , rule . op , rule . val ) ;
}
else
{
TQValueList < SKey > todo ;
todo . append ( ALBUMNAME ) ;
todo . append ( IMAGENAME ) ;
todo . append ( TAGNAME ) ;
todo . append ( ALBUMCAPTION ) ;
todo . append ( ALBUMCOLLECTION ) ;
todo . append ( IMAGECAPTION ) ;
todo . append ( RATING ) ;
sqlQuery + = ' ( ' ;
TQValueListIterator < SKey > it ;
it = todo . begin ( ) ;
while ( it ! = todo . end ( ) )
{
sqlQuery + = subQuery ( * it , rule . op , rule . val ) ;
+ + it ;
if ( it ! = todo . end ( ) )
sqlQuery + = " OR " ;
}
sqlQuery + = ' ) ' ;
}
}
else
{
sqlQuery + = subQuery ( rule . key , rule . op , rule . val ) ;
}
}
else
{
sqlQuery + = ' ' + * it + ' ' ;
}
}
return sqlQuery ;
}
TQString tdeio_digikamsearch : : subQuery ( enum tdeio_digikamsearch : : SKey key ,
enum tdeio_digikamsearch : : SOperator op ,
const TQString & val ) const
{
TQString query ;
switch ( key )
{
case ( ALBUM ) :
{
if ( op = = EQ | | op = = NE )
query = " (Images.dirid $$##$$ $$@@$$) " ;
else // LIKE AND NLIKE
query = " (Images.dirid IN "
" (SELECT a.id FROM Albums a, Albums b "
" WHERE a.url $$##$$ '%' || b.url || '%' AND b.id = $$@@$$)) " ;
query . replace ( " $$@@$$ " , TQString : : fromLatin1 ( " ' " ) + escapeString ( val )
+ TQString : : fromLatin1 ( " ' " ) ) ;
break ;
}
case ( ALBUMNAME ) :
{
query = " (Images.dirid IN "
" (SELECT id FROM Albums WHERE url $$##$$ $$@@$$)) " ;
break ;
}
case ( ALBUMCAPTION ) :
{
query = " (Images.dirid IN "
" (SELECT id FROM Albums WHERE caption $$##$$ $$@@$$)) " ;
break ;
}
case ( ALBUMCOLLECTION ) :
{
query = " (Images.dirid IN "
" (SELECT id FROM Albums WHERE collection $$##$$ $$@@$$)) " ;
break ;
}
case ( TAG ) :
{
if ( op = = EQ )
query = " (Images.id IN "
" (SELECT imageid FROM ImageTags "
" WHERE tagid = $$@@$$)) " ;
else if ( op = = NE )
query = " (Images.id NOT IN "
" (SELECT imageid FROM ImageTags "
" WHERE tagid = $$@@$$)) " ;
else if ( op = = LIKE )
query = " (Images.id IN "
" (SELECT ImageTags.imageid FROM ImageTags JOIN TagsTree on ImageTags.tagid = TagsTree.id "
" WHERE TagsTree.pid = $$@@$$ or ImageTags.tagid = $$@@$$ )) " ;
else // op == NLIKE
query = " (Images.id NOT IN "
" (SELECT ImageTags.imageid FROM ImageTags JOIN TagsTree on ImageTags.tagid = TagsTree.id "
" WHERE TagsTree.pid = $$@@$$ or ImageTags.tagid = $$@@$$ )) " ;
// query = " (Images.id IN "
// " (SELECT imageid FROM ImageTags "
// " WHERE tagid $$##$$ $$@@$$)) ";
query . replace ( " $$@@$$ " , TQString : : fromLatin1 ( " ' " ) + escapeString ( val )
+ TQString : : fromLatin1 ( " ' " ) ) ;
break ;
}
case ( TAGNAME ) :
{
if ( op = = EQ )
query = " (Images.id IN "
" (SELECT imageid FROM ImageTags "
" WHERE tagid IN "
" (SELECT id FROM Tags WHERE name = $$@@$$))) " ;
else if ( op = = NE )
query = " (Images.id NOT IN "
" (SELECT imageid FROM ImageTags "
" WHERE tagid IN "
" (SELECT id FROM Tags WHERE name = $$@@$$))) " ;
else if ( op = = LIKE )
query = " (Images.id IN "
" (SELECT ImageTags.imageid FROM ImageTags JOIN TagsTree on ImageTags.tagid = TagsTree.id "
" WHERE TagsTree.pid = (SELECT id FROM Tags WHERE name LIKE $$@@$$) "
" OR ImageTags.tagid = (SELECT id FROM Tags WHERE name LIKE $$@@$$) )) " ;
else // op == NLIKE
query = " (Images.id NOT IN "
" (SELECT ImageTags.imageid FROM ImageTags JOIN TagsTree on ImageTags.tagid = TagsTree.id "
" WHERE TagsTree.pid = (SELECT id FROM Tags WHERE name LIKE $$@@$$) "
" OR ImageTags.tagid = (SELECT id FROM Tags WHERE name LIKE $$@@$$) )) " ;
// query.replace("$$@@$$", TQString::fromLatin1("'") + escapeString(val)
// + TQString::fromLatin1("'"));
break ;
}
case ( IMAGENAME ) :
{
query = " (Images.name $$##$$ $$@@$$) " ;
break ;
}
case ( IMAGECAPTION ) :
{
query = " (Images.caption $$##$$ $$@@$$) " ;
break ;
}
case ( IMAGEDATE ) :
{
query = " (Images.datetime $$##$$ $$@@$$) " ;
break ;
}
case ( KEYWORD ) :
{
kdWarning ( ) < < " KEYWORD Detected which is not possible " < < endl ;
break ;
}
case ( RATING ) :
{
// For searches for `rating=0`, `rating>=0`, `rating<=0`,
// `rating <c`, `rating<=c`, `rating<>c` with c=1,2,3,4,5,
// special care has to be taken: Images which were never rated
// have no ImageProperties.property='Rating', but
// need to be treated like having a rating of 0.
// This is achieved by including all images which do
// not have property='Rating'.
if ( ( val = = " 0 " and ( op = = EQ or op = = GTE or op = = LTE ) )
or ( val ! = " 0 " and ( op = = LT or op = = LTE or op = = NE ) ) ) {
query = " ( (ImageProperties.value $$##$$ $$@@$$ and ImageProperties.property='Rating') or (Images.id NOT IN (SELECT imageid FROM ImageProperties WHERE property='Rating') ) ) " ;
} else {
query = " (ImageProperties.value $$##$$ $$@@$$ and ImageProperties.property='Rating') " ;
}
break ;
}
}
if ( key ! = TAG )
{
switch ( op )
{
case ( EQ ) :
{
query . replace ( " $$##$$ " , " = " ) ;
query . replace ( " $$@@$$ " , TQString : : fromLatin1 ( " ' " ) + escapeString ( val )
+ TQString : : fromLatin1 ( " ' " ) ) ;
break ;
}
case ( NE ) :
{
query . replace ( " $$##$$ " , " <> " ) ;
query . replace ( " $$@@$$ " , TQString : : fromLatin1 ( " ' " ) + escapeString ( val )
+ TQString : : fromLatin1 ( " ' " ) ) ;
break ;
}
case ( LT ) :
{
query . replace ( " $$##$$ " , " < " ) ;
query . replace ( " $$@@$$ " , TQString : : fromLatin1 ( " ' " ) + escapeString ( val )
+ TQString : : fromLatin1 ( " ' " ) ) ;
break ;
}
case ( GT ) :
{
query . replace ( " $$##$$ " , " > " ) ;
query . replace ( " $$@@$$ " , TQString : : fromLatin1 ( " ' " ) + escapeString ( val )
+ TQString : : fromLatin1 ( " ' " ) ) ;
break ;
}
case ( LTE ) :
{
query . replace ( " $$##$$ " , " <= " ) ;
query . replace ( " $$@@$$ " , TQString : : fromLatin1 ( " ' " ) + escapeString ( val )
+ TQString : : fromLatin1 ( " ' " ) ) ;
break ;
}
case ( GTE ) :
{
query . replace ( " $$##$$ " , " >= " ) ;
query . replace ( " $$@@$$ " , TQString : : fromLatin1 ( " ' " ) + escapeString ( val )
+ TQString : : fromLatin1 ( " ' " ) ) ;
break ;
}
case ( LIKE ) :
{
query . replace ( " $$##$$ " , " LIKE " ) ;
query . replace ( " $$@@$$ " , TQString : : fromLatin1 ( " '% " ) + escapeString ( val )
+ TQString : : fromLatin1 ( " %' " ) ) ;
break ;
}
case ( NLIKE ) :
{
query . replace ( " $$##$$ " , " NOT LIKE " ) ;
query . replace ( " $$@@$$ " , TQString : : fromLatin1 ( " '% " ) + escapeString ( val )
+ TQString : : fromLatin1 ( " %' " ) ) ;
break ;
}
}
}
// special case for imagedate. If the key is imagedate and the operator is EQ,
// we need to split it into two rules
if ( key = = IMAGEDATE & & op = = EQ )
{
TQDate date = TQDate : : fromString ( val , Qt : : ISODate ) ;
if ( ! date . isValid ( ) )
return query ;
query = TQString ( " (Images.datetime > '%1' AND Images.datetime < '%2') " )
. arg ( date . addDays ( - 1 ) . toString ( Qt : : ISODate ) )
. arg ( date . addDays ( 1 ) . toString ( Qt : : ISODate ) ) ;
}
return query ;
}
/* KIO slave registration */
extern " C "
{
DIGIKAM_EXPORT int kdemain ( int argc , char * * argv )
{
KLocale : : setMainCatalogue ( " digikam " ) ;
TDEInstance instance ( " tdeio_digikamsearch " ) ;
TDEGlobal : : locale ( ) ;
if ( argc ! = 4 )
{
kdDebug ( ) < < " Usage: tdeio_digikamsearch protocol domain-socket1 domain-socket2 "
< < endl ;
exit ( - 1 ) ;
}
tdeio_digikamsearch slave ( argv [ 2 ] , argv [ 3 ] ) ;
slave . dispatchLoop ( ) ;
return 0 ;
}
}
TQString tdeio_digikamsearch : : possibleDate ( const TQString & str , bool & exact ) const
{
TQDate date = TQDate : : fromString ( str , Qt : : ISODate ) ;
if ( date . isValid ( ) )
{
exact = true ;
return date . toString ( Qt : : ISODate ) ;
}
exact = false ;
bool ok ;
int num = str . toInt ( & ok ) ;
if ( ok )
{
// ok. its an int, does it look like a year?
if ( 1970 < = num & & num < = TQDate : : currentDate ( ) . year ( ) )
{
// very sure its a year
return TQString ( " %1-%-% " ) . arg ( num ) ;
}
}
else
{
// hmm... not a year. is it a particular month?
for ( int i = 1 ; i < = 12 ; i + + )
{
if ( str . lower ( ) = = m_shortMonths [ i - 1 ] | |
str . lower ( ) = = m_longMonths [ i - 1 ] )
{
TQString monGlob ;
monGlob . sprintf ( " %.2d " , i ) ;
monGlob = " %- " + monGlob + " -% " ;
return monGlob ;
}
}
}
return TQString ( ) ;
}