00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 #include "albumlister.moc"
00027
00028
00029
00030 extern "C"
00031 {
00032 #include <sys/time.h>
00033 }
00034
00035
00036
00037 #include <cstdio>
00038 #include <ctime>
00039
00040
00041
00042 #include <QDataStream>
00043 #include <QFileInfo>
00044 #include <QDir>
00045 #include <QMap>
00046 #include <QPair>
00047 #include <QTimer>
00048
00049
00050
00051 #include <kapplication.h>
00052 #include <kcursor.h>
00053 #include <kio/job.h>
00054 #include <kurl.h>
00055 #include <kdebug.h>
00056
00057
00058
00059 #include "databaseaccess.h"
00060 #include "databasewatch.h"
00061 #include "imagelister.h"
00062 #include "mimefilter.h"
00063 #include "album.h"
00064 #include "albummanager.h"
00065 #include "albumsettings.h"
00066
00067 namespace Digikam
00068 {
00069
00070 class AlbumListerPriv
00071 {
00072 public:
00073
00074 AlbumListerPriv()
00075 {
00076 untaggedFilter = false;
00077 ratingFilter = 0;
00078 filterTimer = 0;
00079 refreshTimer = 0;
00080 job = 0;
00081 currAlbum = 0;
00082 mimeTypeFilter = MimeFilter::AllFiles;
00083 ratingCond = AlbumLister::GreaterEqualCondition;
00084 matchingCond = AlbumLister::OrCondition;
00085 recurseAlbums = false;
00086 recurseTags = false;
00087 }
00088
00089 bool untaggedFilter;
00090
00091 int ratingFilter;
00092 int recurseAlbums;
00093 int recurseTags;
00094
00095 QMap<qlonglong, ImageInfo> itemMap;
00096 QMap<QDateTime, bool> dayFilter;
00097 QSet<int> invalidatedItems;
00098
00099 QList<int> tagFilter;
00100
00101 QTimer *filterTimer;
00102 QTimer *refreshTimer;
00103
00104 KIO::TransferJob *job;
00105
00106 SearchTextSettings textFilterSettings;
00107
00108 ImageInfoList itemList;
00109
00110 QSet<qlonglong> itemListSet;
00111
00112 Album *currAlbum;
00113
00114 MimeFilter::TypeMimeFilter mimeTypeFilter;
00115
00116 AlbumLister::MatchingCondition matchingCond;
00117
00118 AlbumLister::RatingCondition ratingCond;
00119 };
00120
00121 AlbumLister* AlbumLister::m_instance = 0;
00122
00123 AlbumLister* AlbumLister::instance()
00124 {
00125 if (!m_instance)
00126 new AlbumLister();
00127
00128 return m_instance;
00129 }
00130
00131 void AlbumLister::cleanUp()
00132 {
00133 delete m_instance;
00134 }
00135
00136 AlbumLister::AlbumLister()
00137 : d(new AlbumListerPriv)
00138 {
00139 m_instance = this;
00140
00141 d->filterTimer = new QTimer(this);
00142 d->refreshTimer = new QTimer(this);
00143
00144 d->refreshTimer->setSingleShot(true);
00145 d->filterTimer->setSingleShot(true);
00146 d->filterTimer->setInterval(100);
00147
00148 connect(d->filterTimer, SIGNAL(timeout()),
00149 this, SLOT(slotFilterItems()));
00150
00151 connect(d->refreshTimer, SIGNAL(timeout()),
00152 this, SLOT(slotNextRefresh()));
00153
00154 connect(DatabaseAccess::databaseWatch(), SIGNAL(imageChange(const ImageChangeset &)),
00155 this, SLOT(slotImageChange(const ImageChangeset &)));
00156
00157 connect(DatabaseAccess::databaseWatch(), SIGNAL(imageTagChange(const ImageTagChangeset &)),
00158 this, SLOT(slotImageTagChange(const ImageTagChangeset &)));
00159
00160 connect(DatabaseAccess::databaseWatch(), SIGNAL(collectionImageChange(const CollectionImageChangeset &)),
00161 this, SLOT(slotCollectionImageChange(const CollectionImageChangeset &)));
00162
00163 connect(DatabaseAccess::databaseWatch(), SIGNAL(searchChange(const SearchChangeset &)),
00164 this, SLOT(slotSearchChange(const SearchChangeset &)));
00165 }
00166
00167 AlbumLister::~AlbumLister()
00168 {
00169 if (d->job)
00170 {
00171 d->job->kill();
00172 d->job = 0;
00173 }
00174
00175 delete d;
00176 m_instance = 0;
00177 }
00178
00179 void AlbumLister::openAlbum(Album *album)
00180 {
00181 d->currAlbum = album;
00182 d->filterTimer->stop();
00183 emit signalClear();
00184 d->itemList.clear();
00185 d->itemListSet.clear();
00186 d->itemMap.clear();
00187
00188 if (d->job)
00189 {
00190 d->job->kill();
00191 d->job = 0;
00192 }
00193
00194 if (!album)
00195 return;
00196
00197 startListJob(album->databaseUrl());
00198 }
00199
00200 void AlbumLister::refresh()
00201 {
00202 if (!d->currAlbum)
00203 return;
00204
00205 d->filterTimer->stop();
00206
00207 if (d->job)
00208 {
00209 d->job->kill();
00210 d->job = 0;
00211 }
00212
00213 d->itemMap.clear();
00214 for (ImageInfoList::const_iterator it = d->itemList.constBegin(); it != d->itemList.constEnd(); ++it)
00215 {
00216 d->itemMap.insert(it->id(), *it);
00217 }
00218
00219 startListJob(d->currAlbum->databaseUrl());
00220 }
00221
00222 void AlbumLister::slotNextRefresh()
00223 {
00224
00225
00226 if (d->job)
00227 d->refreshTimer->start(50);
00228 else
00229 refresh();
00230 }
00231
00232 void AlbumLister::startListJob(const KUrl& url)
00233 {
00234 d->job = ImageLister::startListJob(url);
00235 d->job->addMetaData("listAlbumsRecursively", d->recurseAlbums ? "true" : "false");
00236 d->job->addMetaData("listTagsRecursively", d->recurseTags ? "true" : "false");
00237
00238 connect(d->job, SIGNAL(result(KJob*)),
00239 this, SLOT(slotResult(KJob*)));
00240
00241 connect(d->job, SIGNAL(data(KIO::Job*, const QByteArray&)),
00242 this, SLOT(slotData(KIO::Job*, const QByteArray&)));
00243 }
00244
00245 void AlbumLister::setRecurseAlbums(bool recursive)
00246 {
00247 d->recurseAlbums = recursive;
00248 refresh();
00249 }
00250
00251 void AlbumLister::setRecurseTags(bool recursive)
00252 {
00253 d->recurseTags = recursive;
00254 refresh();
00255 }
00256
00257 void AlbumLister::setDayFilter(const QList<QDateTime>& days)
00258 {
00259 d->dayFilter.clear();
00260
00261 for (QList<QDateTime>::const_iterator it = days.constBegin(); it != days.constEnd(); ++it)
00262 d->dayFilter.insert(*it, true);
00263
00264 if (!d->filterTimer->isActive())
00265 d->filterTimer->start();
00266 }
00267
00268 bool AlbumLister::tagFiltersIsActive()
00269 {
00270 if (!d->tagFilter.isEmpty() || d->untaggedFilter)
00271 return true;
00272
00273 return false;
00274 }
00275
00276 bool AlbumLister::filterIsActive()
00277 {
00278 return !d->dayFilter.isEmpty() || !d->tagFilter.isEmpty() || !d->textFilterSettings.text.isEmpty()
00279 || d->untaggedFilter || d->ratingFilter!=-1;
00280 }
00281
00282 void AlbumLister::setTagFilter(const QList<int>& tags, const MatchingCondition& matchingCond,
00283 bool showUnTagged)
00284 {
00285 d->tagFilter = tags;
00286 d->matchingCond = matchingCond;
00287 d->untaggedFilter = showUnTagged;
00288 if (!d->filterTimer->isActive())
00289 d->filterTimer->start();
00290 }
00291
00292 void AlbumLister::setRatingFilter(int rating, const RatingCondition& ratingCond)
00293 {
00294 d->ratingFilter = rating;
00295 d->ratingCond = ratingCond;
00296 if (!d->filterTimer->isActive())
00297 d->filterTimer->start();
00298 }
00299
00300 void AlbumLister::setMimeTypeFilter(int mimeTypeFilter)
00301 {
00302 d->mimeTypeFilter = (MimeFilter::TypeMimeFilter)mimeTypeFilter;
00303 if (!d->filterTimer->isActive())
00304 d->filterTimer->start();
00305 }
00306
00307 void AlbumLister::setTextFilter(const SearchTextSettings& settings)
00308 {
00309 d->textFilterSettings = settings;
00310 if (!d->filterTimer->isActive())
00311 d->filterTimer->start();
00312 }
00313
00314 bool AlbumLister::matchesFilter(const ImageInfo& info, bool& foundText)
00315 {
00316 if (!filterIsActive())
00317 return true;
00318
00319 bool match = false;
00320
00321 if (!d->tagFilter.isEmpty())
00322 {
00323 QList<int> tagIds = info.tagIds();
00324 QList<int>::iterator it;
00325
00326 if (d->matchingCond == OrCondition)
00327 {
00328 for (it = d->tagFilter.begin(); it != d->tagFilter.end(); ++it)
00329 {
00330 if (tagIds.contains(*it))
00331 {
00332 match = true;
00333 break;
00334 }
00335 }
00336 }
00337 else
00338 {
00339
00340
00341 for (it = d->tagFilter.begin(); it != d->tagFilter.end(); ++it)
00342 {
00343 if (!tagIds.contains(*it))
00344 break;
00345 }
00346
00347 if (it == d->tagFilter.end())
00348 match = true;
00349 }
00350
00351 match |= (d->untaggedFilter && tagIds.isEmpty());
00352 }
00353 else if (d->untaggedFilter)
00354 {
00355 match = info.tagIds().isEmpty();
00356 }
00357 else
00358 {
00359 match = true;
00360 }
00361
00362 if (!d->dayFilter.isEmpty())
00363 {
00364 match &= d->dayFilter.contains(QDateTime(info.dateTime().date(), QTime()));
00365 }
00366
00367
00368
00369 if (d->ratingFilter >= 0)
00370 {
00371
00372 int rating = info.rating();
00373 if (rating == -1)
00374 rating = 0;
00375
00376 if (d->ratingCond == GreaterEqualCondition)
00377 {
00378
00379 if (rating < d->ratingFilter)
00380 {
00381 match = false;
00382 }
00383 }
00384 else if (d->ratingCond == EqualCondition)
00385 {
00386
00387 if (rating != d->ratingFilter)
00388 {
00389 match = false;
00390 }
00391 }
00392 else
00393 {
00394
00395 if (rating > d->ratingFilter)
00396 {
00397 match = false;
00398 }
00399 }
00400 }
00401
00402
00403
00404 switch(d->mimeTypeFilter)
00405 {
00406
00407 case MimeFilter::ImageFiles:
00408 {
00409 if (info.category() != DatabaseItem::Image)
00410 match = false;
00411 break;
00412 }
00413 case MimeFilter::JPGFiles:
00414 {
00415 if (info.format() != "JPG")
00416 match = false;
00417 break;
00418 }
00419 case MimeFilter::PNGFiles:
00420 {
00421 if (info.format() != "PNG")
00422 match = false;
00423 break;
00424 }
00425 case MimeFilter::TIFFiles:
00426 {
00427 if (info.format() != "TIFF")
00428 match = false;
00429 break;
00430 }
00431 case MimeFilter::DNGFiles:
00432 {
00433 if (info.format() != "RAW-DNG")
00434 match = false;
00435 break;
00436 }
00437 case MimeFilter::NoRAWFiles:
00438 {
00439 if (info.format().startsWith(QLatin1String("RAW")))
00440 match = false;
00441 break;
00442 }
00443 case MimeFilter::RAWFiles:
00444 {
00445 if (!info.format().startsWith(QLatin1String("RAW")))
00446 match = false;
00447 break;
00448 }
00449 case MimeFilter::MoviesFiles:
00450 {
00451 if (info.category() != DatabaseItem::Video)
00452 match = false;
00453 break;
00454 }
00455 case MimeFilter::AudioFiles:
00456 {
00457 if (info.category() != DatabaseItem::Audio)
00458 match = false;
00459 break;
00460 }
00461 default:
00462 break;
00463 }
00464
00465
00466
00467 if (!d->textFilterSettings.text.isEmpty())
00468 {
00469 foundText = false;
00470 if (info.name().contains(d->textFilterSettings.text, d->textFilterSettings.caseSensitive))
00471 {
00472 foundText = true;
00473 }
00474 if (info.comment().contains(d->textFilterSettings.text, d->textFilterSettings.caseSensitive))
00475 foundText = true;
00476 QStringList tags = AlbumManager::instance()->tagNames(info.tagIds());
00477 for (QStringList::const_iterator it = tags.constBegin() ; it != tags.constEnd() ; ++it)
00478 {
00479 if ((*it).contains(d->textFilterSettings.text, d->textFilterSettings.caseSensitive))
00480 foundText = true;
00481 }
00482
00483 PAlbum* palbum = AlbumManager::instance()->findPAlbum(info.albumId());
00484 if ((palbum && palbum->title().contains(d->textFilterSettings.text, d->textFilterSettings.caseSensitive)))
00485 {
00486 foundText = true;
00487 }
00488 match &= foundText;
00489 }
00490
00491 return match;
00492 }
00493
00494 void AlbumLister::stop()
00495 {
00496 d->currAlbum = 0;
00497 d->filterTimer->stop();
00498 emit signalClear();
00499
00500 d->itemList.clear();
00501 d->itemListSet.clear();
00502 d->itemMap.clear();
00503
00504 if (d->job)
00505 {
00506 d->job->kill();
00507 d->job = 0;
00508 }
00509 }
00510
00511 void AlbumLister::invalidateItem(const ImageInfo& item)
00512 {
00513 d->invalidatedItems << item.id();
00514 }
00515
00516 void AlbumLister::slotFilterItems()
00517 {
00518 if (d->job)
00519 {
00520 d->filterTimer->start();
00521 return;
00522 }
00523
00524 ImageInfoList newFilteredItemsList;
00525 ImageInfoList deleteFilteredItemsList;
00526 bool matchForText = false;
00527 bool match = false;
00528
00529 for (ImageInfoListIterator it = d->itemList.begin();
00530 it != d->itemList.end(); ++it)
00531 {
00532 bool foundText = false;
00533 if (matchesFilter(*it, foundText))
00534 {
00535 match = true;
00536 newFilteredItemsList.append(*it);
00537 }
00538 else
00539 {
00540 deleteFilteredItemsList.append(*it);
00541 }
00542
00543 if (foundText)
00544 matchForText = true;
00545 }
00546
00547
00548 bool setCursor = (3*deleteFilteredItemsList.count() + newFilteredItemsList.count()) > 1500;
00549 if (setCursor)
00550 kapp->setOverrideCursor(Qt::WaitCursor);
00551
00552 emit signalItemsTextFilterMatch(matchForText);
00553 emit signalItemsFilterMatch(match);
00554
00555 if (!deleteFilteredItemsList.isEmpty())
00556 {
00557
00558
00559
00560
00561 emit signalClear();
00562 }
00563 if (!newFilteredItemsList.isEmpty())
00564 {
00565 emit signalNewFilteredItems(newFilteredItemsList);
00566 }
00567
00568 if (setCursor)
00569 kapp->restoreOverrideCursor();
00570 }
00571
00572 void AlbumLister::slotResult(KJob* job)
00573 {
00574 d->job = 0;
00575
00576 if (job->error())
00577 {
00578 kWarning() << "Failed to list url: " << job->errorString();
00579 d->itemMap.clear();
00580 d->invalidatedItems.clear();
00581 return;
00582 }
00583
00584 for (QMap<qlonglong, ImageInfo>::const_iterator it = d->itemMap.constBegin();
00585 it != d->itemMap.constEnd(); ++it)
00586 {
00587 emit signalDeleteItem(it.value());
00588 emit signalDeleteFilteredItem(it.value());
00589 d->itemList.removeAll(it.value());
00590 d->itemListSet.remove(it.key());
00591 }
00592
00593 d->itemMap.clear();
00594 d->invalidatedItems.clear();
00595
00596 emit signalCompleted();
00597 }
00598
00599 void AlbumLister::slotData(KIO::Job*, const QByteArray& data)
00600 {
00601 if (data.isEmpty())
00602 return;
00603
00604 ImageInfoList newItemsList;
00605 ImageInfoList newFilteredItemsList;
00606
00607 QByteArray tmp(data);
00608 QDataStream ds(&tmp, QIODevice::ReadOnly);
00609
00610 while (!ds.atEnd())
00611 {
00612 bool foundText = false;
00613 ImageListerRecord record;
00614 ds >> record;
00615
00616 if (d->itemMap.contains(record.imageID))
00617 {
00618 ImageInfo info = d->itemMap[record.imageID];
00619 d->itemMap.remove(record.imageID);
00620
00621 if (d->invalidatedItems.contains(record.imageID))
00622 {
00623 emit signalDeleteItem(info);
00624 emit signalDeleteFilteredItem(info);
00625 d->itemList.removeAll(info);
00626 d->itemListSet.remove(info.id());
00627 }
00628 else
00629 {
00630 if (!matchesFilter(info, foundText))
00631 {
00632 emit signalDeleteFilteredItem(info);
00633 }
00634 continue;
00635 }
00636 }
00637
00638 ImageInfo info(record);
00639
00640 if (matchesFilter(info, foundText))
00641 newFilteredItemsList.append(info);
00642
00643 newItemsList.append(info);
00644 d->itemList.append(info);
00645 d->itemListSet.insert(info.id());
00646 }
00647
00648 if (!newFilteredItemsList.isEmpty())
00649 emit signalNewFilteredItems(newFilteredItemsList);
00650
00651 if (!newItemsList.isEmpty())
00652 emit signalNewItems(newItemsList);
00653
00654 slotFilterItems();
00655 }
00656
00657 void AlbumLister::slotImageChange(const ImageChangeset& changeset)
00658 {
00659 if (!d->currAlbum)
00660 return;
00661
00662
00663 if (d->refreshTimer->isActive())
00664 return;
00665
00666 if (d->currAlbum->type() == Album::SEARCH)
00667 {
00668
00669
00670 foreach(const qlonglong& id, changeset.ids())
00671 {
00672
00673 if (d->itemListSet.contains(id))
00674 {
00675 d->refreshTimer->start(100);
00676 return;
00677 }
00678 }
00679 }
00680
00681
00682 if (d->filterTimer->isActive())
00683 return;
00684
00685
00686 if (!filterIsActive())
00687 return;
00688
00689
00690 DatabaseFields::Set set = changeset.changes();
00691 if (!(set & DatabaseFields::CreationDate) && !(set & DatabaseFields::Rating)
00692 && !(set & DatabaseFields::Category) && !(set & DatabaseFields::Format)
00693 && !(set & DatabaseFields::Name) && !(set & DatabaseFields::Comment))
00694 return;
00695
00696
00697 foreach (const qlonglong& id, changeset.ids())
00698 {
00699
00700 if (d->itemListSet.contains(id))
00701 {
00702 d->filterTimer->start();
00703 return;
00704 }
00705 }
00706 }
00707
00708 void AlbumLister::slotImageTagChange(const ImageTagChangeset& changeset)
00709 {
00710 if (!d->currAlbum)
00711 return;
00712
00713
00714 if (d->refreshTimer->isActive())
00715 return;
00716
00717
00718 if (d->filterTimer->isActive())
00719 return;
00720
00721
00722 if (!tagFiltersIsActive())
00723 return;
00724
00725
00726 foreach (const qlonglong& id, changeset.ids())
00727 {
00728
00729 if (d->itemListSet.contains(id))
00730 {
00731 d->filterTimer->start();
00732 return;
00733 }
00734 }
00735 }
00736
00737 void AlbumLister::slotCollectionImageChange(const CollectionImageChangeset& changeset)
00738 {
00739 if (!d->currAlbum)
00740 return;
00741
00742
00743 if (d->refreshTimer->isActive())
00744 return;
00745
00746 bool doRefresh = false;
00747
00748 switch (changeset.operation())
00749 {
00750 case CollectionImageChangeset::Added:
00751 switch(d->currAlbum->type())
00752 {
00753 case Album::PHYSICAL:
00754
00755 doRefresh = changeset.containsAlbum(d->currAlbum->id());
00756 break;
00757 default:
00758
00759 doRefresh = true;
00760 break;
00761 }
00762
00763 case CollectionImageChangeset::Removed:
00764 case CollectionImageChangeset::RemovedAll:
00765
00766 foreach (const qlonglong& id, changeset.ids())
00767 {
00768
00769 if (d->itemListSet.contains(id))
00770 {
00771 doRefresh = true;
00772 break;
00773 }
00774 }
00775 break;
00776
00777 default:
00778 break;
00779 }
00780
00781 if (doRefresh)
00782 {
00783
00784 if (!d->refreshTimer->isActive())
00785 d->refreshTimer->start(100);
00786 }
00787 }
00788
00789 void AlbumLister::slotSearchChange(const SearchChangeset& changeset)
00790 {
00791 if (!d->currAlbum)
00792 return;
00793
00794 if (changeset.operation() != SearchChangeset::Changed)
00795 return;
00796
00797 SAlbum *album = AlbumManager::instance()->findSAlbum(changeset.searchId());
00798
00799 if (album && d->currAlbum == album)
00800 {
00801 if (!d->refreshTimer->isActive())
00802 d->refreshTimer->start(100);
00803 }
00804 }
00805
00806 }