File indexing completed on 2024-05-12 04:19:37
0001 /* 0002 Gwenview: an image viewer 0003 Copyright 2007 Aurélien Gâteau <agateau@kde.org> 0004 0005 This program is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU General Public License 0007 as published by the Free Software Foundation; either version 2 0008 of the License, or (at your option) any later version. 0009 0010 This program is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 GNU General Public License for more details. 0014 0015 You should have received a copy of the GNU General Public License 0016 along with this program; if not, write to the Free Software 0017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0018 0019 */ 0020 #include "documentfactory.h" 0021 0022 // Qt 0023 #include <QByteArray> 0024 #include <QDateTime> 0025 #include <QMap> 0026 #include <QUndoGroup> 0027 #include <QUrl> 0028 0029 // KF 0030 0031 // Local 0032 #include "gwenview_lib_debug.h" 0033 #include <gvdebug.h> 0034 0035 namespace Gwenview 0036 { 0037 #undef ENABLE_LOG 0038 #undef LOG 0039 // #define ENABLE_LOG 0040 #ifdef ENABLE_LOG 0041 #define LOG(x) qCDebug(GWENVIEW_LIB_LOG) << x 0042 #else 0043 #define LOG(x) ; 0044 #endif 0045 0046 inline int getMaxUnreferencedImages() 0047 { 0048 int defaultValue = 3; 0049 QByteArray ba = qgetenv("GV_MAX_UNREFERENCED_IMAGES"); 0050 if (ba.isEmpty()) { 0051 return defaultValue; 0052 } 0053 LOG("Custom value for max unreferenced images:" << ba); 0054 bool ok; 0055 const int value = ba.toInt(&ok); 0056 return ok ? value : defaultValue; 0057 } 0058 0059 static const int MAX_UNREFERENCED_IMAGES = getMaxUnreferencedImages(); 0060 0061 /** 0062 * This internal structure holds the document and the last time it has been 0063 * accessed. This access time is used to "garbage collect" the loaded 0064 * documents. 0065 */ 0066 struct DocumentInfo { 0067 Document::Ptr mDocument; 0068 QDateTime mLastAccess; 0069 }; 0070 0071 /** 0072 * Our collection of DocumentInfo instances. We keep them as pointers to avoid 0073 * altering DocumentInfo::mDocument refcount, since we rely on it to garbage 0074 * collect documents. 0075 */ 0076 using DocumentMap = QMap<QUrl, DocumentInfo *>; 0077 0078 struct DocumentFactoryPrivate { 0079 DocumentMap mDocumentMap; 0080 QUndoGroup mUndoGroup; 0081 0082 /** 0083 * Removes items in a map if they are no longer referenced elsewhere 0084 */ 0085 void garbageCollect(DocumentMap &map) 0086 { 0087 // Build a map of all unreferenced images. We use a MultiMap because in 0088 // rare cases documents may get accessed at the same millisecond. 0089 // See https://bugs.kde.org/show_bug.cgi?id=296401 0090 using UnreferencedImages = QMultiMap<QDateTime, QUrl>; 0091 UnreferencedImages unreferencedImages; 0092 0093 DocumentMap::Iterator it = map.begin(), end = map.end(); 0094 for (; it != end; ++it) { 0095 DocumentInfo *info = it.value(); 0096 if (info->mDocument->ref == 1 && !info->mDocument->isModified()) { 0097 unreferencedImages.insert(info->mLastAccess, it.key()); 0098 } 0099 } 0100 0101 // Remove oldest unreferenced images. Since the map is sorted by key, 0102 // the oldest one is always unreferencedImages.begin(). 0103 for (UnreferencedImages::Iterator unreferencedIt = unreferencedImages.begin(); unreferencedImages.count() > MAX_UNREFERENCED_IMAGES; 0104 unreferencedIt = unreferencedImages.erase(unreferencedIt)) { 0105 const QUrl url = unreferencedIt.value(); 0106 LOG("Collecting" << url); 0107 it = map.find(url); 0108 Q_ASSERT(it != map.end()); 0109 delete it.value(); 0110 map.erase(it); 0111 } 0112 0113 #ifdef ENABLE_LOG 0114 logDocumentMap(map); 0115 #endif 0116 } 0117 0118 void logDocumentMap(const DocumentMap &map) 0119 { 0120 LOG("map:"); 0121 DocumentMap::ConstIterator it = map.constBegin(), end = map.constEnd(); 0122 for (; it != end; ++it) { 0123 LOG("-" << it.key() << "refCount=" << it.value()->mDocument.count() << "lastAccess=" << it.value()->mLastAccess); 0124 } 0125 } 0126 0127 QList<QUrl> mModifiedDocumentList; 0128 }; 0129 0130 DocumentFactory::DocumentFactory() 0131 : d(new DocumentFactoryPrivate) 0132 { 0133 } 0134 0135 DocumentFactory::~DocumentFactory() 0136 { 0137 qDeleteAll(d->mDocumentMap); 0138 delete d; 0139 } 0140 0141 DocumentFactory *DocumentFactory::instance() 0142 { 0143 static DocumentFactory factory; 0144 return &factory; 0145 } 0146 0147 Document::Ptr DocumentFactory::getCachedDocument(const QUrl &url) const 0148 { 0149 const DocumentInfo *info = d->mDocumentMap.value(url); 0150 return info ? info->mDocument : Document::Ptr(); 0151 } 0152 0153 Document::Ptr DocumentFactory::load(const QUrl &url) 0154 { 0155 GV_RETURN_VALUE_IF_FAIL(!url.isEmpty(), Document::Ptr()); 0156 DocumentInfo *info = nullptr; 0157 0158 DocumentMap::Iterator it = d->mDocumentMap.find(url); 0159 0160 if (it != d->mDocumentMap.end()) { 0161 LOG(url.fileName() << "url in mDocumentMap"); 0162 info = it.value(); 0163 info->mLastAccess = QDateTime::currentDateTime(); 0164 return info->mDocument; 0165 } 0166 0167 // At this point we couldn't find the document in the map 0168 0169 // Start loading the document 0170 LOG(url.fileName() << "loading"); 0171 auto doc = new Document(url); 0172 connect(doc, &Document::loaded, this, &DocumentFactory::slotLoaded); 0173 connect(doc, &Document::saved, this, &DocumentFactory::slotSaved); 0174 connect(doc, &Document::modified, this, &DocumentFactory::slotModified); 0175 connect(doc, &Document::busyChanged, this, &DocumentFactory::slotBusyChanged); 0176 0177 // Make sure that an url passed as command line argument is loaded 0178 // and shown before a possibly long running dirlister on a slow 0179 // network device is started. So start the dirlister after url is 0180 // loaded or failed to load. 0181 connect(doc, &Document::loaded, [this, url]() { 0182 Q_EMIT readyForDirListerStart(url); 0183 }); 0184 connect(doc, &Document::loadingFailed, [this, url]() { 0185 Q_EMIT readyForDirListerStart(url); 0186 }); 0187 connect(doc, &Document::downSampledImageReady, [this, url]() { 0188 Q_EMIT readyForDirListerStart(url); 0189 }); 0190 0191 doc->reload(); 0192 0193 // Create DocumentInfo instance 0194 info = new DocumentInfo; 0195 Document::Ptr docPtr(doc); 0196 info->mDocument = docPtr; 0197 info->mLastAccess = QDateTime::currentDateTime(); 0198 0199 // Place DocumentInfo in the map 0200 d->mDocumentMap[url] = info; 0201 0202 d->garbageCollect(d->mDocumentMap); 0203 0204 return docPtr; 0205 } 0206 0207 QList<QUrl> DocumentFactory::modifiedDocumentList() const 0208 { 0209 return d->mModifiedDocumentList; 0210 } 0211 0212 bool DocumentFactory::hasUrl(const QUrl &url) const 0213 { 0214 return d->mDocumentMap.contains(url); 0215 } 0216 0217 void DocumentFactory::clearCache() 0218 { 0219 qDeleteAll(d->mDocumentMap); 0220 d->mDocumentMap.clear(); 0221 d->mModifiedDocumentList.clear(); 0222 } 0223 0224 void DocumentFactory::slotLoaded(const QUrl &url) 0225 { 0226 if (d->mModifiedDocumentList.contains(url)) { 0227 d->mModifiedDocumentList.removeAll(url); 0228 Q_EMIT modifiedDocumentListChanged(); 0229 Q_EMIT documentChanged(url); 0230 } 0231 } 0232 0233 void DocumentFactory::slotSaved(const QUrl &oldUrl, const QUrl &newUrl) 0234 { 0235 const bool oldIsNew = oldUrl == newUrl; 0236 const bool oldUrlWasModified = d->mModifiedDocumentList.removeOne(oldUrl); 0237 bool newUrlWasModified = false; 0238 if (!oldIsNew) { 0239 newUrlWasModified = d->mModifiedDocumentList.removeOne(newUrl); 0240 DocumentInfo *info = d->mDocumentMap.take(oldUrl); 0241 d->mDocumentMap.insert(newUrl, info); 0242 } 0243 d->garbageCollect(d->mDocumentMap); 0244 if (oldUrlWasModified || newUrlWasModified) { 0245 Q_EMIT modifiedDocumentListChanged(); 0246 } 0247 if (oldUrlWasModified) { 0248 Q_EMIT documentChanged(oldUrl); 0249 } 0250 if (!oldIsNew) { 0251 Q_EMIT documentChanged(newUrl); 0252 } 0253 } 0254 0255 void DocumentFactory::slotModified(const QUrl &url) 0256 { 0257 if (!d->mModifiedDocumentList.contains(url)) { 0258 d->mModifiedDocumentList << url; 0259 Q_EMIT modifiedDocumentListChanged(); 0260 } 0261 Q_EMIT documentChanged(url); 0262 } 0263 0264 void DocumentFactory::slotBusyChanged(const QUrl &url, bool busy) 0265 { 0266 Q_EMIT documentBusyStateChanged(url, busy); 0267 } 0268 0269 QUndoGroup *DocumentFactory::undoGroup() 0270 { 0271 return &d->mUndoGroup; 0272 } 0273 0274 void DocumentFactory::forget(const QUrl &url) 0275 { 0276 DocumentInfo *info = d->mDocumentMap.take(url); 0277 if (!info) { 0278 return; 0279 } 0280 delete info; 0281 0282 if (d->mModifiedDocumentList.contains(url)) { 0283 d->mModifiedDocumentList.removeAll(url); 0284 Q_EMIT modifiedDocumentListChanged(); 0285 } 0286 } 0287 0288 } // namespace 0289 0290 #include "moc_documentfactory.cpp"