]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodelrolesupdater.cpp
Remove kde-runtime dependency "nepomukdatamanagement"
[dolphin.git] / src / kitemviews / kfileitemmodelrolesupdater.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
19
20 #include "kfileitemmodelrolesupdater.h"
21
22 #include "kfileitemmodel.h"
23
24 #include <KConfig>
25 #include <KConfigGroup>
26 #include <KDebug>
27 #include <KFileItem>
28 #include <KGlobal>
29 #include <KIO/JobUiDelegate>
30 #include <KIO/PreviewJob>
31
32 #include "private/kpixmapmodifier.h"
33
34 #include <QApplication>
35 #include <QPainter>
36 #include <QPixmap>
37 #include <QElapsedTimer>
38 #include <QTimer>
39
40 #ifdef HAVE_NEPOMUK
41 #include "private/knepomukrolesprovider.h"
42 #include "private/nepomuk/resourcewatcher.h"
43 #endif
44
45 // Required includes for subItemsCount():
46 #ifdef Q_WS_WIN
47 #include <QDir>
48 #else
49 #include <dirent.h>
50 #include <QFile>
51 #endif
52
53 // #define KFILEITEMMODELROLESUPDATER_DEBUG
54
55 namespace {
56 // Maximum time in ms that the KFileItemModelRolesUpdater
57 // may perform a blocking operation
58 const int MaxBlockTimeout = 200;
59
60 // Maximum number of items that will get resolved synchronously.
61 // The value should roughly represent the number of maximum visible
62 // items, as it does not make sense to resolve more items synchronously
63 // and probably reach the MaxBlockTimeout because of invisible items.
64 const int MaxResolveItemsCount = 100;
65 }
66
67 KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QObject* parent) :
68 QObject(parent),
69 m_paused(false),
70 m_previewChangedDuringPausing(false),
71 m_iconSizeChangedDuringPausing(false),
72 m_rolesChangedDuringPausing(false),
73 m_previewShown(false),
74 m_enlargeSmallPreviews(true),
75 m_clearPreviews(false),
76 m_sortingProgress(-1),
77 m_model(model),
78 m_iconSize(),
79 m_firstVisibleIndex(0),
80 m_lastVisibleIndex(-1),
81 m_roles(),
82 m_enabledPlugins(),
83 m_pendingVisibleItems(),
84 m_pendingInvisibleItems(),
85 m_previewJobs(),
86 m_changedItemsTimer(0),
87 m_changedItems()
88 #ifdef HAVE_NEPOMUK
89 , m_nepomukResourceWatcher(0),
90 m_nepomukUriItems()
91 #endif
92
93 {
94 Q_ASSERT(model);
95
96 const KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings");
97 m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList()
98 << "directorythumbnail"
99 << "imagethumbnail"
100 << "jpegthumbnail");
101
102 connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
103 this, SLOT(slotItemsInserted(KItemRangeList)));
104 connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
105 this, SLOT(slotItemsRemoved(KItemRangeList)));
106 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
107 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
108 connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
109 this, SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
110
111 // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous
112 // resolving of the roles. Postpone the resolving until no update has been done for 1 second.
113 m_changedItemsTimer = new QTimer(this);
114 m_changedItemsTimer->setInterval(1000);
115 m_changedItemsTimer->setSingleShot(true);
116 connect(m_changedItemsTimer, SIGNAL(timeout()), this, SLOT(resolveChangedItems()));
117
118 m_resolvableRoles.insert("size");
119 m_resolvableRoles.insert("type");
120 m_resolvableRoles.insert("isExpandable");
121 #ifdef HAVE_NEPOMUK
122 m_resolvableRoles += KNepomukRolesProvider::instance().roles();
123 #endif
124 }
125
126 KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
127 {
128 }
129
130 void KFileItemModelRolesUpdater::setIconSize(const QSize& size)
131 {
132 if (size != m_iconSize) {
133 m_iconSize = size;
134 if (m_paused) {
135 m_iconSizeChangedDuringPausing = true;
136 } else if (m_previewShown) {
137 // An icon size change requires the regenerating of
138 // all previews
139 sortAndResolveAllRoles();
140 } else {
141 sortAndResolvePendingRoles();
142 }
143 }
144 }
145
146 QSize KFileItemModelRolesUpdater::iconSize() const
147 {
148 return m_iconSize;
149 }
150
151 void KFileItemModelRolesUpdater::setVisibleIndexRange(int index, int count)
152 {
153 if (index < 0) {
154 index = 0;
155 }
156 if (count < 0) {
157 count = 0;
158 }
159
160 if (index == m_firstVisibleIndex && count == m_lastVisibleIndex - m_firstVisibleIndex + 1) {
161 // The range has not been changed
162 return;
163 }
164
165 m_firstVisibleIndex = index;
166 m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1);
167
168 if (hasPendingRoles() && !m_paused) {
169 sortAndResolvePendingRoles();
170 }
171 }
172
173 void KFileItemModelRolesUpdater::setPreviewsShown(bool show)
174 {
175 if (show == m_previewShown) {
176 return;
177 }
178
179 m_previewShown = show;
180 if (!show) {
181 m_clearPreviews = true;
182 }
183
184 updateAllPreviews();
185 }
186
187 bool KFileItemModelRolesUpdater::previewsShown() const
188 {
189 return m_previewShown;
190 }
191
192 void KFileItemModelRolesUpdater::setEnlargeSmallPreviews(bool enlarge)
193 {
194 if (enlarge != m_enlargeSmallPreviews) {
195 m_enlargeSmallPreviews = enlarge;
196 if (m_previewShown) {
197 updateAllPreviews();
198 }
199 }
200 }
201
202 bool KFileItemModelRolesUpdater::enlargeSmallPreviews() const
203 {
204 return m_enlargeSmallPreviews;
205 }
206
207 void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList& list)
208 {
209 if (m_enabledPlugins == list) {
210 m_enabledPlugins = list;
211 if (m_previewShown) {
212 updateAllPreviews();
213 }
214 }
215 }
216
217 void KFileItemModelRolesUpdater::setPaused(bool paused)
218 {
219 if (paused == m_paused) {
220 return;
221 }
222
223 m_paused = paused;
224 if (paused) {
225 if (hasPendingRoles()) {
226 foreach (KJob* job, m_previewJobs) {
227 job->kill();
228 }
229 Q_ASSERT(m_previewJobs.isEmpty());
230 }
231 } else {
232 const bool resolveAll = (m_iconSizeChangedDuringPausing && m_previewShown) ||
233 m_previewChangedDuringPausing ||
234 m_rolesChangedDuringPausing;
235 if (resolveAll) {
236 sortAndResolveAllRoles();
237 } else {
238 sortAndResolvePendingRoles();
239 }
240
241 m_iconSizeChangedDuringPausing = false;
242 m_previewChangedDuringPausing = false;
243 m_rolesChangedDuringPausing = false;
244 }
245 }
246
247 void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray>& roles)
248 {
249 if (m_roles != roles) {
250 m_roles = roles;
251
252 #ifdef HAVE_NEPOMUK
253 // Check whether there is at least one role that must be resolved
254 // with the help of Nepomuk. If this is the case, a (quite expensive)
255 // resolving will be done in KFileItemModelRolesUpdater::rolesData() and
256 // the role gets watched for changes.
257 const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance();
258 bool hasNepomukRole = false;
259 QSetIterator<QByteArray> it(roles);
260 while (it.hasNext()) {
261 const QByteArray& role = it.next();
262 if (rolesProvider.roles().contains(role)) {
263 hasNepomukRole = true;
264 break;
265 }
266 }
267
268 if (hasNepomukRole && !m_nepomukResourceWatcher) {
269 Q_ASSERT(m_nepomukUriItems.isEmpty());
270
271 m_nepomukResourceWatcher = new Nepomuk::ResourceWatcher(this);
272 connect(m_nepomukResourceWatcher, SIGNAL(propertyChanged(Nepomuk::Resource,Nepomuk::Types::Property,QVariantList,QVariantList)),
273 this, SLOT(applyChangedNepomukRoles(Nepomuk::Resource)));
274 connect(m_nepomukResourceWatcher, SIGNAL(propertyRemoved(Nepomuk::Resource,Nepomuk::Types::Property,QVariant)),
275 this, SLOT(applyChangedNepomukRoles(Nepomuk::Resource)));
276 connect(m_nepomukResourceWatcher, SIGNAL(propertyAdded(Nepomuk::Resource,Nepomuk::Types::Property,QVariant)),
277 this, SLOT(applyChangedNepomukRoles(Nepomuk::Resource)));
278 connect(m_nepomukResourceWatcher, SIGNAL(resourceCreated(Nepomuk::Resource,QList<QUrl>)),
279 this, SLOT(applyChangedNepomukRoles(Nepomuk::Resource)));
280 } else if (!hasNepomukRole && m_nepomukResourceWatcher) {
281 delete m_nepomukResourceWatcher;
282 m_nepomukResourceWatcher = 0;
283 m_nepomukUriItems.clear();
284 }
285 #endif
286
287 updateSortProgress();
288
289 if (m_paused) {
290 m_rolesChangedDuringPausing = true;
291 } else {
292 sortAndResolveAllRoles();
293 }
294 }
295 }
296
297 QSet<QByteArray> KFileItemModelRolesUpdater::roles() const
298 {
299 return m_roles;
300 }
301
302 bool KFileItemModelRolesUpdater::isPaused() const
303 {
304 return m_paused;
305 }
306
307 QStringList KFileItemModelRolesUpdater::enabledPlugins() const
308 {
309 return m_enabledPlugins;
310 }
311
312 void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList& itemRanges)
313 {
314 startUpdating(itemRanges);
315 }
316
317 void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRanges)
318 {
319 Q_UNUSED(itemRanges);
320
321 #ifdef HAVE_NEPOMUK
322 if (m_nepomukResourceWatcher) {
323 // Don't let the ResourceWatcher watch for removed items
324 if (m_model->count() == 0) {
325 m_nepomukResourceWatcher->setResources(QList<Nepomuk::Resource>());
326 m_nepomukUriItems.clear();
327 } else {
328 QList<Nepomuk::Resource> newResources;
329 const QList<Nepomuk::Resource> oldResources = m_nepomukResourceWatcher->resources();
330 foreach (const Nepomuk::Resource& resource, oldResources) {
331 const QUrl uri = resource.resourceUri();
332 const KUrl itemUrl = m_nepomukUriItems.value(uri);
333 if (m_model->index(itemUrl) >= 0) {
334 newResources.append(resource);
335 } else {
336 m_nepomukUriItems.remove(uri);
337 }
338 }
339 m_nepomukResourceWatcher->setResources(newResources);
340 }
341 }
342 #endif
343
344 m_firstVisibleIndex = 0;
345 m_lastVisibleIndex = -1;
346 if (!hasPendingRoles()) {
347 return;
348 }
349
350 if (m_model->count() == 0) {
351 // Most probably a directory change is done. Clear all pending items
352 // and also kill all ongoing preview-jobs.
353 resetPendingRoles();
354
355 m_changedItems.clear();
356 m_changedItemsTimer->stop();
357 } else {
358 // Remove all items from m_pendingVisibleItems and m_pendingInvisibleItems
359 // that are not part of the model anymore. The items from m_changedItems
360 // don't need to be handled here, removed items are just skipped in
361 // resolveChangedItems().
362 for (int i = 0; i <= 1; ++i) {
363 QSet<KFileItem>& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems;
364 QMutableSetIterator<KFileItem> it(pendingItems);
365 while (it.hasNext()) {
366 const KFileItem item = it.next();
367 if (m_model->index(item) < 0) {
368 pendingItems.remove(item);
369 }
370 }
371 }
372 }
373 }
374
375 void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRanges,
376 const QSet<QByteArray>& roles)
377 {
378 Q_UNUSED(roles);
379
380 if (m_changedItemsTimer->isActive()) {
381 // A call of slotItemsChanged() has been done recently. Postpone the resolving
382 // of the roles until the timer has exceeded.
383 foreach (const KItemRange& itemRange, itemRanges) {
384 int index = itemRange.index;
385 for (int count = itemRange.count; count > 0; --count) {
386 m_changedItems.insert(m_model->fileItem(index));
387 ++index;
388 }
389 }
390 } else {
391 // No call of slotItemsChanged() has been done recently, resolve the roles now.
392 startUpdating(itemRanges);
393 }
394 m_changedItemsTimer->start();
395 }
396
397 void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current,
398 const QByteArray& previous)
399 {
400 Q_UNUSED(current);
401 Q_UNUSED(previous);
402 updateSortProgress();
403 }
404
405 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPixmap& pixmap)
406 {
407 m_pendingVisibleItems.remove(item);
408 m_pendingInvisibleItems.remove(item);
409
410 const int index = m_model->index(item);
411 if (index < 0) {
412 return;
413 }
414
415 QPixmap scaledPixmap = pixmap;
416
417 const QString mimeType = item.mimetype();
418 const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
419 const QString mimeTypeGroup = mimeType.left(slashIndex);
420 if (mimeTypeGroup == QLatin1String("image")) {
421 if (m_enlargeSmallPreviews) {
422 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
423 } else {
424 // Assure that small previews don't get enlarged. Instead they
425 // should be shown centered within the frame.
426 const QSize contentSize = KPixmapModifier::sizeInsideFrame(m_iconSize);
427 const bool enlargingRequired = scaledPixmap.width() < contentSize.width() &&
428 scaledPixmap.height() < contentSize.height();
429 if (enlargingRequired) {
430 QSize frameSize = scaledPixmap.size();
431 frameSize.scale(m_iconSize, Qt::KeepAspectRatio);
432
433 QPixmap largeFrame(frameSize);
434 largeFrame.fill(Qt::transparent);
435
436 KPixmapModifier::applyFrame(largeFrame, frameSize);
437
438 QPainter painter(&largeFrame);
439 painter.drawPixmap((largeFrame.width() - scaledPixmap.width()) / 2,
440 (largeFrame.height() - scaledPixmap.height()) / 2,
441 scaledPixmap);
442 scaledPixmap = largeFrame;
443 } else {
444 // The image must be shrinked as it is too large to fit into
445 // the available icon size
446 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
447 }
448 }
449 } else {
450 KPixmapModifier::scale(scaledPixmap, m_iconSize);
451 }
452
453 QHash<QByteArray, QVariant> data = rolesData(item);
454 data.insert("iconPixmap", scaledPixmap);
455
456 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
457 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
458 m_model->setData(index, data);
459 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
460 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
461
462 applySortProgressToModel();
463 }
464
465 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item)
466 {
467 m_pendingVisibleItems.remove(item);
468 m_pendingInvisibleItems.remove(item);
469
470 const bool clearPreviews = m_clearPreviews;
471 m_clearPreviews = true;
472 applyResolvedRoles(item, ResolveAll);
473 m_clearPreviews = clearPreviews;
474
475 applySortProgressToModel();
476 }
477
478 void KFileItemModelRolesUpdater::slotPreviewJobFinished(KJob* job)
479 {
480 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
481 kDebug() << "Preview job finished. Pending visible:" << m_pendingVisibleItems.count() << "invisible:" << m_pendingInvisibleItems.count();
482 #endif
483
484 m_previewJobs.removeOne(job);
485 if (!m_previewJobs.isEmpty() || !hasPendingRoles()) {
486 return;
487 }
488
489 const KFileItemList visibleItems = sortedItems(m_pendingVisibleItems);
490 startPreviewJob(visibleItems + m_pendingInvisibleItems.toList());
491 }
492
493 void KFileItemModelRolesUpdater::resolveNextPendingRoles()
494 {
495 if (m_paused) {
496 return;
497 }
498
499 if (m_previewShown) {
500 // The preview has been turned on since the last run. Skip
501 // resolving further pending roles as this is done as soon
502 // as a preview has been received.
503 return;
504 }
505
506 int resolvedCount = 0;
507 bool changed = false;
508 for (int i = 0; i <= 1; ++i) {
509 QSet<KFileItem>& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems;
510 QSetIterator<KFileItem> it(pendingItems);
511 while (it.hasNext() && !changed && resolvedCount < MaxResolveItemsCount) {
512 const KFileItem item = it.next();
513 pendingItems.remove(item);
514 changed = applyResolvedRoles(item, ResolveAll);
515 ++resolvedCount;
516 }
517 }
518
519 if (hasPendingRoles()) {
520 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
521 } else {
522 m_clearPreviews = false;
523 }
524
525 applySortProgressToModel();
526
527 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
528 static int callCount = 0;
529 ++callCount;
530 if (callCount % 100 == 0) {
531 kDebug() << "Remaining visible roles to resolve:" << m_pendingVisibleItems.count()
532 << "invisible:" << m_pendingInvisibleItems.count();
533 }
534 #endif
535 }
536
537 void KFileItemModelRolesUpdater::resolveChangedItems()
538 {
539 if (m_changedItems.isEmpty()) {
540 return;
541 }
542
543 KItemRangeList itemRanges;
544
545 QSetIterator<KFileItem> it(m_changedItems);
546 while (it.hasNext()) {
547 const KFileItem& item = it.next();
548 const int index = m_model->index(item);
549 if (index >= 0) {
550 itemRanges.append(KItemRange(index, 1));
551 }
552 }
553 m_changedItems.clear();
554
555 startUpdating(itemRanges);
556 }
557
558 void KFileItemModelRolesUpdater::applyChangedNepomukRoles(const Nepomuk::Resource& resource)
559 {
560 #ifdef HAVE_NEPOMUK
561 const KUrl itemUrl = m_nepomukUriItems.value(resource.resourceUri());
562 const KFileItem item = m_model->fileItem(itemUrl);
563 QHash<QByteArray, QVariant> data = rolesData(item);
564
565 const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance();
566 QHashIterator<QByteArray, QVariant> it(rolesProvider.roleValues(resource, m_roles));
567 while (it.hasNext()) {
568 it.next();
569 data.insert(it.key(), it.value());
570 }
571
572 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
573 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
574 const int index = m_model->index(item);
575 m_model->setData(index, data);
576 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
577 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
578 #else
579 Q_UNUSED(resource);
580 #endif
581 }
582
583 void KFileItemModelRolesUpdater::startUpdating(const KItemRangeList& itemRanges)
584 {
585 // If no valid index range is given assume that all items are visible.
586 // A cleanup will be done later as soon as the index range has been set.
587 const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
588
589 if (hasValidIndexRange) {
590 // Move all current pending visible items that are not visible anymore
591 // to the pending invisible items.
592 QSetIterator<KFileItem> it(m_pendingVisibleItems);
593 while (it.hasNext()) {
594 const KFileItem item = it.next();
595 const int index = m_model->index(item);
596 if (index < m_firstVisibleIndex || index > m_lastVisibleIndex) {
597 m_pendingVisibleItems.remove(item);
598 m_pendingInvisibleItems.insert(item);
599 }
600 }
601 }
602
603 int rangesCount = 0;
604
605 foreach (const KItemRange& range, itemRanges) {
606 rangesCount += range.count;
607
608 // Add the inserted items to the pending visible and invisible items
609 const int lastIndex = range.index + range.count - 1;
610 for (int i = range.index; i <= lastIndex; ++i) {
611 const KFileItem item = m_model->fileItem(i);
612 if (!hasValidIndexRange || (i >= m_firstVisibleIndex && i <= m_lastVisibleIndex)) {
613 m_pendingVisibleItems.insert(item);
614 } else {
615 m_pendingInvisibleItems.insert(item);
616 }
617 }
618 }
619
620 resolvePendingRoles();
621 }
622
623 void KFileItemModelRolesUpdater::startPreviewJob(const KFileItemList& items)
624 {
625 if (items.isEmpty() || m_paused) {
626 return;
627 }
628
629 // PreviewJob internally caches items always with the size of
630 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
631 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
632 // do a downscaling anyhow because of the frame, so in this case only the provided
633 // cache sizes are requested.
634 const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128)
635 ? QSize(256, 256) : QSize(128, 128);
636
637 // KIO::filePreview() will request the MIME-type of all passed items, which (in the
638 // worst case) might block the application for several seconds. To prevent such
639 // a blocking the MIME-type of the items will determined until the MaxBlockTimeout
640 // has been reached and only those items will get passed. As soon as the MIME-type
641 // has been resolved once KIO::PreviewJob() can already access the resolved
642 // MIME-type in a fast way.
643 QElapsedTimer timer;
644 timer.start();
645 KFileItemList itemSubSet;
646 for (int i = 0; i < items.count(); ++i) {
647 KFileItem item = items.at(i);
648 item.determineMimeType();
649 itemSubSet.append(items.at(i));
650 if (timer.elapsed() > MaxBlockTimeout) {
651 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
652 kDebug() << "Maximum time of" << MaxBlockTimeout << "ms exceeded, creating only previews for"
653 << (i + 1) << "items," << (items.count() - (i + 1)) << "will be resolved later";
654 #endif
655 break;
656 }
657 }
658 KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins);
659 job->setIgnoreMaximumSize(items.first().isLocalFile());
660 if (job->ui()) {
661 job->ui()->setWindow(qApp->activeWindow());
662 }
663
664 connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)),
665 this, SLOT(slotGotPreview(KFileItem,QPixmap)));
666 connect(job, SIGNAL(failed(KFileItem)),
667 this, SLOT(slotPreviewFailed(KFileItem)));
668 connect(job, SIGNAL(finished(KJob*)),
669 this, SLOT(slotPreviewJobFinished(KJob*)));
670
671 m_previewJobs.append(job);
672 }
673
674
675 bool KFileItemModelRolesUpdater::hasPendingRoles() const
676 {
677 return !m_pendingVisibleItems.isEmpty() || !m_pendingInvisibleItems.isEmpty();
678 }
679
680 void KFileItemModelRolesUpdater::resolvePendingRoles()
681 {
682 int resolvedCount = 0;
683
684 bool hasSlowRoles = m_previewShown;
685 if (!hasSlowRoles) {
686 QSetIterator<QByteArray> it(m_roles);
687 while (it.hasNext()) {
688 if (m_resolvableRoles.contains(it.next())) {
689 hasSlowRoles = true;
690 break;
691 }
692 }
693 }
694
695 const ResolveHint resolveHint = hasSlowRoles ? ResolveFast : ResolveAll;
696
697 // Resolving the MIME type can be expensive. Assure that not more than MaxBlockTimeout ms are
698 // spend for resolving them synchronously. Usually this is more than enough to determine
699 // all visible items, but there are corner cases where this limit gets easily exceeded.
700 QElapsedTimer timer;
701 timer.start();
702
703 // Resolve the MIME type of all visible items
704 QSetIterator<KFileItem> visibleIt(m_pendingVisibleItems);
705 while (visibleIt.hasNext()) {
706 const KFileItem item = visibleIt.next();
707 if (!hasSlowRoles) {
708 Q_ASSERT(!m_pendingInvisibleItems.contains(item));
709 // All roles will be resolved by applyResolvedRoles()
710 m_pendingVisibleItems.remove(item);
711 }
712 applyResolvedRoles(item, resolveHint);
713 ++resolvedCount;
714
715 if (timer.elapsed() > MaxBlockTimeout) {
716 break;
717 }
718 }
719
720 // Resolve the MIME type of the invisible items at least until the timeout
721 // has been exceeded or the maximum number of items has been reached
722 KFileItemList invisibleItems;
723 if (m_lastVisibleIndex >= 0) {
724 // The visible range is valid, don't care about the order how the MIME
725 // type of invisible items get resolved
726 invisibleItems = m_pendingInvisibleItems.toList();
727 } else {
728 // The visible range is temporary invalid (e.g. happens when loading
729 // a directory) so take care to sort the currently invisible items where
730 // a part will get visible later
731 invisibleItems = sortedItems(m_pendingInvisibleItems);
732 }
733
734 int index = 0;
735 while (resolvedCount < MaxResolveItemsCount && index < invisibleItems.count() && timer.elapsed() <= MaxBlockTimeout) {
736 const KFileItem item = invisibleItems.at(index);
737 applyResolvedRoles(item, resolveHint);
738
739 if (!hasSlowRoles) {
740 // All roles have been resolved already by applyResolvedRoles()
741 m_pendingInvisibleItems.remove(item);
742 }
743 ++index;
744 ++resolvedCount;
745 }
746
747 if (m_previewShown) {
748 KFileItemList items = sortedItems(m_pendingVisibleItems);
749 items += invisibleItems;
750 startPreviewJob(items);
751 } else {
752 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
753 }
754
755 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
756 if (timer.elapsed() > MaxBlockTimeout) {
757 kDebug() << "Maximum time of" << MaxBlockTimeout
758 << "ms exceeded, skipping items... Remaining visible:" << m_pendingVisibleItems.count()
759 << "invisible:" << m_pendingInvisibleItems.count();
760 }
761 kDebug() << "[TIME] Resolved pending roles:" << timer.elapsed();
762 #endif
763
764 applySortProgressToModel();
765 }
766
767 void KFileItemModelRolesUpdater::resetPendingRoles()
768 {
769 m_pendingVisibleItems.clear();
770 m_pendingInvisibleItems.clear();
771
772 foreach (KJob* job, m_previewJobs) {
773 job->kill();
774 }
775 Q_ASSERT(m_previewJobs.isEmpty());
776 }
777
778 void KFileItemModelRolesUpdater::sortAndResolveAllRoles()
779 {
780 if (m_paused) {
781 return;
782 }
783
784 resetPendingRoles();
785 Q_ASSERT(m_pendingVisibleItems.isEmpty());
786 Q_ASSERT(m_pendingInvisibleItems.isEmpty());
787
788 if (m_model->count() == 0) {
789 return;
790 }
791
792 // Determine all visible items
793 Q_ASSERT(m_firstVisibleIndex >= 0);
794 for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
795 const KFileItem item = m_model->fileItem(i);
796 if (!item.isNull()) {
797 m_pendingVisibleItems.insert(item);
798 }
799 }
800
801 // Determine all invisible items
802 for (int i = 0; i < m_firstVisibleIndex; ++i) {
803 const KFileItem item = m_model->fileItem(i);
804 if (!item.isNull()) {
805 m_pendingInvisibleItems.insert(item);
806 }
807 }
808 for (int i = m_lastVisibleIndex + 1; i < m_model->count(); ++i) {
809 const KFileItem item = m_model->fileItem(i);
810 if (!item.isNull()) {
811 m_pendingInvisibleItems.insert(item);
812 }
813 }
814
815 resolvePendingRoles();
816 }
817
818 void KFileItemModelRolesUpdater::sortAndResolvePendingRoles()
819 {
820 Q_ASSERT(!m_paused);
821 if (m_model->count() == 0) {
822 return;
823 }
824
825 // If no valid index range is given assume that all items are visible.
826 // A cleanup will be done later as soon as the index range has been set.
827 const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
828
829 // Trigger a preview generation of all pending items. Assure that the visible
830 // pending items get generated first.
831 QSet<KFileItem> pendingItems;
832 pendingItems += m_pendingVisibleItems;
833 pendingItems += m_pendingInvisibleItems;
834
835 resetPendingRoles();
836 Q_ASSERT(m_pendingVisibleItems.isEmpty());
837 Q_ASSERT(m_pendingInvisibleItems.isEmpty());
838
839 QSetIterator<KFileItem> it(pendingItems);
840 while (it.hasNext()) {
841 const KFileItem item = it.next();
842 if (item.isNull()) {
843 continue;
844 }
845
846 const int index = m_model->index(item);
847 if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) {
848 m_pendingVisibleItems.insert(item);
849 } else {
850 m_pendingInvisibleItems.insert(item);
851 }
852 }
853
854 resolvePendingRoles();
855 }
856
857 void KFileItemModelRolesUpdater::applySortProgressToModel()
858 {
859 if (m_sortingProgress < 0) {
860 return;
861 }
862
863 // Inform the model about the progress of the resolved items,
864 // so that it can give an indication when the sorting has been finished.
865 const int resolvedCount = m_model->count()
866 - m_pendingVisibleItems.count()
867 - m_pendingInvisibleItems.count();
868 if (resolvedCount > 0) {
869 m_model->emitSortProgress(resolvedCount);
870 if (resolvedCount == m_model->count()) {
871 m_sortingProgress = -1;
872 }
873 }
874 }
875
876 void KFileItemModelRolesUpdater::updateSortProgress()
877 {
878 const QByteArray sortRole = m_model->sortRole();
879
880 // Optimization if the sorting is done by type: In case if all MIME-types
881 // are known, the types have been resolved already by KFileItemModel and
882 // no sort-progress feedback is required.
883 const bool showProgress = (sortRole == "type")
884 ? hasUnknownMimeTypes()
885 : m_resolvableRoles.contains(sortRole);
886
887 if (m_sortingProgress >= 0) {
888 // Mark the current sorting as finished
889 m_model->emitSortProgress(m_model->count());
890 }
891 m_sortingProgress = showProgress ? 0 : -1;
892 }
893
894 bool KFileItemModelRolesUpdater::hasUnknownMimeTypes() const
895 {
896 const int count = m_model->count();
897 for (int i = 0; i < count; ++i) {
898 const KFileItem item = m_model->fileItem(i);
899 if (!item.isMimeTypeKnown()) {
900 return true;
901 }
902 }
903
904 return false;
905 }
906
907 bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, ResolveHint hint)
908 {
909 if (item.isNull()) {
910 return false;
911 }
912
913 const bool resolveAll = (hint == ResolveAll);
914
915 bool mimeTypeChanged = false;
916 if (!item.isMimeTypeKnown()) {
917 item.determineMimeType();
918 mimeTypeChanged = true;
919 }
920
921 if (mimeTypeChanged || resolveAll || m_clearPreviews) {
922 const int index = m_model->index(item);
923 if (index < 0) {
924 return false;
925 }
926
927 QHash<QByteArray, QVariant> data;
928 if (resolveAll) {
929 data = rolesData(item);
930 }
931
932 data.insert("iconName", item.iconName());
933
934 if (m_clearPreviews) {
935 data.insert("iconPixmap", QPixmap());
936 }
937
938 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
939 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
940 m_model->setData(index, data);
941 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
942 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
943 return true;
944 }
945
946 return false;
947 }
948
949 QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem& item) const
950 {
951 QHash<QByteArray, QVariant> data;
952
953 const bool getSizeRole = m_roles.contains("size");
954 const bool getIsExpandableRole = m_roles.contains("isExpandable");
955
956 if ((getSizeRole || getIsExpandableRole) && item.isDir()) {
957 if (item.isLocalFile()) {
958 const QString path = item.localPath();
959 const int count = subItemsCount(path);
960 if (getSizeRole) {
961 data.insert("size", count);
962 }
963 if (getIsExpandableRole) {
964 data.insert("isExpandable", count > 0);
965 }
966 } else if (getSizeRole) {
967 data.insert("size", -1); // -1 indicates an unknown number of items
968 }
969 }
970
971 if (m_roles.contains("type")) {
972 data.insert("type", item.mimeComment());
973 }
974
975 data.insert("iconOverlays", item.overlays());
976
977 #ifdef HAVE_NEPOMUK
978 if (m_nepomukResourceWatcher) {
979 const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance();
980 Nepomuk::Resource resource(item.nepomukUri());
981 QHashIterator<QByteArray, QVariant> it(rolesProvider.roleValues(resource, m_roles));
982 while (it.hasNext()) {
983 it.next();
984 data.insert(it.key(), it.value());
985 }
986
987 QUrl uri = resource.resourceUri();
988 if (uri.isEmpty()) {
989 // TODO: Is there another way to explicitly create a resource?
990 // We need a resource to be able to track it for changes.
991 resource.setRating(0);
992 uri = resource.resourceUri();
993 }
994 if (!uri.isEmpty() && !m_nepomukUriItems.contains(uri)) {
995 // TODO: Calling stop()/start() is a workaround until
996 // ResourceWatcher has been fixed.
997 m_nepomukResourceWatcher->stop();
998 m_nepomukResourceWatcher->addResource(resource);
999 m_nepomukResourceWatcher->start();
1000
1001 m_nepomukUriItems.insert(uri, item.url());
1002 }
1003 }
1004 #endif
1005
1006 return data;
1007 }
1008
1009 KFileItemList KFileItemModelRolesUpdater::sortedItems(const QSet<KFileItem>& items) const
1010 {
1011 KFileItemList itemList;
1012 if (items.isEmpty()) {
1013 return itemList;
1014 }
1015
1016 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
1017 QElapsedTimer timer;
1018 timer.start();
1019 #endif
1020
1021 QList<int> indexes;
1022 indexes.reserve(items.count());
1023
1024 QSetIterator<KFileItem> it(items);
1025 while (it.hasNext()) {
1026 const KFileItem item = it.next();
1027 const int index = m_model->index(item);
1028 if (index >= 0) {
1029 indexes.append(index);
1030 }
1031 }
1032 qSort(indexes);
1033
1034 itemList.reserve(items.count());
1035 foreach (int index, indexes) {
1036 itemList.append(m_model->fileItem(index));
1037 }
1038
1039 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
1040 kDebug() << "[TIME] Sorting of items:" << timer.elapsed();
1041 #endif
1042 return itemList;
1043 }
1044
1045 int KFileItemModelRolesUpdater::subItemsCount(const QString& path) const
1046 {
1047 const bool countHiddenFiles = m_model->showHiddenFiles();
1048 const bool showFoldersOnly = m_model->showDirectoriesOnly();
1049
1050 #ifdef Q_WS_WIN
1051 QDir dir(path);
1052 QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System;
1053 if (countHiddenFiles) {
1054 filters |= QDir::Hidden;
1055 }
1056 if (showFoldersOnly) {
1057 filters |= QDir::Dirs;
1058 } else {
1059 filters |= QDir::AllEntries;
1060 }
1061 return dir.entryList(filters).count();
1062 #else
1063 // Taken from kdelibs/kio/kio/kdirmodel.cpp
1064 // Copyright (C) 2006 David Faure <faure@kde.org>
1065
1066 int count = -1;
1067 DIR* dir = ::opendir(QFile::encodeName(path));
1068 if (dir) {
1069 count = 0;
1070 struct dirent *dirEntry = 0;
1071 while ((dirEntry = ::readdir(dir))) { // krazy:exclude=syscalls
1072 if (dirEntry->d_name[0] == '.') {
1073 if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) {
1074 // Skip "." or hidden files
1075 continue;
1076 }
1077 if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') {
1078 // Skip ".."
1079 continue;
1080 }
1081 }
1082
1083 // If only directories are counted, consider an unknown file type also
1084 // as directory instead of trying to do an expensive stat() (see bug 292642).
1085 if (!showFoldersOnly || dirEntry->d_type == DT_DIR || dirEntry->d_type == DT_UNKNOWN) {
1086 ++count;
1087 }
1088 }
1089 ::closedir(dir);
1090 }
1091 return count;
1092 #endif
1093 }
1094
1095 void KFileItemModelRolesUpdater::updateAllPreviews()
1096 {
1097 if (m_paused) {
1098 m_previewChangedDuringPausing = true;
1099 } else {
1100 sortAndResolveAllRoles();
1101 }
1102 }
1103
1104 #include "kfileitemmodelrolesupdater.moc"