263 lines
7.6 KiB
C++
Raw Normal View History

2024-06-29 11:52:32 +06:00
// vim: set tabstop=4 shiftwidth=4 expandtab:
/*
Gwenview: an image viewer
Copyright 2009 Aurélien Gâteau <agateau@kde.org>
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, Cambridge, MA 02110-1301, USA.
*/
// Self
#include "historymodel.h"
// Qt
#include <QDir>
#include <QFile>
#include <QMimeDatabase>
#include <QRegularExpression>
#include <QTemporaryFile>
#include <QUrl>
// KF
#include <KConfig>
#include <KConfigGroup>
#include <KDirModel>
#include <KFileItem>
#include <KFilePlacesModel>
#include <KFormat>
#include <KLocalizedString>
// Local
#include "gwenview_lib_debug.h"
#include <lib/urlutils.h>
namespace Gwenview
{
struct HistoryItem : public QStandardItem {
void save() const
{
KConfig config(mConfigPath, KConfig::SimpleConfig);
KConfigGroup group(&config, "general");
group.writeEntry("url", mUrl.toString());
group.writeEntry("dateTime", mDateTime.toString(Qt::ISODate));
config.sync();
}
static HistoryItem *create(const QUrl &url, const QDateTime &dateTime, const QString &storageDir)
{
if (!QDir().mkpath(storageDir)) {
qCCritical(GWENVIEW_LIB_LOG) << "Could not create history dir" << storageDir;
return nullptr;
}
QTemporaryFile file(storageDir + QStringLiteral("/gvhistoryXXXXXXrc"));
file.setAutoRemove(false);
if (!file.open()) {
qCCritical(GWENVIEW_LIB_LOG) << "Could not create history file";
return nullptr;
}
auto item = new HistoryItem(url, dateTime, file.fileName());
item->save();
return item;
}
static HistoryItem *load(const QString &fileName)
{
KConfig config(fileName, KConfig::SimpleConfig);
KConfigGroup group(&config, "general");
const QUrl url(group.readEntry("url"));
if (!url.isValid()) {
qCCritical(GWENVIEW_LIB_LOG) << "Invalid url" << url;
return nullptr;
}
const QDateTime dateTime = QDateTime::fromString(group.readEntry("dateTime"), Qt::ISODate);
if (!dateTime.isValid()) {
qCCritical(GWENVIEW_LIB_LOG) << "Invalid dateTime" << dateTime;
return nullptr;
}
return new HistoryItem(url, dateTime, fileName);
}
QUrl url() const
{
return mUrl;
}
QDateTime dateTime() const
{
return mDateTime;
}
void setDateTime(const QDateTime &dateTime)
{
if (mDateTime != dateTime) {
mDateTime = dateTime;
save();
}
}
void unlink()
{
QFile::remove(mConfigPath);
}
private:
QUrl mUrl;
QDateTime mDateTime;
QString mConfigPath;
HistoryItem(const QUrl &url, const QDateTime &dateTime, const QString &configPath)
: mUrl(url)
, mDateTime(dateTime)
, mConfigPath(configPath)
{
QString text(mUrl.toDisplayString(QUrl::PreferLocalFile));
#ifdef Q_OS_UNIX
// shorten home directory, but avoid showing a cryptic "~/"
if (text.length() > QDir::homePath().length() + 1) {
text.replace(QRegularExpression(QLatin1Char('^') + QDir::homePath()), QStringLiteral("~"));
}
#endif
setText(text);
QMimeDatabase db;
const QString iconName = db.mimeTypeForUrl(mUrl).iconName();
setIcon(QIcon::fromTheme(iconName));
setData(mUrl, KFilePlacesModel::UrlRole);
const KFileItem fileItem(mUrl);
setData(QVariant(fileItem), KDirModel::FileItemRole);
const QString date = KFormat().formatRelativeDateTime(mDateTime, QLocale::LongFormat);
setData(i18n("Last visited: %1", date), Qt::ToolTipRole);
}
bool operator<(const QStandardItem &other) const override
{
return mDateTime > static_cast<const HistoryItem *>(&other)->mDateTime;
}
};
struct HistoryModelPrivate {
HistoryModel *q = nullptr;
QString mStorageDir;
int mMaxCount;
QMap<QUrl, HistoryItem *> mHistoryItemForUrl;
void load()
{
QDir dir(mStorageDir);
if (!dir.exists()) {
return;
}
const QStringList rcFilesList = dir.entryList(QStringList() << QStringLiteral("*rc"));
for (const QString &name : rcFilesList) {
HistoryItem *item = HistoryItem::load(dir.filePath(name));
if (!item) {
continue;
}
const QUrl itemUrl = item->url();
if (UrlUtils::urlIsFastLocalFile(itemUrl)) {
if (!QFile::exists(itemUrl.path())) {
qCDebug(GWENVIEW_LIB_LOG) << "Removing" << itemUrl.path() << "from recent folders. It does not exist anymore";
item->unlink();
delete item;
continue;
}
}
HistoryItem *existingItem = mHistoryItemForUrl.value(item->url());
if (existingItem) {
// We already know this url(!) update existing item dateTime
// and get rid of duplicate
if (existingItem->dateTime() < item->dateTime()) {
existingItem->setDateTime(item->dateTime());
}
item->unlink();
delete item;
} else {
mHistoryItemForUrl.insert(item->url(), item);
q->appendRow(item);
}
}
q->sort(0);
}
void garbageCollect()
{
while (q->rowCount() > mMaxCount) {
HistoryItem *item = static_cast<HistoryItem *>(q->takeRow(q->rowCount() - 1).at(0));
mHistoryItemForUrl.remove(item->url());
item->unlink();
delete item;
}
}
};
HistoryModel::HistoryModel(QObject *parent, const QString &storageDir, int maxCount)
: QStandardItemModel(parent)
, d(new HistoryModelPrivate)
{
d->q = this;
d->mStorageDir = storageDir;
d->mMaxCount = maxCount;
d->load();
}
HistoryModel::~HistoryModel()
{
delete d;
}
void HistoryModel::addUrl(const QUrl &url, const QDateTime &_dateTime)
{
const QDateTime dateTime = _dateTime.isValid() ? _dateTime : QDateTime::currentDateTime();
HistoryItem *historyItem = d->mHistoryItemForUrl.value(url);
if (historyItem) {
historyItem->setDateTime(dateTime);
sort(0);
} else {
historyItem = HistoryItem::create(url, dateTime, d->mStorageDir);
if (!historyItem) {
qCCritical(GWENVIEW_LIB_LOG) << "Could not save history for url" << url;
return;
}
d->mHistoryItemForUrl.insert(url, historyItem);
appendRow(historyItem);
sort(0);
d->garbageCollect();
}
}
bool HistoryModel::removeRows(int start, int count, const QModelIndex &parent)
{
Q_ASSERT(!parent.isValid());
for (int row = start + count - 1; row >= start; --row) {
auto historyItem = static_cast<HistoryItem *>(item(row, 0));
Q_ASSERT(historyItem);
d->mHistoryItemForUrl.remove(historyItem->url());
historyItem->unlink();
}
return QStandardItemModel::removeRows(start, count, parent);
}
} // namespace
#include "moc_historymodel.cpp"