/* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "moc_documentfactory.cpp" // Qt #include #include #include #include // KDE #include #include // Local #include namespace Gwenview { inline int getMaxUnreferencedImages() { int defaultValue = 3; QByteArray ba = qgetenv("GV_MAX_UNREFERENCED_IMAGES"); if (ba.isEmpty()) { return defaultValue; } kDebug() << "Custom value for max unreferenced images:" << ba; bool ok; int value = ba.toInt(&ok); return ok ? value : defaultValue; } static const int MAX_UNREFERENCED_IMAGES = getMaxUnreferencedImages(); /** * This internal structure holds the document and the last time it has been * accessed. This access time is used to "garbage collect" the loaded * documents. */ struct DocumentInfo { Document::Ptr mDocument; QDateTime mLastAccess; }; /** * Our collection of DocumentInfo instances. We keep them as pointers to avoid * altering DocumentInfo::mDocument refcount, since we rely on it to garbage * collect documents. */ typedef QMap DocumentMap; struct DocumentFactoryPrivate { DocumentMap mDocumentMap; QUndoGroup mUndoGroup; /** * Removes items in a map if they are no longer referenced elsewhere */ void garbageCollect(DocumentMap& map) { // Build a map of all unreferenced images. We use a MultiMap because in // rare cases documents may get accessed at the same millisecond. // See https://bugs.kde.org/show_bug.cgi?id=296401 typedef QMultiMap UnreferencedImages; UnreferencedImages unreferencedImages; DocumentMap::Iterator it = map.begin(), end = map.end(); for (; it != end; ++it) { DocumentInfo* info = it.value(); if (info->mDocument.count() == 1 && !info->mDocument->isModified()) { unreferencedImages.insert(info->mLastAccess, it.key()); } } // Remove oldest unreferenced images. Since the map is sorted by key, // the oldest one is always unreferencedImages.begin(). for ( UnreferencedImages::Iterator unreferencedIt = unreferencedImages.begin(); unreferencedImages.count() > MAX_UNREFERENCED_IMAGES; unreferencedIt = unreferencedImages.erase(unreferencedIt)) { KUrl url = unreferencedIt.value(); kDebug() << "Collecting" << url; it = map.find(url); Q_ASSERT(it != map.end()); delete it.value(); map.erase(it); } logDocumentMap(map); } void logDocumentMap(const DocumentMap& map) { kDebug() << "map:"; DocumentMap::ConstIterator it = map.constBegin(), end = map.constEnd(); for (; it != end; ++it) { kDebug() << "-" << it.key() << "refCount=" << it.value()->mDocument.count() << "lastAccess=" << it.value()->mLastAccess; } } QList mModifiedDocumentList; }; DocumentFactory::DocumentFactory() : d(new DocumentFactoryPrivate) { } DocumentFactory::~DocumentFactory() { qDeleteAll(d->mDocumentMap); delete d; } DocumentFactory* DocumentFactory::instance() { static DocumentFactory factory; return &factory; } Document::Ptr DocumentFactory::getCachedDocument(const KUrl& url) const { const DocumentInfo* info = d->mDocumentMap.value(url); return info ? info->mDocument : Document::Ptr(); } Document::Ptr DocumentFactory::load(const KUrl& url) { GV_RETURN_VALUE_IF_FAIL(!url.isEmpty(), Document::Ptr()); DocumentInfo* info = 0; DocumentMap::Iterator it = d->mDocumentMap.find(url); if (it != d->mDocumentMap.end()) { kDebug() << url.fileName() << "url in mDocumentMap"; info = it.value(); info->mLastAccess = QDateTime::currentDateTime(); return info->mDocument; } // At this point we couldn't find the document in the map // Start loading the document kDebug() << url.fileName() << "loading"; Document* doc = new Document(url); connect(doc, SIGNAL(loaded(KUrl)), SLOT(slotLoaded(KUrl))); connect(doc, SIGNAL(saved(KUrl,KUrl)), SLOT(slotSaved(KUrl,KUrl))); connect(doc, SIGNAL(modified(KUrl)), SLOT(slotModified(KUrl))); connect(doc, SIGNAL(busyChanged(KUrl,bool)), SLOT(slotBusyChanged(KUrl,bool))); // Create DocumentInfo instance info = new DocumentInfo; Document::Ptr docPtr(doc); info->mDocument = docPtr; info->mLastAccess = QDateTime::currentDateTime(); // Place DocumentInfo in the map d->mDocumentMap[url] = info; d->garbageCollect(d->mDocumentMap); return docPtr; } QList DocumentFactory::modifiedDocumentList() const { return d->mModifiedDocumentList; } bool DocumentFactory::hasUrl(const KUrl& url) const { return d->mDocumentMap.contains(url); } void DocumentFactory::clearCache() { qDeleteAll(d->mDocumentMap); d->mDocumentMap.clear(); d->mModifiedDocumentList.clear(); } void DocumentFactory::slotLoaded(const KUrl& url) { if (d->mModifiedDocumentList.contains(url)) { d->mModifiedDocumentList.removeAll(url); emit modifiedDocumentListChanged(); emit documentChanged(url); } } void DocumentFactory::slotSaved(const KUrl& oldUrl, const KUrl& newUrl) { bool oldIsNew = oldUrl == newUrl; bool oldUrlWasModified = d->mModifiedDocumentList.removeOne(oldUrl); bool newUrlWasModified = false; if (!oldIsNew) { newUrlWasModified = d->mModifiedDocumentList.removeOne(newUrl); DocumentInfo* info = d->mDocumentMap.take(oldUrl); d->mDocumentMap.insert(newUrl, info); } d->garbageCollect(d->mDocumentMap); if (oldUrlWasModified || newUrlWasModified) { emit modifiedDocumentListChanged(); } if (oldUrlWasModified) { emit documentChanged(oldUrl); } if (!oldIsNew) { emit documentChanged(newUrl); } } void DocumentFactory::slotModified(const KUrl& url) { if (!d->mModifiedDocumentList.contains(url)) { d->mModifiedDocumentList << url; emit modifiedDocumentListChanged(); } emit documentChanged(url); } void DocumentFactory::slotBusyChanged(const KUrl& url, bool busy) { emit documentBusyStateChanged(url, busy); } QUndoGroup* DocumentFactory::undoGroup() { return &d->mUndoGroup; } void DocumentFactory::forget(const KUrl& url) { DocumentInfo* info = d->mDocumentMap.take(url); if (!info) { return; } delete info; if (d->mModifiedDocumentList.contains(url)) { d->mModifiedDocumentList.removeAll(url); emit modifiedDocumentListChanged(); } } } // namespace