29
31
#include <KImageCache>
30
32
#include <KMimeType>
31
34
#include <KIO/PreviewJob>
33
36
#include <soprano/vocabulary.h>
35
38
#include <Nepomuk/File>
39
#include <Nepomuk/Tag>
40
#include <Nepomuk/Variant>
36
42
#include <Nepomuk/Query/AndTerm>
43
#include <Nepomuk/Query/OrTerm>
37
44
#include <Nepomuk/Query/NegationTerm>
38
45
#include <Nepomuk/Query/ResourceTerm>
39
#include <Nepomuk/Tag>
40
#include <Nepomuk/Variant>
41
#include <nepomuk/comparisonterm.h>
42
#include <nepomuk/literalterm.h>
43
#include <nepomuk/queryparser.h>
44
#include <nepomuk/resourcetypeterm.h>
45
#include <nepomuk/standardqueries.h>
47
#include <nepomuk/nfo.h>
48
#include <nepomuk/nie.h>
46
#include <Nepomuk/Query/ComparisonTerm>
47
#include <Nepomuk/Query/LiteralTerm>
48
#include <Nepomuk/Query/QueryParser>
49
#include <Nepomuk/Query/ResourceTypeTerm>
50
#include <Nepomuk/Query/StandardQuery>
54
using namespace Nepomuk::Vocabulary;
55
using namespace Soprano::Vocabulary;
53
57
MetadataModel::MetadataModel(QObject *parent)
54
58
: AbstractMetadataModel(parent),
56
m_screenshotSize(180, 120)
60
m_countQueryClient(0),
63
m_scoreResources(false),
64
m_thumbnailSize(180, 120),
65
m_thumbnailerPlugins(new QStringList(KIO::PreviewJob::availablePlugins()))
58
m_queryTimer = new QTimer(this);
59
m_queryTimer->setSingleShot(true);
60
connect(m_queryTimer, SIGNAL(timeout()),
61
this, SLOT(doQuery()));
63
67
m_newEntriesTimer = new QTimer(this);
64
68
m_newEntriesTimer->setSingleShot(true);
65
69
connect(m_newEntriesTimer, SIGNAL(timeout()),
232
286
rootTerm.addSubTerm(Nepomuk::Query::NegationTerm::negateTerm(Nepomuk::Query::ResourceTypeTerm(propertyUrl(type))));
234
288
rootTerm.addSubTerm(Nepomuk::Query::ResourceTypeTerm(propertyUrl(type)));
235
if (type != "nfo:Bookmark") {
289
/*if (type != "nfo:Bookmark") {
236
290
//FIXME: remove bookmarks if not explicitly asked for
237
291
rootTerm.addSubTerm(Nepomuk::Query::NegationTerm::negateTerm(Nepomuk::Query::ResourceTypeTerm(propertyUrl("nfo:Bookmark"))));
242
if (!mimeType().isEmpty()) {
243
QString type = mimeType();
244
bool negation = false;
245
if (type.startsWith("!")) {
246
type = type.remove(0, 1);
250
Nepomuk::Query::ComparisonTerm term(Nepomuk::Vocabulary::NIE::mimeType(), Nepomuk::Query::LiteralTerm(type));
294
if (resourceType() == "nfo:Archive") {
295
Nepomuk::Query::ComparisonTerm term(Nepomuk::Vocabulary::NIE::mimeType(), Nepomuk::Query::LiteralTerm("application/epub+zip"));
253
297
rootTerm.addSubTerm(Nepomuk::Query::NegationTerm::negateTerm(term));
255
rootTerm.addSubTerm(term);
301
if (!mimeTypeStrings().isEmpty()) {
302
Nepomuk::Query::OrTerm mimeTerm;
303
foreach (QString type, mimeTypeStrings()) {
304
if (type.isEmpty()) {
307
bool negation = false;
308
if (type.startsWith('!')) {
309
type = type.remove(0, 1);
313
Nepomuk::Query::ComparisonTerm term(Nepomuk::Vocabulary::NIE::mimeType(), Nepomuk::Query::LiteralTerm(type), Nepomuk::Query::ComparisonTerm::Equal);
316
mimeTerm.addSubTerm(Nepomuk::Query::NegationTerm::negateTerm(term));
318
mimeTerm.addSubTerm(term);
321
rootTerm.addSubTerm(mimeTerm);
260
325
if (parameters && parameters->size() > 0) {
261
326
foreach (const QString &key, parameters->keys()) {
262
327
QString parameter = parameters->value(key).toString();
328
if (parameter.isEmpty()) {
263
331
bool negation = false;
264
if (parameter.startsWith("!")) {
332
if (parameter.startsWith('!')) {
265
333
parameter = parameter.remove(0, 1);
269
337
//FIXME: Contains should work, but doesn't match for file names
270
Nepomuk::Query::ComparisonTerm term(propertyUrl(key), Nepomuk::Query::LiteralTerm(parameter), Nepomuk::Query::ComparisonTerm::Regexp);
338
// we must prepend and append "*" to the file name for the default Nepomuk match type (Contains) really work.
339
Nepomuk::Query::ComparisonTerm term(propertyUrl(key), Nepomuk::Query::LiteralTerm(parameter));
273
342
rootTerm.addSubTerm(Nepomuk::Query::NegationTerm::negateTerm(term));
328
397
rootTerm.addSubTerm(term);
400
if (m_scoreResources) {
401
QString activity = activityId();
402
if (activity.startsWith('!')) {
403
activity = activity.remove(0, 1);
406
Nepomuk::Query::ComparisonTerm term = Nepomuk::Query::ComparisonTerm(propertyUrl("kao:targettedResource"), Nepomuk::Query::Term());
407
term.setVariableName("c");
408
term.setInverted(true);
410
Nepomuk::Query::AndTerm andTerm = Nepomuk::Query::AndTerm();
411
Nepomuk::Query::ResourceTypeTerm typeTerm(KAO::ResourceScoreCache());
412
andTerm.addSubTerm(typeTerm);
413
if (!activity.isEmpty()) {
414
Nepomuk::Query::ComparisonTerm usedActivityTerm(propertyUrl("kao:usedActivity"),
415
Nepomuk::Query::ResourceTerm(Nepomuk::Resource(activity, Nepomuk::Vocabulary::KAO::Activity()))
417
andTerm.addSubTerm(usedActivityTerm);
419
Nepomuk::Query::ComparisonTerm cachedScoreTerm(propertyUrl("kao:cachedScore"),
420
Nepomuk::Query::Term());
421
cachedScoreTerm.setVariableName("score");
422
cachedScoreTerm.setSortWeight(1, Qt::DescendingOrder);
423
andTerm.addSubTerm(cachedScoreTerm);
425
term.setSubTerm(andTerm);
426
rootTerm.addSubTerm(term);
429
//bind directly some properties, to avoid calling hyper inefficient resource::property
431
m_query.addRequestProperty(Nepomuk::Query::Query::RequestProperty(NIE::url()));
432
m_query.addRequestProperty(Nepomuk::Query::Query::RequestProperty(NAO::hasSymbol()));
433
m_query.addRequestProperty(Nepomuk::Query::Query::RequestProperty(NIE::mimeType()));
434
m_query.addRequestProperty(Nepomuk::Query::Query::RequestProperty(NAO::description()));
435
m_query.addRequestProperty(Nepomuk::Query::Query::RequestProperty(Xesam::description()));
436
m_query.addRequestProperty(Nepomuk::Query::Query::RequestProperty(RDFS::comment()));
332
439
int weight = m_sortBy.length() + 1;
333
440
foreach (const QString &sortProperty, m_sortBy) {
441
if (sortProperty.isEmpty()) {
334
444
Nepomuk::Query::ComparisonTerm sortTerm(propertyUrl(sortProperty), Nepomuk::Query::Term());
335
445
sortTerm.setSortWeight(weight, m_sortOrder);
336
446
rootTerm.addSubTerm(sortTerm);
348
458
emit countChanged();
350
delete m_queryClient;
351
m_queryClient = new Nepomuk::Query::QueryServiceClient(this);
353
connect(m_queryClient, SIGNAL(newEntries(const QList<Nepomuk::Query::Result> &)),
354
this, SLOT(newEntries(const QList<Nepomuk::Query::Result> &)));
355
connect(m_queryClient, SIGNAL(entriesRemoved(const QList<QUrl> &)),
356
this, SLOT(entriesRemoved(const QList<QUrl> &)));
357
connect(m_queryClient, SIGNAL(finishedListing()), this, SLOT(finishedListing()));
359
/*FIXME: safe without limit?
360
if (limit > RESULT_LIMIT || limit <= 0) {
361
m_query.setLimit(RESULT_LIMIT);
365
m_queryClient->query(m_query);
460
delete m_countQueryClient;
461
//qDeleteAll is broken in 4.8
462
foreach (Nepomuk::Query::QueryServiceClient *client, m_queryClients) {
465
m_queryClients.clear();
466
m_pagesForClient.clear();
467
m_validIndexForPage.clear();
468
m_queryClientsHistory.clear();
469
m_cachedResources.clear();
470
m_runningClients = 0;
471
m_countQueryClient = new Nepomuk::Query::QueryServiceClient(this);
473
connect(m_countQueryClient, SIGNAL(newEntries(QList<Nepomuk::Query::Result>)),
474
this, SLOT(countQueryResult(QList<Nepomuk::Query::Result>)));
477
m_query.setLimit(m_limit);
480
m_countQueryClient->sparqlQuery(m_query.toSparqlQuery(Nepomuk::Query::Query::CreateCountQuery));
482
//if page size is invalid, fetch all
483
if (m_pageSize < 1) {
488
// Nepomuk::Query::QueryServiceClient does not emit finishedListing signal when there is no new entries (no matches).
489
QTimer::singleShot(5000, this, SLOT(finishedListing()));
492
void MetadataModel::fetchResultsPage(int page)
494
Nepomuk::Query::QueryServiceClient *client = new Nepomuk::Query::QueryServiceClient(this);
496
m_queryClients[page] = client;
497
m_pagesForClient[client] = page;
498
m_validIndexForPage[page] = 0;
500
Nepomuk::Query::Query pageQuery(m_query);
501
if (m_pageSize > 0) {
502
pageQuery.setOffset(m_pageSize*page);
503
pageQuery.setLimit(m_pageSize);
506
client->query(pageQuery);
508
connect(client, SIGNAL(newEntries(QList<Nepomuk::Query::Result>)),
509
this, SLOT(newEntries(QList<Nepomuk::Query::Result>)));
510
connect(client, SIGNAL(entriesRemoved(QList<QUrl>)),
511
this, SLOT(entriesRemoved(QList<QUrl>)));
512
connect(client, SIGNAL(finishedListing()), this, SLOT(finishedListing()));
514
m_queryClientsHistory << client;
518
void MetadataModel::countQueryResult(const QList< Nepomuk::Query::Result > &entries)
521
//this should be always 1
522
foreach (const Nepomuk::Query::Result &res, entries) {
523
int count = res.additionalBinding(QLatin1String("cnt")).variant().toInt();
525
if (count < m_resources.size()) {
526
beginRemoveRows(QModelIndex(), count-1, m_resources.size()-1);
527
m_resources.resize(count);
529
} else if (count > m_resources.size()) {
530
beginInsertRows(QModelIndex(), m_resources.size(), count-1);
531
m_resources.resize(count);
368
537
void MetadataModel::newEntries(const QList< Nepomuk::Query::Result > &entries)
371
foreach (Nepomuk::Query::Result res, entries) {
539
const int page = m_pagesForClient.value(qobject_cast<Nepomuk::Query::QueryServiceClient *>(sender()));
541
foreach (const Nepomuk::Query::Result &res, entries) {
372
542
//kDebug() << "Result!!!" << res.resource().genericLabel() << res.resource().type();
373
543
//kDebug() << "Result label:" << res.genericLabel();
374
m_resourcesToInsert << res.resource();
545
Nepomuk::Resource resource = res.resource();
546
if (resource.property(propertyUrl("nie:url")).toString().isEmpty()) {
549
m_resourcesToInsert[page] << resource;
551
//pre-popuplating of the cache to avoid accessing properties directly
552
//label is a bit too complex to take from query
553
if (resource.hasType(Nepomuk::Vocabulary::NFO::PaginatedTextDocument())) { // pdf files
554
m_cachedResources[resource][Label] = resource.property(Nepomuk::Vocabulary::NFO::fileName()).toString();
555
//kDebug() << "Using label" << m_cachedResources[resource][Label] << "instead of" << resource.genericLabel();
557
m_cachedResources[resource][Label] = resource.genericLabel();
560
m_cachedResources[resource][Description] = resource.description();
562
m_cachedResources[resource][Url] = resource.property(propertyUrl("nie:url")).toString();
565
foreach (const QUrl &u, resource.types()) {
566
types << u.toString();
568
m_cachedResources[resource][Types] = types;
570
if (!resource.symbols().isEmpty()) {
571
m_cachedResources[resource][Icon] = resource.symbols().first();
573
//if it's an application, fetch the icon from the desktop file
574
Nepomuk::Types::Class resClass(resource.resourceType());
575
if (resClass.label() == "Application") {
576
KService::Ptr serv = KService::serviceByDesktopPath(m_cachedResources[resource][Url].toUrl().path());
578
m_cachedResources[resource][Icon] = serv->icon();
580
m_cachedResources[resource][Icon] = KMimeType::iconNameForUrl(m_cachedResources[resource][Url].toString());
583
m_cachedResources[resource][Icon] = KMimeType::iconNameForUrl(m_cachedResources[resource][Url].toString());
587
//those seems to not be possible avoiding to access the resource
588
m_cachedResources[resource][ClassName] = resource.className();
589
m_cachedResources[resource][ResourceType] = resource.resourceType();
590
m_cachedResources[resource][IsFile] = resource.isFile();
591
// m_cachedResources[resource][MimeType] = resource.mimeType();
592
m_cachedResources[resource][MimeType] = resource.property(propertyUrl("nfo:mimeType")).toString();
594
//FIXME: The most complicated of all, this should really be simplified
596
//FIXME: a more elegant way is needed
597
QString genericClassName = m_cachedResources.value(resource).value(ClassName).toString();
598
//FIXME: most bookmarks are Document too, so Bookmark wins
599
if (m_cachedResources.value(resource).value(Label).value<QList<QUrl> >().contains(NFO::Bookmark())) {
600
m_cachedResources[resource][GenericClassName] = "Bookmark";
603
Nepomuk::Types::Class resClass(resource.resourceType());
604
foreach (const Nepomuk::Types::Class &parentClass, resClass.parentClasses()) {
605
const QString label = parentClass.label();
606
if (label == "Document" ||
610
label == "Contact") {
611
genericClassName = label;
613
//two cases where the class is 2 levels behind the level of generalization we want
614
} else if (parentClass.label() == "RasterImage") {
615
genericClassName = "Image";
616
} else if (parentClass.label() == "TextDocument") {
617
genericClassName = "Document";
620
m_cachedResources[resource][GenericClassName] = genericClassName;
377
if (!m_newEntriesTimer->isActive()) {
625
if (!m_newEntriesTimer->isActive() && !m_resourcesToInsert[page].isEmpty()) {
378
626
m_newEntriesTimer->start(200);
382
630
void MetadataModel::newEntriesDelayed()
384
beginInsertRows(QModelIndex(), m_resources.count(), m_resources.count()+m_resourcesToInsert.count()-1);
388
foreach (Nepomuk::Resource res, m_resourcesToInsert) {
389
//kDebug() << "Result!!!" << res.resource().genericLabel() << res.resource().type();
390
//kDebug() << "Result label:" << res.genericLabel();
391
m_uriToResourceIndex[res.resourceUri()] = m_resources.count();
393
m_watcher->addResource(res);
632
if (m_resourcesToInsert.isEmpty()) {
636
m_elapsedTime.start();
637
QHash<int, QList<Nepomuk::Resource> >::const_iterator i;
638
for (i = m_resourcesToInsert.constBegin(); i != m_resourcesToInsert.constEnd(); ++i) {
639
const QList<Nepomuk::Resource> resourcesToInsert = i.value();
644
if (m_pageSize > 0) {
645
pageStart = i.key() * m_pageSize;
647
int startOffset = m_validIndexForPage.value(i.key());
648
int offset = startOffset;
650
//if new result arrive on an already running query, they may arrive before countQueryResult
651
if (m_resources.size() < pageStart + startOffset + 1) {
652
beginInsertRows(QModelIndex(), m_resources.size(), pageStart + startOffset);
653
m_resources.resize(pageStart + startOffset + 1);
656
//this happens only when m_validIndexForPage has been invalidate by row removal
657
if (!m_validIndexForPage.contains(i.key()) && m_resources[pageStart + startOffset].isValid()) {
658
while (startOffset < m_resources.size() && m_resources[pageStart + startOffset].isValid()) {
664
foreach (const Nepomuk::Resource &res, resourcesToInsert) {
665
//kDebug() << "Result!!!" << res.genericLabel() << res.type();
666
//kDebug() << "Page:" << i.key() << "Index:"<< pageStart + offset;
668
m_uriToResourceIndex[res.resourceUri()] = pageStart + offset;
669
//there can be new results before the count query gets updated
670
if (pageStart + offset < m_resources.size()) {
671
m_resources[pageStart + offset] = res;
672
m_watcher->addResource(res);
675
beginInsertRows(QModelIndex(), m_resources.size(), pageStart + offset);
676
m_resources.resize(pageStart + offset + 1);
677
m_resources[pageStart + offset] = res;
678
m_watcher->addResource(res);
684
m_validIndexForPage[i.key()] = offset;
687
emit dataChanged(createIndex(pageStart + startOffset, 0),
688
createIndex(pageStart + startOffset + resourcesToInsert.count()-1, 0));
690
kDebug() << "Elapsed time populating the model" << m_elapsedTime.elapsed();
398
691
m_resourcesToInsert.clear();
404
694
void MetadataModel::propertyChanged(Nepomuk::Resource res, Nepomuk::Types::Property prop, QVariant val)
468
777
const Nepomuk::Resource &resource = m_resources[index.row()];
780
if (!resource.isValid() && m_pageSize > 0 && !m_queryClients.contains(floor(index.row()/m_pageSize))) {
782
const_cast<MetadataModel *>(this)->fetchResultsPage(floor(index.row()/m_pageSize));
784
//m_pageSize <= 0, means fetch all
785
} else if (!resource.isValid() && !m_queryClients.contains(0)) {
787
const_cast<MetadataModel *>(this)->fetchResultsPage(0);
789
} else if (!resource.isValid()) {
793
//We're lucky: was cached
794
if (m_cachedResources.value(resource).contains(role)) {
795
return m_cachedResources.value(resource).value(role);
471
799
case Qt::DisplayRole:
473
return resource.genericLabel();
475
return resource.genericDescription();
478
foreach (const QUrl &u, resource.types()) {
479
types << u.toString();
484
return resource.className();
485
case GenericClassName: {
486
//FIXME: a more elegant way is needed
487
QString genericClassName = resource.className();
488
//FIXME: most bookmarks are Document too, so Bookmark wins
489
if (resource.types().contains(QUrl::fromEncoded("http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Bookmark"))) {
492
Nepomuk::Types::Class resClass(resource.resourceType());
493
foreach (Nepomuk::Types::Class parentClass, resClass.parentClasses()) {
494
if (parentClass.label() == "Document" ||
495
parentClass.label() == "Audio" ||
496
parentClass.label() == "Video" ||
497
parentClass.label() == "Image" ||
498
parentClass.label() == "Contact") {
499
genericClassName = parentClass.label();
501
//two cases where the class is 2 levels behind the level of generalization we want
502
} else if (parentClass.label() == "RasterImage") {
503
genericClassName = "Image";
504
} else if (parentClass.label() == "TextDocument") {
505
genericClassName = "Document";
508
return genericClassName;
510
case Qt::DecorationRole: {
511
QString icon = resource.genericIcon();
512
if (icon.isEmpty() && resource.isFile()) {
513
KUrl url = resource.toFile().url();
514
if (!url.isEmpty()) {
515
icon = KMimeType::iconNameForUrl(url);
518
if (icon.isEmpty()) {
519
// use resource types to find a suitable icon.
521
icon = retrieveIconName(QStringList(resource.className()));
522
//kDebug() << "symbol" << icon;
524
if (icon.split(",").count() > 1) {
525
kDebug() << "More than one icon!" << icon;
526
icon = icon.split(",").last();
801
return m_cachedResources.value(resource).value(Label);
802
case Qt::DecorationRole:
803
return KIcon(m_cachedResources.value(resource).value(Icon).toString());
532
QString icon = resource.genericIcon();
533
if (icon.isEmpty() && resource.isFile()) {
534
KUrl url = resource.toFile().url();
535
if (!url.isEmpty()) {
536
icon = KMimeType::iconNameForUrl(url);
539
if (icon.isEmpty()) {
540
// use resource types to find a suitable icon.
542
icon = retrieveIconName(QStringList(resource.className()));
543
//kDebug() << "symbol" << icon;
545
if (icon.split(",").count() > 1) {
546
kDebug() << "More than one icon!" << icon;
547
icon = icon.split(",").last();
552
if (resource.isFile() && resource.toFile().url().isLocalFile()) {
553
KUrl file(resource.toFile().url());
554
QImage preview = QImage(m_screenshotSize, QImage::Format_ARGB32_Premultiplied);
806
return m_cachedResources.value(resource).value(Icon).toString();
808
KUrl url(m_cachedResources.value(resource).value(Url).toString());
809
if (m_cachedResources.value(resource).value(IsFile).toBool() && url.isLocalFile()) {
810
QImage preview = QImage(m_thumbnailSize, QImage::Format_ARGB32_Premultiplied);
556
if (m_imageCache->findImage(file.prettyUrl(), &preview)) {
812
if (m_imageCache->findImage(url.prettyUrl(), &preview)) {
560
816
m_previewTimer->start(100);
561
const_cast<MetadataModel *>(this)->m_filesToPreview[file] = QPersistentModelIndex(index);
817
const_cast<MetadataModel *>(this)->m_filesToPreview[url] = QPersistentModelIndex(index);
563
819
return QVariant();
565
return resource.isFile();
567
822
return resource.exists();
569
824
return resource.rating();
570
825
case NumericRating:
571
return resource.property(QUrl("http://www.semanticdesktop.org/ontologies/2007/08/15/nao#numericRating")).toString();
826
return resource.property(NAO::numericRating()).toString();
573
828
return resource.symbols();
574
829
case ResourceUri:
575
830
return resource.resourceUri();
577
return resource.resourceType();
579
return resource.property(QUrl("http://www.semanticdesktop.org/ontologies/2007/01/19/nie#mimeType")).toString();
581
if (resource.isFile() && resource.toFile().url().isLocalFile()) {
582
return resource.toFile().url().prettyUrl();
584
return resource.property(QUrl("http://www.semanticdesktop.org/ontologies/2007/01/19/nie#url")).toString();
588
832
QStringList topics;
589
833
foreach (const Nepomuk::Resource &u, resource.topics()) {