File indexing completed on 2024-10-27 04:20:57

0001 /*
0002     SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "locationhistorymodel.h"
0008 #include "logging.h"
0009 
0010 #include <QDirIterator>
0011 #include <QJsonDocument>
0012 #include <QJsonObject>
0013 #include <QStandardPaths>
0014 
0015 using namespace KPublicTransport;
0016 
0017 static QString basePath()
0018 {
0019 #ifdef Q_OS_ANDROID
0020     constexpr auto dataLoc = QStandardPaths::AppDataLocation;
0021 #else
0022     constexpr auto dataLoc = QStandardPaths::GenericDataLocation;
0023 #endif
0024     return QStandardPaths::writableLocation(dataLoc) + QLatin1String("/org.kde.kpublictransport/location-history/");
0025 }
0026 
0027 LocationHistoryModel::LocationHistoryModel(QObject *parent)
0028     : QAbstractListModel(parent)
0029 {
0030     rescan();
0031 }
0032 
0033 LocationHistoryModel::~LocationHistoryModel() = default;
0034 
0035 int LocationHistoryModel::rowCount(const QModelIndex &parent) const
0036 {
0037     if (parent.isValid()) {
0038         return 0;
0039     }
0040     return m_locations.size();
0041 }
0042 
0043 QVariant LocationHistoryModel::data(const QModelIndex &index, int role) const
0044 {
0045     if (!checkIndex(index)) {
0046         return {};
0047     }
0048 
0049     switch (role) {
0050         case LocationRole: return m_locations[index.row()].loc;
0051         case LocationNameRole: return m_locations[index.row()].loc.name();
0052         case LastUsedRole: return m_locations[index.row()].lastUse;
0053         case UseCountRole: return m_locations[index.row()].useCount;
0054     }
0055 
0056     return {};
0057 }
0058 
0059 QHash<int, QByteArray> LocationHistoryModel::roleNames() const
0060 {
0061     auto r = QAbstractListModel::roleNames();
0062     r.insert(LocationRole, "location");
0063     r.insert(LocationNameRole, "locationName");
0064     r.insert(LastUsedRole, "lastUsed");
0065     r.insert(UseCountRole, "useCount");
0066     return r;
0067 }
0068 
0069 bool LocationHistoryModel::removeRow(int row, const QModelIndex& parent)
0070 {
0071     return QAbstractListModel::removeRow(row, parent);
0072 }
0073 
0074 bool LocationHistoryModel::removeRows(int row, int count, const QModelIndex &parent)
0075 {
0076     if (parent.isValid()) {
0077         return false;
0078     }
0079 
0080     const auto path = basePath();
0081     beginRemoveRows({}, row, row + count - 1);
0082     for (int i = row; i < row + count; ++i) {
0083         QFile::remove(path + m_locations[i].id);
0084     }
0085     m_locations.erase(m_locations.begin() + row, m_locations.begin() + row + count);
0086     endRemoveRows();
0087     return true;
0088 }
0089 
0090 void LocationHistoryModel::addLocation(const Location &loc)
0091 {
0092     for (auto it = m_locations.begin(); it != m_locations.end(); ++it) {
0093         if (Location::isSame((*it).loc, loc)) {
0094             (*it).loc = Location::merge((*it).loc, loc);
0095             (*it).lastUse = QDateTime::currentDateTime();
0096             (*it).useCount++;
0097             store(*it);
0098             const auto idx = index(std::distance(m_locations.begin(), it));
0099             Q_EMIT dataChanged(idx, idx);
0100             return;
0101         }
0102     }
0103 
0104     Data data;
0105     data.id = QUuid::createUuid().toString(QUuid::WithoutBraces);
0106     data.loc = loc;
0107     data.lastUse = QDateTime::currentDateTime();
0108     data.useCount = 1;
0109     store(data);
0110 
0111     beginInsertRows({}, m_locations.size(), m_locations.size());
0112     m_locations.push_back(std::move(data));
0113     endInsertRows();
0114 }
0115 
0116 void LocationHistoryModel::clear()
0117 {
0118     beginResetModel();
0119     const auto path = basePath();
0120     for (const auto &data : m_locations) {
0121         QFile::remove(path + data.id);
0122     }
0123     m_locations.clear();
0124     endResetModel();
0125 }
0126 
0127 void LocationHistoryModel::rescan()
0128 {
0129     beginResetModel();
0130     for(QDirIterator it(basePath(), QDir::Files); it.hasNext();) {
0131         QFile f(it.next());
0132         if (!f.open(QFile::ReadOnly)) {
0133             qCWarning(Log) << "Unable to read history entry:" << f.fileName() << f.errorString();
0134             continue;
0135         }
0136 
0137         const auto doc = QJsonDocument::fromJson(f.readAll());
0138         const auto obj = doc.object();
0139         Data data;
0140         data.id = it.fileInfo().baseName();
0141         data.loc = Location::fromJson(obj.value(QLatin1String("location")).toObject());
0142         data.lastUse = QDateTime::fromString(obj.value(QLatin1String("lastUse")).toString(), Qt::ISODate);
0143         data.useCount = obj.value(QLatin1String("useCount")).toInt();
0144         m_locations.push_back(std::move(data));
0145     }
0146     endResetModel();
0147 }
0148 
0149 void LocationHistoryModel::store(const LocationHistoryModel::Data &data) const
0150 {
0151     const auto path = basePath();
0152     QDir().mkpath(path);
0153 
0154     QFile f(path + data.id);
0155     if (!f.open(QFile::WriteOnly)) {
0156         qCWarning(Log) << "Unable to write history entry:" << f.fileName() << f.errorString();
0157         return;
0158     }
0159 
0160     QJsonObject obj;
0161     obj.insert(QLatin1String("location"), Location::toJson(data.loc));
0162     obj.insert(QLatin1String("lastUse"), data.lastUse.toString(Qt::ISODate));
0163     obj.insert(QLatin1String("useCount"), data.useCount);
0164     f.write(QJsonDocument(obj).toJson(QJsonDocument::Compact));
0165 }