]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodelrolesupdater.cpp
Remove workaround to start/stop the resource watcher
[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_nepomukResourceWatcher->stop();
327 m_nepomukUriItems.clear();
328 } else {
329 QList<Nepomuk::Resource> newResources;
330 const QList<Nepomuk::Resource> oldResources = m_nepomukResourceWatcher->resources();
331 foreach (const Nepomuk::Resource& resource, oldResources) {
332 const QUrl uri = resource.resourceUri();
333 const KUrl itemUrl = m_nepomukUriItems.value(uri);
334 if (m_model->index(itemUrl) >= 0) {
335 newResources.append(resource);
336 } else {
337 m_nepomukUriItems.remove(uri);
338 }
339 }
340 m_nepomukResourceWatcher->setResources(newResources);
341 if (newResources.isEmpty()) {
342 Q_ASSERT(m_nepomukUriItems.isEmpty());
343 m_nepomukResourceWatcher->stop();
344 }
345 }
346 }
347 #endif
348
349 m_firstVisibleIndex = 0;
350 m_lastVisibleIndex = -1;
351 if (!hasPendingRoles()) {
352 return;
353 }
354
355 if (m_model->count() == 0) {
356 // Most probably a directory change is done. Clear all pending items
357 // and also kill all ongoing preview-jobs.
358 resetPendingRoles();
359
360 m_changedItems.clear();
361 m_changedItemsTimer->stop();
362 } else {
363 // Remove all items from m_pendingVisibleItems and m_pendingInvisibleItems
364 // that are not part of the model anymore. The items from m_changedItems
365 // don't need to be handled here, removed items are just skipped in
366 // resolveChangedItems().
367 for (int i = 0; i <= 1; ++i) {
368 QSet<KFileItem>& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems;
369 QMutableSetIterator<KFileItem> it(pendingItems);
370 while (it.hasNext()) {
371 const KFileItem item = it.next();
372 if (m_model->index(item) < 0) {
373 pendingItems.remove(item);
374 }
375 }
376 }
377 }
378 }
379
380 void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRanges,
381 const QSet<QByteArray>& roles)
382 {
383 Q_UNUSED(roles);
384
385 if (m_changedItemsTimer->isActive()) {
386 // A call of slotItemsChanged() has been done recently. Postpone the resolving
387 // of the roles until the timer has exceeded.
388 foreach (const KItemRange& itemRange, itemRanges) {
389 int index = itemRange.index;
390 for (int count = itemRange.count; count > 0; --count) {
391 m_changedItems.insert(m_model->fileItem(index));
392 ++index;
393 }
394 }
395 } else {
396 // No call of slotItemsChanged() has been done recently, resolve the roles now.
397 startUpdating(itemRanges);
398 }
399 m_changedItemsTimer->start();
400 }
401
402 void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current,
403 const QByteArray& previous)
404 {
405 Q_UNUSED(current);
406 Q_UNUSED(previous);
407 updateSortProgress();
408 }
409
410 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPixmap& pixmap)
411 {
412 m_pendingVisibleItems.remove(item);
413 m_pendingInvisibleItems.remove(item);
414
415 const int index = m_model->index(item);
416 if (index < 0) {
417 return;
418 }
419
420 QPixmap scaledPixmap = pixmap;
421
422 const QString mimeType = item.mimetype();
423 const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
424 const QString mimeTypeGroup = mimeType.left(slashIndex);
425 if (mimeTypeGroup == QLatin1String("image")) {
426 if (m_enlargeSmallPreviews) {
427 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
428 } else {
429 // Assure that small previews don't get enlarged. Instead they
430 // should be shown centered within the frame.
431 const QSize contentSize = KPixmapModifier::sizeInsideFrame(m_iconSize);
432 const bool enlargingRequired = scaledPixmap.width() < contentSize.width() &&
433 scaledPixmap.height() < contentSize.height();
434 if (enlargingRequired) {
435 QSize frameSize = scaledPixmap.size();
436 frameSize.scale(m_iconSize, Qt::KeepAspectRatio);
437
438 QPixmap largeFrame(frameSize);
439 largeFrame.fill(Qt::transparent);
440
441 KPixmapModifier::applyFrame(largeFrame, frameSize);
442
443 QPainter painter(&largeFrame);
444 painter.drawPixmap((largeFrame.width() - scaledPixmap.width()) / 2,
445 (largeFrame.height() - scaledPixmap.height()) / 2,
446 scaledPixmap);
447 scaledPixmap = largeFrame;
448 } else {
449 // The image must be shrinked as it is too large to fit into
450 // the available icon size
451 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
452 }
453 }
454 } else {
455 KPixmapModifier::scale(scaledPixmap, m_iconSize);
456 }
457
458 QHash<QByteArray, QVariant> data = rolesData(item);
459 data.insert("iconPixmap", scaledPixmap);
460
461 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
462 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
463 m_model->setData(index, data);
464 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
465 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
466
467 applySortProgressToModel();
468 }
469
470 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item)
471 {
472 m_pendingVisibleItems.remove(item);
473 m_pendingInvisibleItems.remove(item);
474
475 const bool clearPreviews = m_clearPreviews;
476 m_clearPreviews = true;
477 applyResolvedRoles(item, ResolveAll);
478 m_clearPreviews = clearPreviews;
479
480 applySortProgressToModel();
481 }
482
483 void KFileItemModelRolesUpdater::slotPreviewJobFinished(KJob* job)
484 {
485 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
486 kDebug() << "Preview job finished. Pending visible:" << m_pendingVisibleItems.count() << "invisible:" << m_pendingInvisibleItems.count();
487 #endif
488
489 m_previewJobs.removeOne(job);
490 if (!m_previewJobs.isEmpty() || !hasPendingRoles()) {
491 return;
492 }
493
494 const KFileItemList visibleItems = sortedItems(m_pendingVisibleItems);
495 startPreviewJob(visibleItems + m_pendingInvisibleItems.toList());
496 }
497
498 void KFileItemModelRolesUpdater::resolveNextPendingRoles()
499 {
500 if (m_paused) {
501 return;
502 }
503
504 if (m_previewShown) {
505 // The preview has been turned on since the last run. Skip
506 // resolving further pending roles as this is done as soon
507 // as a preview has been received.
508 return;
509 }
510
511 int resolvedCount = 0;
512 bool changed = false;
513 for (int i = 0; i <= 1; ++i) {
514 QSet<KFileItem>& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems;
515 QSetIterator<KFileItem> it(pendingItems);
516 while (it.hasNext() && !changed && resolvedCount < MaxResolveItemsCount) {
517 const KFileItem item = it.next();
518 pendingItems.remove(item);
519 changed = applyResolvedRoles(item, ResolveAll);
520 ++resolvedCount;
521 }
522 }
523
524 if (hasPendingRoles()) {
525 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
526 } else {
527 m_clearPreviews = false;
528 }
529
530 applySortProgressToModel();
531
532 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
533 static int callCount = 0;
534 ++callCount;
535 if (callCount % 100 == 0) {
536 kDebug() << "Remaining visible roles to resolve:" << m_pendingVisibleItems.count()
537 << "invisible:" << m_pendingInvisibleItems.count();
538 }
539 #endif
540 }
541
542 void KFileItemModelRolesUpdater::resolveChangedItems()
543 {
544 if (m_changedItems.isEmpty()) {
545 return;
546 }
547
548 KItemRangeList itemRanges;
549
550 QSetIterator<KFileItem> it(m_changedItems);
551 while (it.hasNext()) {
552 const KFileItem& item = it.next();
553 const int index = m_model->index(item);
554 if (index >= 0) {
555 itemRanges.append(KItemRange(index, 1));
556 }
557 }
558 m_changedItems.clear();
559
560 startUpdating(itemRanges);
561 }
562
563 void KFileItemModelRolesUpdater::applyChangedNepomukRoles(const Nepomuk::Resource& resource)
564 {
565 #ifdef HAVE_NEPOMUK
566 const KUrl itemUrl = m_nepomukUriItems.value(resource.resourceUri());
567 const KFileItem item = m_model->fileItem(itemUrl);
568 QHash<QByteArray, QVariant> data = rolesData(item);
569
570 const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance();
571 QHashIterator<QByteArray, QVariant> it(rolesProvider.roleValues(resource, m_roles));
572 while (it.hasNext()) {
573 it.next();
574 data.insert(it.key(), it.value());
575 }
576
577 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
578 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
579 const int index = m_model->index(item);
580 m_model->setData(index, data);
581 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
582 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
583 #else
584 Q_UNUSED(resource);
585 #endif
586 }
587
588 void KFileItemModelRolesUpdater::startUpdating(const KItemRangeList& itemRanges)
589 {
590 // If no valid index range is given assume that all items are visible.
591 // A cleanup will be done later as soon as the index range has been set.
592 const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
593
594 if (hasValidIndexRange) {
595 // Move all current pending visible items that are not visible anymore
596 // to the pending invisible items.
597 QSetIterator<KFileItem> it(m_pendingVisibleItems);
598 while (it.hasNext()) {
599 const KFileItem item = it.next();
600 const int index = m_model->index(item);
601 if (index < m_firstVisibleIndex || index > m_lastVisibleIndex) {
602 m_pendingVisibleItems.remove(item);
603 m_pendingInvisibleItems.insert(item);
604 }
605 }
606 }
607
608 int rangesCount = 0;
609
610 foreach (const KItemRange& range, itemRanges) {
611 rangesCount += range.count;
612
613 // Add the inserted items to the pending visible and invisible items
614 const int lastIndex = range.index + range.count - 1;
615 for (int i = range.index; i <= lastIndex; ++i) {
616 const KFileItem item = m_model->fileItem(i);
617 if (!hasValidIndexRange || (i >= m_firstVisibleIndex && i <= m_lastVisibleIndex)) {
618 m_pendingVisibleItems.insert(item);
619 } else {
620 m_pendingInvisibleItems.insert(item);
621 }
622 }
623 }
624
625 resolvePendingRoles();
626 }
627
628 void KFileItemModelRolesUpdater::startPreviewJob(const KFileItemList& items)
629 {
630 if (items.isEmpty() || m_paused) {
631 return;
632 }
633
634 // PreviewJob internally caches items always with the size of
635 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
636 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
637 // do a downscaling anyhow because of the frame, so in this case only the provided
638 // cache sizes are requested.
639 const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128)
640 ? QSize(256, 256) : QSize(128, 128);
641
642 // KIO::filePreview() will request the MIME-type of all passed items, which (in the
643 // worst case) might block the application for several seconds. To prevent such
644 // a blocking the MIME-type of the items will determined until the MaxBlockTimeout
645 // has been reached and only those items will get passed. As soon as the MIME-type
646 // has been resolved once KIO::PreviewJob() can already access the resolved
647 // MIME-type in a fast way.
648 QElapsedTimer timer;
649 timer.start();
650 KFileItemList itemSubSet;
651 for (int i = 0; i < items.count(); ++i) {
652 KFileItem item = items.at(i);
653 item.determineMimeType();
654 itemSubSet.append(items.at(i));
655 if (timer.elapsed() > MaxBlockTimeout) {
656 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
657 kDebug() << "Maximum time of" << MaxBlockTimeout << "ms exceeded, creating only previews for"
658 << (i + 1) << "items," << (items.count() - (i + 1)) << "will be resolved later";
659 #endif
660 break;
661 }
662 }
663 KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins);
664 job->setIgnoreMaximumSize(items.first().isLocalFile());
665 if (job->ui()) {
666 job->ui()->setWindow(qApp->activeWindow());
667 }
668
669 connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)),
670 this, SLOT(slotGotPreview(KFileItem,QPixmap)));
671 connect(job, SIGNAL(failed(KFileItem)),
672 this, SLOT(slotPreviewFailed(KFileItem)));
673 connect(job, SIGNAL(finished(KJob*)),
674 this, SLOT(slotPreviewJobFinished(KJob*)));
675
676 m_previewJobs.append(job);
677 }
678
679
680 bool KFileItemModelRolesUpdater::hasPendingRoles() const
681 {
682 return !m_pendingVisibleItems.isEmpty() || !m_pendingInvisibleItems.isEmpty();
683 }
684
685 void KFileItemModelRolesUpdater::resolvePendingRoles()
686 {
687 int resolvedCount = 0;
688
689 bool hasSlowRoles = m_previewShown;
690 if (!hasSlowRoles) {
691 QSetIterator<QByteArray> it(m_roles);
692 while (it.hasNext()) {
693 if (m_resolvableRoles.contains(it.next())) {
694 hasSlowRoles = true;
695 break;
696 }
697 }
698 }
699
700 const ResolveHint resolveHint = hasSlowRoles ? ResolveFast : ResolveAll;
701
702 // Resolving the MIME type can be expensive. Assure that not more than MaxBlockTimeout ms are
703 // spend for resolving them synchronously. Usually this is more than enough to determine
704 // all visible items, but there are corner cases where this limit gets easily exceeded.
705 QElapsedTimer timer;
706 timer.start();
707
708 // Resolve the MIME type of all visible items
709 QSetIterator<KFileItem> visibleIt(m_pendingVisibleItems);
710 while (visibleIt.hasNext()) {
711 const KFileItem item = visibleIt.next();
712 if (!hasSlowRoles) {
713 Q_ASSERT(!m_pendingInvisibleItems.contains(item));
714 // All roles will be resolved by applyResolvedRoles()
715 m_pendingVisibleItems.remove(item);
716 }
717 applyResolvedRoles(item, resolveHint);
718 ++resolvedCount;
719
720 if (timer.elapsed() > MaxBlockTimeout) {
721 break;
722 }
723 }
724
725 // Resolve the MIME type of the invisible items at least until the timeout
726 // has been exceeded or the maximum number of items has been reached
727 KFileItemList invisibleItems;
728 if (m_lastVisibleIndex >= 0) {
729 // The visible range is valid, don't care about the order how the MIME
730 // type of invisible items get resolved
731 invisibleItems = m_pendingInvisibleItems.toList();
732 } else {
733 // The visible range is temporary invalid (e.g. happens when loading
734 // a directory) so take care to sort the currently invisible items where
735 // a part will get visible later
736 invisibleItems = sortedItems(m_pendingInvisibleItems);
737 }
738
739 int index = 0;
740 while (resolvedCount < MaxResolveItemsCount && index < invisibleItems.count() && timer.elapsed() <= MaxBlockTimeout) {
741 const KFileItem item = invisibleItems.at(index);
742 applyResolvedRoles(item, resolveHint);
743
744 if (!hasSlowRoles) {
745 // All roles have been resolved already by applyResolvedRoles()
746 m_pendingInvisibleItems.remove(item);
747 }
748 ++index;
749 ++resolvedCount;
750 }
751
752 if (m_previewShown) {
753 KFileItemList items = sortedItems(m_pendingVisibleItems);
754 items += invisibleItems;
755 startPreviewJob(items);
756 } else {
757 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
758 }
759
760 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
761 if (timer.elapsed() > MaxBlockTimeout) {
762 kDebug() << "Maximum time of" << MaxBlockTimeout
763 << "ms exceeded, skipping items... Remaining visible:" << m_pendingVisibleItems.count()
764 << "invisible:" << m_pendingInvisibleItems.count();
765 }
766 kDebug() << "[TIME] Resolved pending roles:" << timer.elapsed();
767 #endif
768
769 applySortProgressToModel();
770 }
771
772 void KFileItemModelRolesUpdater::resetPendingRoles()
773 {
774 m_pendingVisibleItems.clear();
775 m_pendingInvisibleItems.clear();
776
777 foreach (KJob* job, m_previewJobs) {
778 job->kill();
779 }
780 Q_ASSERT(m_previewJobs.isEmpty());
781 }
782
783 void KFileItemModelRolesUpdater::sortAndResolveAllRoles()
784 {
785 if (m_paused) {
786 return;
787 }
788
789 resetPendingRoles();
790 Q_ASSERT(m_pendingVisibleItems.isEmpty());
791 Q_ASSERT(m_pendingInvisibleItems.isEmpty());
792
793 if (m_model->count() == 0) {
794 return;
795 }
796
797 // Determine all visible items
798 Q_ASSERT(m_firstVisibleIndex >= 0);
799 for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
800 const KFileItem item = m_model->fileItem(i);
801 if (!item.isNull()) {
802 m_pendingVisibleItems.insert(item);
803 }
804 }
805
806 // Determine all invisible items
807 for (int i = 0; i < m_firstVisibleIndex; ++i) {
808 const KFileItem item = m_model->fileItem(i);
809 if (!item.isNull()) {
810 m_pendingInvisibleItems.insert(item);
811 }
812 }
813 for (int i = m_lastVisibleIndex + 1; i < m_model->count(); ++i) {
814 const KFileItem item = m_model->fileItem(i);
815 if (!item.isNull()) {
816 m_pendingInvisibleItems.insert(item);
817 }
818 }
819
820 resolvePendingRoles();
821 }
822
823 void KFileItemModelRolesUpdater::sortAndResolvePendingRoles()
824 {
825 Q_ASSERT(!m_paused);
826 if (m_model->count() == 0) {
827 return;
828 }
829
830 // If no valid index range is given assume that all items are visible.
831 // A cleanup will be done later as soon as the index range has been set.
832 const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
833
834 // Trigger a preview generation of all pending items. Assure that the visible
835 // pending items get generated first.
836 QSet<KFileItem> pendingItems;
837 pendingItems += m_pendingVisibleItems;
838 pendingItems += m_pendingInvisibleItems;
839
840 resetPendingRoles();
841 Q_ASSERT(m_pendingVisibleItems.isEmpty());
842 Q_ASSERT(m_pendingInvisibleItems.isEmpty());
843
844 QSetIterator<KFileItem> it(pendingItems);
845 while (it.hasNext()) {
846 const KFileItem item = it.next();
847 if (item.isNull()) {
848 continue;
849 }
850
851 const int index = m_model->index(item);
852 if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) {
853 m_pendingVisibleItems.insert(item);
854 } else {
855 m_pendingInvisibleItems.insert(item);
856 }
857 }
858
859 resolvePendingRoles();
860 }
861
862 void KFileItemModelRolesUpdater::applySortProgressToModel()
863 {
864 if (m_sortingProgress < 0) {
865 return;
866 }
867
868 // Inform the model about the progress of the resolved items,
869 // so that it can give an indication when the sorting has been finished.
870 const int resolvedCount = m_model->count()
871 - m_pendingVisibleItems.count()
872 - m_pendingInvisibleItems.count();
873 if (resolvedCount > 0) {
874 m_model->emitSortProgress(resolvedCount);
875 if (resolvedCount == m_model->count()) {
876 m_sortingProgress = -1;
877 }
878 }
879 }
880
881 void KFileItemModelRolesUpdater::updateSortProgress()
882 {
883 const QByteArray sortRole = m_model->sortRole();
884
885 // Optimization if the sorting is done by type: In case if all MIME-types
886 // are known, the types have been resolved already by KFileItemModel and
887 // no sort-progress feedback is required.
888 const bool showProgress = (sortRole == "type")
889 ? hasUnknownMimeTypes()
890 : m_resolvableRoles.contains(sortRole);
891
892 if (m_sortingProgress >= 0) {
893 // Mark the current sorting as finished
894 m_model->emitSortProgress(m_model->count());
895 }
896 m_sortingProgress = showProgress ? 0 : -1;
897 }
898
899 bool KFileItemModelRolesUpdater::hasUnknownMimeTypes() const
900 {
901 const int count = m_model->count();
902 for (int i = 0; i < count; ++i) {
903 const KFileItem item = m_model->fileItem(i);
904 if (!item.isMimeTypeKnown()) {
905 return true;
906 }
907 }
908
909 return false;
910 }
911
912 bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, ResolveHint hint)
913 {
914 if (item.isNull()) {
915 return false;
916 }
917
918 const bool resolveAll = (hint == ResolveAll);
919
920 bool mimeTypeChanged = false;
921 if (!item.isMimeTypeKnown()) {
922 item.determineMimeType();
923 mimeTypeChanged = true;
924 }
925
926 if (mimeTypeChanged || resolveAll || m_clearPreviews) {
927 const int index = m_model->index(item);
928 if (index < 0) {
929 return false;
930 }
931
932 QHash<QByteArray, QVariant> data;
933 if (resolveAll) {
934 data = rolesData(item);
935 }
936
937 data.insert("iconName", item.iconName());
938
939 if (m_clearPreviews) {
940 data.insert("iconPixmap", QPixmap());
941 }
942
943 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
944 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
945 m_model->setData(index, data);
946 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
947 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
948 return true;
949 }
950
951 return false;
952 }
953
954 QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem& item) const
955 {
956 QHash<QByteArray, QVariant> data;
957
958 const bool getSizeRole = m_roles.contains("size");
959 const bool getIsExpandableRole = m_roles.contains("isExpandable");
960
961 if ((getSizeRole || getIsExpandableRole) && item.isDir()) {
962 if (item.isLocalFile()) {
963 const QString path = item.localPath();
964 const int count = subItemsCount(path);
965 if (getSizeRole) {
966 data.insert("size", count);
967 }
968 if (getIsExpandableRole) {
969 data.insert("isExpandable", count > 0);
970 }
971 } else if (getSizeRole) {
972 data.insert("size", -1); // -1 indicates an unknown number of items
973 }
974 }
975
976 if (m_roles.contains("type")) {
977 data.insert("type", item.mimeComment());
978 }
979
980 data.insert("iconOverlays", item.overlays());
981
982 #ifdef HAVE_NEPOMUK
983 if (m_nepomukResourceWatcher) {
984 const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance();
985 Nepomuk::Resource resource(item.nepomukUri());
986 QHashIterator<QByteArray, QVariant> it(rolesProvider.roleValues(resource, m_roles));
987 while (it.hasNext()) {
988 it.next();
989 data.insert(it.key(), it.value());
990 }
991
992 QUrl uri = resource.resourceUri();
993 if (uri.isEmpty()) {
994 // TODO: Is there another way to explicitly create a resource?
995 // We need a resource to be able to track it for changes.
996 resource.setRating(0);
997 uri = resource.resourceUri();
998 }
999 if (!uri.isEmpty() && !m_nepomukUriItems.contains(uri)) {
1000 m_nepomukResourceWatcher->addResource(resource);
1001
1002 if (m_nepomukUriItems.isEmpty()) {
1003 m_nepomukResourceWatcher->start();
1004 }
1005
1006 m_nepomukUriItems.insert(uri, item.url());
1007 }
1008 }
1009 #endif
1010
1011 return data;
1012 }
1013
1014 KFileItemList KFileItemModelRolesUpdater::sortedItems(const QSet<KFileItem>& items) const
1015 {
1016 KFileItemList itemList;
1017 if (items.isEmpty()) {
1018 return itemList;
1019 }
1020
1021 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
1022 QElapsedTimer timer;
1023 timer.start();
1024 #endif
1025
1026 QList<int> indexes;
1027 indexes.reserve(items.count());
1028
1029 QSetIterator<KFileItem> it(items);
1030 while (it.hasNext()) {
1031 const KFileItem item = it.next();
1032 const int index = m_model->index(item);
1033 if (index >= 0) {
1034 indexes.append(index);
1035 }
1036 }
1037 qSort(indexes);
1038
1039 itemList.reserve(items.count());
1040 foreach (int index, indexes) {
1041 itemList.append(m_model->fileItem(index));
1042 }
1043
1044 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
1045 kDebug() << "[TIME] Sorting of items:" << timer.elapsed();
1046 #endif
1047 return itemList;
1048 }
1049
1050 int KFileItemModelRolesUpdater::subItemsCount(const QString& path) const
1051 {
1052 const bool countHiddenFiles = m_model->showHiddenFiles();
1053 const bool showFoldersOnly = m_model->showDirectoriesOnly();
1054
1055 #ifdef Q_WS_WIN
1056 QDir dir(path);
1057 QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System;
1058 if (countHiddenFiles) {
1059 filters |= QDir::Hidden;
1060 }
1061 if (showFoldersOnly) {
1062 filters |= QDir::Dirs;
1063 } else {
1064 filters |= QDir::AllEntries;
1065 }
1066 return dir.entryList(filters).count();
1067 #else
1068 // Taken from kdelibs/kio/kio/kdirmodel.cpp
1069 // Copyright (C) 2006 David Faure <faure@kde.org>
1070
1071 int count = -1;
1072 DIR* dir = ::opendir(QFile::encodeName(path));
1073 if (dir) {
1074 count = 0;
1075 struct dirent *dirEntry = 0;
1076 while ((dirEntry = ::readdir(dir))) { // krazy:exclude=syscalls
1077 if (dirEntry->d_name[0] == '.') {
1078 if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) {
1079 // Skip "." or hidden files
1080 continue;
1081 }
1082 if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') {
1083 // Skip ".."
1084 continue;
1085 }
1086 }
1087
1088 // If only directories are counted, consider an unknown file type also
1089 // as directory instead of trying to do an expensive stat() (see bug 292642).
1090 if (!showFoldersOnly || dirEntry->d_type == DT_DIR || dirEntry->d_type == DT_UNKNOWN) {
1091 ++count;
1092 }
1093 }
1094 ::closedir(dir);
1095 }
1096 return count;
1097 #endif
1098 }
1099
1100 void KFileItemModelRolesUpdater::updateAllPreviews()
1101 {
1102 if (m_paused) {
1103 m_previewChangedDuringPausing = true;
1104 } else {
1105 sortAndResolveAllRoles();
1106 }
1107 }
1108
1109 #include "kfileitemmodelrolesupdater.moc"