KPublicTransport

locationhistorymodel.cpp
1/*
2 SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "locationhistorymodel.h"
8#include "logging.h"
9
10#include <QDirIterator>
11#include <QJsonDocument>
12#include <QJsonObject>
13#include <QStandardPaths>
14
15using namespace KPublicTransport;
16using namespace Qt::Literals;
17
18static QString basePath()
19{
20#ifdef Q_OS_ANDROID
21 constexpr auto dataLoc = QStandardPaths::AppDataLocation;
22#else
23 constexpr auto dataLoc = QStandardPaths::GenericDataLocation;
24#endif
25 return QStandardPaths::writableLocation(dataLoc) + "/org.kde.kpublictransport/location-history/"_L1;
26}
27
28LocationHistoryModel::LocationHistoryModel(QObject *parent)
29 : QAbstractListModel(parent)
30{
31 rescan();
32}
33
34LocationHistoryModel::~LocationHistoryModel() = default;
35
36int LocationHistoryModel::rowCount(const QModelIndex &parent) const
37{
38 if (parent.isValid()) {
39 return 0;
40 }
41 return (int)m_locations.size();
42}
43
44QVariant LocationHistoryModel::data(const QModelIndex &index, int role) const
45{
46 if (!checkIndex(index)) {
47 return {};
48 }
49
50 switch (role) {
51 case LocationRole: return m_locations[index.row()].loc;
52 case LocationNameRole: return m_locations[index.row()].loc.name();
53 case LastUsedRole: return m_locations[index.row()].lastUse;
54 case UseCountRole: return m_locations[index.row()].useCount;
55 case IsRemovableRole: return m_locations[index.row()].removable;
56 }
57
58 return {};
59}
60
61QHash<int, QByteArray> LocationHistoryModel::roleNames() const
62{
64 r.insert(LocationRole, "location");
65 r.insert(LocationNameRole, "locationName");
66 r.insert(LastUsedRole, "lastUsed");
67 r.insert(UseCountRole, "useCount");
68 r.insert(IsRemovableRole, "removable");
69 return r;
70}
71
72bool LocationHistoryModel::removeRow(int row, const QModelIndex& parent)
73{
75}
76
77bool LocationHistoryModel::removeRows(int row, int count, const QModelIndex &parent)
78{
79 if (parent.isValid()) {
80 return false;
81 }
82
83 const auto path = basePath();
84 beginRemoveRows({}, row, row + count - 1);
85 for (int i = row; i < row + count; ++i) {
86 QFile::remove(path + m_locations[i].id);
87 }
88 m_locations.erase(m_locations.begin() + row, m_locations.begin() + row + count);
90 return true;
91}
92
94{
95 for (auto it = m_locations.begin(); it != m_locations.end(); ++it) {
96 if (Location::isSame((*it).loc, loc)) {
97 (*it).loc = Location::merge((*it).loc, loc);
98 (*it).lastUse = QDateTime::currentDateTime();
99 (*it).useCount++;
100 store(*it);
101 const auto idx = index((int)std::distance(m_locations.begin(), it));
102 Q_EMIT dataChanged(idx, idx);
103 return;
104 }
105 }
106
107 Data data;
109 data.loc = loc;
110 data.lastUse = QDateTime::currentDateTime();
111 data.useCount = 1;
112 store(data);
113
114 beginInsertRows({}, (int)m_locations.size(), (int)m_locations.size());
115 m_locations.push_back(std::move(data));
117}
118
119void LocationHistoryModel::addPresetLocation(const Location &loc, const QDateTime &lastUse, int useCount)
120{
121 for (auto it = m_locations.begin(); it != m_locations.end(); ++it) {
122 if (Location::isSame((*it).loc, loc)) {
123 (*it).loc = Location::merge((*it).loc, loc);
124 (*it).lastUse = std::max((*it).lastUse, lastUse);
125 (*it).useCount += useCount;
126 (*it).removable = false;
127 const auto idx = index((int)std::distance(m_locations.begin(), it));
128 Q_EMIT dataChanged(idx, idx);
129 return;
130 }
131 }
132
133 Data data;
134 data.loc = loc;
135 data.lastUse = lastUse;
136 data.useCount = useCount;
137 data.removable = false;
138 store(data);
139
140 beginInsertRows({}, (int)m_locations.size(), (int)m_locations.size());
141 m_locations.push_back(std::move(data));
143}
144
146{
148 m_locations.clear();
150 rescan();
151}
152
154{
156 const auto path = basePath();
157 for (const auto &data : m_locations) {
158 QFile::remove(path + data.id);
159 }
160 m_locations.clear();
162}
163
164void LocationHistoryModel::rescan()
165{
167 for(QDirIterator it(basePath(), QDir::Files); it.hasNext();) {
168 QFile f(it.next());
169 if (!f.open(QFile::ReadOnly)) {
170 qCWarning(Log) << "Unable to read history entry:" << f.fileName() << f.errorString();
171 continue;
172 }
173
174 const auto doc = QJsonDocument::fromJson(f.readAll());
175 const auto obj = doc.object();
176 Data data;
177 data.id = it.fileInfo().baseName();
178 data.loc = Location::fromJson(obj.value("location"_L1).toObject());
179 data.lastUse = QDateTime::fromString(obj.value("lastUse"_L1).toString(), Qt::ISODate);
180 data.useCount = obj.value("useCount"_L1).toInt();
181 m_locations.push_back(std::move(data));
182 }
184}
185
186void LocationHistoryModel::store(const LocationHistoryModel::Data &data)
187{
188 if (data.id.isEmpty()) {
189 return;
190 }
191
192 const auto path = basePath();
193 QDir().mkpath(path);
194
195 QFile f(path + data.id);
196 if (!f.open(QFile::WriteOnly)) {
197 qCWarning(Log) << "Unable to write history entry:" << f.fileName() << f.errorString();
198 return;
199 }
200
201 QJsonObject obj;
202 obj.insert("location"_L1, Location::toJson(data.loc));
203 obj.insert("lastUse"_L1, data.lastUse.toString(Qt::ISODate));
204 obj.insert("useCount"_L1, data.useCount);
205 f.write(QJsonDocument(obj).toJson(QJsonDocument::Compact));
206}
void clearPresetLocations()
Remove all preset locations, only keeping the history locations.
void addPresetLocation(const KPublicTransport::Location &loc, const QDateTime &lastUse, int useCount)
Add a preset location.
void clear()
Delete the entire history content.
void addLocation(const KPublicTransport::Location &loc)
Add a location to the history.
static Location fromJson(const QJsonObject &obj)
Deserialize a Location object from JSON.
Definition location.cpp:559
static QJsonObject toJson(const Location &loc)
Serializes one Location object to JSON.
Definition location.cpp:494
static Location merge(const Location &lhs, const Location &rhs)
Merge two departure instances.
Definition location.cpp:417
static bool isSame(const Location &lhs, const Location &rhs)
Checks if to instances refer to the same location (which does not necessarily mean they are exactly e...
Definition location.cpp:311
QString path(const QString &relativePath)
Query operations and data types for accessing realtime public transport information from online servi...
KEDUVOCDOCUMENT_EXPORT void rescan()
void beginInsertRows(const QModelIndex &parent, int first, int last)
void beginRemoveRows(const QModelIndex &parent, int first, int last)
bool checkIndex(const QModelIndex &index, CheckIndexOptions options) const const
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
bool removeRow(int row, const QModelIndex &parent)
virtual QHash< int, QByteArray > roleNames() const const
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
QDateTime currentDateTime()
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
QString toString(QStringView format, QCalendar cal) const const
bool mkpath(const QString &dirPath) const const
bool hasNext() const const
bool remove()
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
iterator insert(QLatin1StringView key, const QJsonValue &value)
int row() const const
Q_EMITQ_EMIT
QObject * parent() const const
QString writableLocation(StandardLocation type)
bool isEmpty() const const
QUuid createUuid()
QString toString(StringFormat mode) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:07:52 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.