You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
digikam/digikam/tdeioslave/digikamsearch.cpp

735 lines
22 KiB

/* ============================================================
*
* 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();
}