1 /*
2     Copyright (C) 2017 Volker Krause <vkrause@kde.org>
3 
4     Permission is hereby granted, free of charge, to any person obtaining
5     a copy of this software and associated documentation files (the
6     "Software"), to deal in the Software without restriction, including
7     without limitation the rights to use, copy, modify, merge, publish,
8     distribute, sublicense, and/or sell copies of the Software, and to
9     permit persons to whom the Software is furnished to do so, subject to
10     the following conditions:
11 
12     The above copyright notice and this permission notice shall be included
13     in all copies or substantial portions of the Software.
14 
15     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18     IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19     CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20     TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21     SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23 
24 #include "auditloguicontroller.h"
25 
26 #include <provider.h>
27 
28 #include <QAbstractListModel>
29 #include <QDateTime>
30 #include <QDebug>
31 #include <QDir>
32 #include <QJsonArray>
33 #include <QJsonDocument>
34 #include <QJsonObject>
35 #include <QLocale>
36 #include <QMetaEnum>
37 #include <QStandardPaths>
38 
39 #include <algorithm>
40 #include <vector>
41 
42 using namespace KUserFeedback;
43 
44 namespace KUserFeedback {
45 class AuditLogEntryModel : public QAbstractListModel
46 {
47     Q_OBJECT
48 public:
49     explicit AuditLogEntryModel(const QString &path, QObject *parent);
50 
51     void reload();
52 
53     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
54     QVariant data(const QModelIndex &index, int role) const override;
55 
56     QHash<int, QByteArray> roleNames() const override;
57 
58 private:
59     QString m_path;
60     std::vector<QDateTime> m_entries;
61 };
62 
63 
64 class AuditLogUiControllerPrivate
65 {
66 public:
67     QString path;
68     AuditLogEntryModel *logEntryModel;
69 };
70 }
71 
72 
AuditLogEntryModel(const QString & path,QObject * parent)73 AuditLogEntryModel::AuditLogEntryModel(const QString &path, QObject *parent)
74     : QAbstractListModel(parent)
75     , m_path(path)
76 {
77     reload();
78 }
79 
reload()80 void AuditLogEntryModel::reload()
81 {
82     beginResetModel();
83     m_entries.clear();
84 
85     foreach (auto e, QDir(m_path).entryList(QDir::Files | QDir::Readable)) {
86         if (!e.endsWith(QLatin1String(".log")))
87             continue;
88         e.chop(4);
89         const auto dt = QDateTime::fromString(e, QStringLiteral("yyyyMMdd-hhmmss"));
90         if (dt.isValid())
91             m_entries.push_back(dt);
92     }
93     std::sort(m_entries.begin(), m_entries.end(), [](const QDateTime &lhs, const QDateTime &rhs) {
94         return lhs > rhs;
95     });
96     endResetModel();
97 }
98 
rowCount(const QModelIndex & parent) const99 int AuditLogEntryModel::rowCount(const QModelIndex &parent) const
100 {
101     if (parent.isValid())
102         return 0;
103     return m_entries.size();
104 }
105 
data(const QModelIndex & index,int role) const106 QVariant AuditLogEntryModel::data(const QModelIndex &index, int role) const
107 {
108     switch (role) {
109         case Qt::DisplayRole:
110             return QLocale().toString(m_entries[index.row()]);
111         case Qt::UserRole:
112             return m_entries[index.row()];
113     }
114     return QVariant();
115 }
116 
roleNames() const117 QHash<int, QByteArray> AuditLogEntryModel::roleNames() const
118 {
119     QHash<int, QByteArray> roles;
120     roles.insert(Qt::DisplayRole, "display");
121     roles.insert(Qt::UserRole, "data");
122     return roles;
123 }
124 
125 
AuditLogUiController(QObject * parent)126 AuditLogUiController::AuditLogUiController(QObject* parent)
127     : QObject(parent)
128     , d(new AuditLogUiControllerPrivate)
129 {
130     d->path = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QStringLiteral("/kuserfeedback/audit/");
131     d->logEntryModel = new AuditLogEntryModel(d->path, this);
132 
133     connect(d->logEntryModel, &QAbstractItemModel::modelReset, this, &AuditLogUiController::logEntryCountChanged);
134 }
135 
~AuditLogUiController()136 AuditLogUiController::~AuditLogUiController()
137 {
138 }
139 
hasLogEntries() const140 bool AuditLogUiController::hasLogEntries() const
141 {
142     return d->logEntryModel->rowCount() != 0;
143 }
144 
logEntryModel() const145 QAbstractItemModel* AuditLogUiController::logEntryModel() const
146 {
147     return d->logEntryModel;
148 }
149 
telemetryModeString(Provider::TelemetryMode mode)150 static QString telemetryModeString(Provider::TelemetryMode mode)
151 {
152     switch (mode) {
153         case Provider::NoTelemetry:
154             Q_ASSERT(false);
155             return QString();
156         case Provider::BasicSystemInformation:
157             return AuditLogUiController::tr("Basic System Information");
158         case Provider::BasicUsageStatistics:
159             return AuditLogUiController::tr("Basic Usage Statistics");
160         case Provider::DetailedSystemInformation:
161             return AuditLogUiController::tr("Detailed System Information");
162         case Provider::DetailedUsageStatistics:
163             return AuditLogUiController::tr("Detailed Usage Statistics");
164     }
165     Q_UNREACHABLE();
166 }
167 
logEntry(const QDateTime & dt) const168 QString AuditLogUiController::logEntry(const QDateTime &dt) const
169 {
170     const QString fn = d->path + dt.toString(QStringLiteral("yyyyMMdd-hhmmss")) + QStringLiteral(".log");
171     QFile file(fn);
172     if (!file.open(QFile::ReadOnly))
173         return tr("Unable to open file %1: %2.").arg(fn, file.errorString());
174 
175     const auto doc = QJsonDocument::fromJson(file.readAll());
176     const auto topObj = doc.object();
177     struct Entry {
178         QString key;
179         QString desc;
180         QString rawData;
181         Provider::TelemetryMode mode;
182     };
183     std::vector<Entry> entries;
184     entries.reserve(topObj.size());
185 
186     const auto idx = Provider::staticMetaObject.indexOfEnumerator("TelemetryMode");
187     Q_ASSERT(idx >= 0);
188     const auto modeEnum = Provider::staticMetaObject.enumerator(idx);
189 
190     for (auto it = topObj.begin(); it != topObj.end(); ++it) {
191         Entry e;
192         e.key = it.key();
193         const auto obj = it.value().toObject();
194         e.desc = obj.value(QLatin1String("description")).toString();
195         const auto data = obj.value(QLatin1String("data"));
196         if (data.isObject())
197             e.rawData = QString::fromUtf8(QJsonDocument(data.toObject()).toJson());
198         else if (data.isArray())
199             e.rawData = QString::fromUtf8(QJsonDocument(data.toArray()).toJson());
200         e.mode = static_cast<Provider::TelemetryMode>(modeEnum.keyToValue(obj.value(QLatin1String("telemetryMode")).toString().toUtf8().constData()));
201         entries.push_back(e);
202     }
203 
204     std::sort(entries.begin(), entries.end(), [](const Entry &lhs, const Entry &rhs) -> bool {
205         if (lhs.mode == rhs.mode)
206             return lhs.key < rhs.key;
207         return lhs.mode < rhs.mode;
208     });
209 
210     QString res;
211     for (auto it = entries.begin(); it != entries.end(); ++it) {
212         res += QStringLiteral("<b>") + (*it).desc + QStringLiteral("</b><br/>");
213         res += tr("Category: <i>%1</i><br/>").arg(telemetryModeString((*it).mode));
214         res += tr("Key: <i>%1</i><br/>").arg((*it).key);
215         res += tr("Submitted data: <tt>%1</tt><br/><br/>").arg((*it).rawData);
216     }
217     return res;
218 }
219 
clear()220 void AuditLogUiController::clear()
221 {
222     QDir dir(d->path);
223     foreach (auto e, dir.entryList(QDir::Files | QDir::Readable)) {
224         if (!e.endsWith(QLatin1String(".log")))
225             continue;
226         dir.remove(e);
227     }
228 
229     d->logEntryModel->reload();
230 }
231 
232 #include "auditloguicontroller.moc"
233