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"