File indexing completed on 2024-04-28 04:18:51
0001 // vim: set tabstop=4 shiftwidth=4 expandtab: 0002 /* 0003 Gwenview: an image viewer 0004 Copyright 2009 Aurélien Gâteau <agateau@kde.org> 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License 0008 as published by the Free Software Foundation; either version 2 0009 of the License, or (at your option) any later version. 0010 0011 This program is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 GNU General Public License for more details. 0015 0016 You should have received a copy of the GNU General Public License 0017 along with this program; if not, write to the Free Software 0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. 0019 0020 */ 0021 // Self 0022 #include "historymodel.h" 0023 0024 // Qt 0025 #include <QDir> 0026 #include <QFile> 0027 #include <QMimeDatabase> 0028 #include <QRegularExpression> 0029 #include <QTemporaryFile> 0030 #include <QUrl> 0031 0032 // KF 0033 #include <KConfig> 0034 #include <KConfigGroup> 0035 #include <KDirModel> 0036 #include <KFileItem> 0037 #include <KFilePlacesModel> 0038 #include <KFormat> 0039 #include <KLocalizedString> 0040 0041 // Local 0042 #include "gwenview_lib_debug.h" 0043 #include <lib/urlutils.h> 0044 0045 namespace Gwenview 0046 { 0047 struct HistoryItem : public QStandardItem { 0048 void save() const 0049 { 0050 KConfig config(mConfigPath, KConfig::SimpleConfig); 0051 KConfigGroup group(&config, "general"); 0052 group.writeEntry("url", mUrl.toString()); 0053 group.writeEntry("dateTime", mDateTime.toString(Qt::ISODate)); 0054 config.sync(); 0055 } 0056 0057 static HistoryItem *create(const QUrl &url, const QDateTime &dateTime, const QString &storageDir) 0058 { 0059 if (!QDir().mkpath(storageDir)) { 0060 qCCritical(GWENVIEW_LIB_LOG) << "Could not create history dir" << storageDir; 0061 return nullptr; 0062 } 0063 QTemporaryFile file(storageDir + QStringLiteral("/gvhistoryXXXXXXrc")); 0064 file.setAutoRemove(false); 0065 if (!file.open()) { 0066 qCCritical(GWENVIEW_LIB_LOG) << "Could not create history file"; 0067 return nullptr; 0068 } 0069 0070 auto item = new HistoryItem(url, dateTime, file.fileName()); 0071 item->save(); 0072 return item; 0073 } 0074 0075 static HistoryItem *load(const QString &fileName) 0076 { 0077 KConfig config(fileName, KConfig::SimpleConfig); 0078 KConfigGroup group(&config, "general"); 0079 0080 const QUrl url(group.readEntry("url")); 0081 if (!url.isValid()) { 0082 qCCritical(GWENVIEW_LIB_LOG) << "Invalid url" << url; 0083 return nullptr; 0084 } 0085 const QDateTime dateTime = QDateTime::fromString(group.readEntry("dateTime"), Qt::ISODate); 0086 if (!dateTime.isValid()) { 0087 qCCritical(GWENVIEW_LIB_LOG) << "Invalid dateTime" << dateTime; 0088 return nullptr; 0089 } 0090 0091 return new HistoryItem(url, dateTime, fileName); 0092 } 0093 0094 QUrl url() const 0095 { 0096 return mUrl; 0097 } 0098 0099 QDateTime dateTime() const 0100 { 0101 return mDateTime; 0102 } 0103 0104 void setDateTime(const QDateTime &dateTime) 0105 { 0106 if (mDateTime != dateTime) { 0107 mDateTime = dateTime; 0108 save(); 0109 } 0110 } 0111 0112 void unlink() 0113 { 0114 QFile::remove(mConfigPath); 0115 } 0116 0117 private: 0118 QUrl mUrl; 0119 QDateTime mDateTime; 0120 QString mConfigPath; 0121 0122 HistoryItem(const QUrl &url, const QDateTime &dateTime, const QString &configPath) 0123 : mUrl(url) 0124 , mDateTime(dateTime) 0125 , mConfigPath(configPath) 0126 { 0127 QString text(mUrl.toDisplayString(QUrl::PreferLocalFile)); 0128 #ifdef Q_OS_UNIX 0129 // shorten home directory, but avoid showing a cryptic "~/" 0130 if (text.length() > QDir::homePath().length() + 1) { 0131 text.replace(QRegularExpression(QLatin1Char('^') + QDir::homePath()), QStringLiteral("~")); 0132 } 0133 #endif 0134 setText(text); 0135 0136 QMimeDatabase db; 0137 const QString iconName = db.mimeTypeForUrl(mUrl).iconName(); 0138 setIcon(QIcon::fromTheme(iconName)); 0139 0140 setData(mUrl, KFilePlacesModel::UrlRole); 0141 0142 const KFileItem fileItem(mUrl); 0143 setData(QVariant(fileItem), KDirModel::FileItemRole); 0144 0145 const QString date = KFormat().formatRelativeDateTime(mDateTime, QLocale::LongFormat); 0146 setData(i18n("Last visited: %1", date), Qt::ToolTipRole); 0147 } 0148 0149 bool operator<(const QStandardItem &other) const override 0150 { 0151 return mDateTime > static_cast<const HistoryItem *>(&other)->mDateTime; 0152 } 0153 }; 0154 0155 struct HistoryModelPrivate { 0156 HistoryModel *q = nullptr; 0157 QString mStorageDir; 0158 int mMaxCount; 0159 0160 QMap<QUrl, HistoryItem *> mHistoryItemForUrl; 0161 0162 void load() 0163 { 0164 QDir dir(mStorageDir); 0165 if (!dir.exists()) { 0166 return; 0167 } 0168 const QStringList rcFilesList = dir.entryList(QStringList() << QStringLiteral("*rc")); 0169 for (const QString &name : rcFilesList) { 0170 HistoryItem *item = HistoryItem::load(dir.filePath(name)); 0171 if (!item) { 0172 continue; 0173 } 0174 0175 const QUrl itemUrl = item->url(); 0176 if (UrlUtils::urlIsFastLocalFile(itemUrl)) { 0177 if (!QFile::exists(itemUrl.path())) { 0178 qCDebug(GWENVIEW_LIB_LOG) << "Removing" << itemUrl.path() << "from recent folders. It does not exist anymore"; 0179 item->unlink(); 0180 delete item; 0181 continue; 0182 } 0183 } 0184 0185 HistoryItem *existingItem = mHistoryItemForUrl.value(item->url()); 0186 if (existingItem) { 0187 // We already know this url(!) update existing item dateTime 0188 // and get rid of duplicate 0189 if (existingItem->dateTime() < item->dateTime()) { 0190 existingItem->setDateTime(item->dateTime()); 0191 } 0192 item->unlink(); 0193 delete item; 0194 } else { 0195 mHistoryItemForUrl.insert(item->url(), item); 0196 q->appendRow(item); 0197 } 0198 } 0199 q->sort(0); 0200 } 0201 0202 void garbageCollect() 0203 { 0204 while (q->rowCount() > mMaxCount) { 0205 HistoryItem *item = static_cast<HistoryItem *>(q->takeRow(q->rowCount() - 1).at(0)); 0206 mHistoryItemForUrl.remove(item->url()); 0207 item->unlink(); 0208 delete item; 0209 } 0210 } 0211 }; 0212 0213 HistoryModel::HistoryModel(QObject *parent, const QString &storageDir, int maxCount) 0214 : QStandardItemModel(parent) 0215 , d(new HistoryModelPrivate) 0216 { 0217 d->q = this; 0218 d->mStorageDir = storageDir; 0219 d->mMaxCount = maxCount; 0220 d->load(); 0221 } 0222 0223 HistoryModel::~HistoryModel() 0224 { 0225 delete d; 0226 } 0227 0228 void HistoryModel::addUrl(const QUrl &url, const QDateTime &_dateTime) 0229 { 0230 const QDateTime dateTime = _dateTime.isValid() ? _dateTime : QDateTime::currentDateTime(); 0231 HistoryItem *historyItem = d->mHistoryItemForUrl.value(url); 0232 if (historyItem) { 0233 historyItem->setDateTime(dateTime); 0234 sort(0); 0235 } else { 0236 historyItem = HistoryItem::create(url, dateTime, d->mStorageDir); 0237 if (!historyItem) { 0238 qCCritical(GWENVIEW_LIB_LOG) << "Could not save history for url" << url; 0239 return; 0240 } 0241 d->mHistoryItemForUrl.insert(url, historyItem); 0242 appendRow(historyItem); 0243 sort(0); 0244 d->garbageCollect(); 0245 } 0246 } 0247 0248 bool HistoryModel::removeRows(int start, int count, const QModelIndex &parent) 0249 { 0250 Q_ASSERT(!parent.isValid()); 0251 for (int row = start + count - 1; row >= start; --row) { 0252 auto historyItem = static_cast<HistoryItem *>(item(row, 0)); 0253 Q_ASSERT(historyItem); 0254 d->mHistoryItemForUrl.remove(historyItem->url()); 0255 historyItem->unlink(); 0256 } 0257 return QStandardItemModel::removeRows(start, count, parent); 0258 } 0259 0260 } // namespace 0261 0262 #include "moc_historymodel.cpp"