2018-08-12 16:45:39 +02:00
# include "searchworker.h"
2018-09-02 12:27:23 +02:00
# include <QRegularExpression>
2018-08-12 16:45:39 +02:00
# include <QDebug>
2018-09-02 12:27:23 +02:00
# include <QSqlError>
2018-09-02 13:54:27 +02:00
# include <QStack>
2018-08-12 16:45:39 +02:00
SearchWorker : : SearchWorker ( )
{
}
SearchWorker : : SearchWorker ( const QString & dbpath )
{
2018-09-02 12:27:23 +02:00
db = QSqlDatabase : : addDatabase ( " QSQLITE " ) ;
2018-08-12 16:45:39 +02:00
db . setDatabaseName ( dbpath ) ;
if ( ! db . open ( ) )
{
qDebug ( ) < < " failed to open database " ;
}
}
2018-09-02 12:27:23 +02:00
QVector < SearchWorker : : Command > SearchWorker : : tokenize ( QString expression )
{
2018-09-02 13:54:27 +02:00
if ( ! checkParanthesis ( expression ) )
{
throw std : : invalid_argument ( " Invalid paranthesis " ) ;
}
2018-09-02 12:27:23 +02:00
//TODO: merge lonewords
QVector < Command > result ;
QRegularExpression rx ( " ((?<filtername>( \\ .| \\ w) + ) : ( ? < args > \ \ ( ( ? < innerargs > [ ^ \ \ ) ] + ) \ \ ) | ( \ \ w ) + ) | ( ? < boolean > AND | OR | ! ) | ( ? < bracket > \ \ ( | \ \ ) ) | ( ? < loneword > \ \ w + ) ) " ) ;
QRegularExpressionMatchIterator i = rx . globalMatch ( expression ) ;
bool wasbool = true ;
while ( i . hasNext ( ) )
{
QRegularExpressionMatch m = i . next ( ) ;
QString boolean = m . captured ( " boolean " ) ;
QString filtername = m . captured ( " filtername " ) ;
QString bracket = m . captured ( " bracket " ) ;
QString loneword = m . captured ( " loneword " ) ;
if ( boolean ! = " " )
{
/* if(wasbool)
{
throw new std : : runtime_error ( " Bool after Bool is invalid " ) ;
} */
wasbool = true ;
result . append ( Command ( boolean ) ) ;
}
if ( bracket ! = " " )
{
if ( ! wasbool )
{
if ( bracket = = " ( " )
{
result . append ( Command ( " AND " ) ) ;
}
}
result . append ( Command ( bracket ) ) ;
}
if ( loneword ! = " " )
{
if ( ! wasbool )
{
result . append ( Command ( " AND " ) ) ;
}
wasbool = false ;
2018-09-02 12:38:07 +02:00
result . append ( Command ( " path.contains " , loneword ) ) ;
2018-09-02 12:27:23 +02:00
}
if ( filtername ! = " " )
{
if ( ! wasbool )
{
result . append ( Command ( " AND " ) ) ;
}
wasbool = false ;
QString value = m . captured ( " innerargs " ) ;
if ( value = = " " )
value = m . captured ( " args " ) ;
result . append ( Command ( filtername , value ) ) ;
}
}
return result ;
}
QString SearchWorker : : createSql ( const SearchWorker : : Command & cmd )
{
QString key = cmd . key ;
QString value = cmd . value ;
2018-09-02 20:30:52 +02:00
value = value . replace ( " ' " , " \\ ' " ) ;
2018-09-02 12:27:23 +02:00
if ( key = = " AND " | | key = = " OR " | | key = = " ( " | | key = = " ) " )
{
return " " + key + " " ;
}
if ( key = = " ! " )
{
return " NOT " ;
}
if ( key = = " path.starts " )
{
return " file.path LIKE ' " + value + " %' " ;
}
if ( key = = " path.ends " )
{
return " file.path LIKE '% " + value + " ' " ;
}
if ( key = = " path.contains " | | key = = " inpath " )
{
return " file.path LIKE '% " + value + " %' " ;
}
if ( key = = " page " )
{
return " content.page = " + value ;
}
2018-09-02 12:38:07 +02:00
if ( key = = " contains " | | key = = " c " )
2018-09-02 12:27:23 +02:00
{
2018-09-02 13:54:27 +02:00
return " content.id IN (SELECT content_fts.ROWID FROM content_fts WHERE content_fts.content MATCH ' " + value + " ' ) " ;
2018-09-02 12:27:23 +02:00
}
2018-09-02 13:54:27 +02:00
throw std : : invalid_argument ( " Unknown filter: " + key . toStdString ( ) ) ;
2018-09-02 12:27:23 +02:00
}
2018-08-30 21:54:29 +02:00
2018-09-02 12:27:23 +02:00
QString SearchWorker : : makeSql ( const QVector < SearchWorker : : Command > & tokens )
2018-08-30 21:54:29 +02:00
{
2018-09-02 12:27:23 +02:00
QString result ;
for ( const Command & c : tokens )
{
result + = createSql ( c ) ;
}
return result ;
2018-08-30 21:54:29 +02:00
}
2018-08-12 16:45:39 +02:00
2018-09-02 13:54:27 +02:00
bool SearchWorker : : checkParanthesis ( QString expression )
{
QStack < QChar > open ;
QStack < QChar > close ;
for ( QChar & c : expression )
{
if ( c = = ' ( ' )
{
open . push ( c ) ;
}
if ( c = = ' ) ' )
{
close . push ( c ) ;
}
}
if ( open . size ( ) ! = close . size ( ) )
{
return false ;
}
while ( ! open . empty ( ) & & ! close . empty ( ) )
{
QChar o = open . pop ( ) ;
QChar c = close . pop ( ) ;
if ( o ! = ' ( ' & & c ! = ' ) ' )
{
return false ;
}
}
return true ;
}
2018-09-02 12:27:23 +02:00
void SearchWorker : : search ( const QString & query )
{
QSqlQuery dbquery ( db ) ;
QVector < SearchResult > results ;
2018-09-02 13:54:27 +02:00
QString whereSql ;
try
{
whereSql = makeSql ( tokenize ( query ) ) ;
}
catch ( const std : : exception & e )
{
emit searchError ( e . what ( ) ) ;
return ;
}
2018-09-02 12:45:28 +02:00
QString prep ;
//TODO: hack, as we don't wanna look into content and get redundant results, when we don't even care about content
if ( whereSql . contains ( " content. " ) )
{
2018-09-02 13:54:27 +02:00
prep = " SELECT file.path AS path, content.page AS page, file.mtime AS mtime FROM file INNER JOIN content ON file.id = content.fileid WHERE 1=1 AND " + whereSql + " ORDER By file.mtime DESC, content.page ASC " ;
2018-09-02 12:45:28 +02:00
}
else
{
prep = " SELECT file.path AS path, 0 as page, file.mtime AS mtime FROM file WHERE " + whereSql + " ORDER by file.mtime DESC " ;
}
2018-09-02 12:27:23 +02:00
dbquery . prepare ( prep ) ;
2018-09-02 13:54:27 +02:00
bool success = dbquery . exec ( ) ;
if ( ! success )
{
qDebug ( ) < < " prepped: " < < prep ;
qDebug ( ) < < dbquery . lastError ( ) ;
emit searchError ( dbquery . lastError ( ) . text ( ) ) ;
return ;
}
2018-09-02 12:27:23 +02:00
while ( dbquery . next ( ) )
{
SearchResult result ;
result . path = dbquery . value ( " path " ) . toString ( ) ;
result . page = dbquery . value ( " page " ) . toUInt ( ) ;
result . mtime = dbquery . value ( " mtime " ) . toUInt ( ) ;
results . append ( result ) ;
}
emit searchResultsReady ( results ) ;
}