1 /*
2 * SPDX-FileCopyrightText: 2015-2016 Ivan Cukic <ivan.cukic@kde.org>
3 *
4 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6
7 #include "ResourcesDatabaseSchema.h"
8
9 #include <QCoreApplication>
10 #include <QStandardPaths>
11 #include <QVariant>
12
13 namespace Common
14 {
15 namespace ResourcesDatabaseSchema
16 {
17 const QString name = QStringLiteral("Resources");
18
version()19 QString version()
20 {
21 return QStringLiteral("2015.02.09");
22 }
23
schema()24 QStringList schema()
25 {
26 // If only we could use initializer lists here ...
27
28 return QStringList()
29
30 << // Schema information table, used for versioning
31 QStringLiteral(
32 "CREATE TABLE IF NOT EXISTS SchemaInfo ("
33 "key text PRIMARY KEY, value text"
34 ")")
35
36 << QStringLiteral("INSERT OR IGNORE INTO schemaInfo VALUES ('version', '%1')").arg(version())
37 << QStringLiteral("UPDATE schemaInfo SET value = '%1' WHERE key = 'version'").arg(version())
38
39 << // The ResourceEvent table saves the Opened/Closed event pairs for
40 // a resource. The Accessed event is mapped to those.
41 // Focusing events are not stored in order not to get a
42 // huge database file and to lessen writes to the disk.
43 QStringLiteral(
44 "CREATE TABLE IF NOT EXISTS ResourceEvent ("
45 "usedActivity TEXT, "
46 "initiatingAgent TEXT, "
47 "targettedResource TEXT, "
48 "start INTEGER, "
49 "end INTEGER "
50 ")")
51
52 << // The ResourceScoreCache table stores the calculated scores
53 // for resources based on the recorded events.
54 QStringLiteral(
55 "CREATE TABLE IF NOT EXISTS ResourceScoreCache ("
56 "usedActivity TEXT, "
57 "initiatingAgent TEXT, "
58 "targettedResource TEXT, "
59 "scoreType INTEGER, "
60 "cachedScore FLOAT, "
61 "firstUpdate INTEGER, "
62 "lastUpdate INTEGER, "
63 "PRIMARY KEY(usedActivity, initiatingAgent, targettedResource)"
64 ")")
65
66 << // @since 2014.05.05
67 // The ResourceLink table stores the information, formerly kept by
68 // Nepomuk, of which resources are linked to which activities.
69 // The additional features compared to the old days are
70 // the ability to limit the link to specific applications, and
71 // to create global links.
72 QStringLiteral(
73 "CREATE TABLE IF NOT EXISTS ResourceLink ("
74 "usedActivity TEXT, "
75 "initiatingAgent TEXT, "
76 "targettedResource TEXT, "
77 "PRIMARY KEY(usedActivity, initiatingAgent, targettedResource)"
78 ")")
79
80 << // @since 2015.01.18
81 // The ResourceInfo table stores the collected information about a
82 // resource that is not agent nor activity related like the
83 // title and the mime type.
84 // If these are automatically retrieved (works for files), the
85 // flag is set to true. This is done for the agents to be able to
86 // override these.
87 QStringLiteral(
88 "CREATE TABLE IF NOT EXISTS ResourceInfo ("
89 "targettedResource TEXT, "
90 "title TEXT, "
91 "mimetype TEXT, "
92 "autoTitle INTEGER, "
93 "autoMimetype INTEGER, "
94 "PRIMARY KEY(targettedResource)"
95 ")")
96
97 ;
98 }
99
100 // TODO: This will require some refactoring after we introduce more databases
defaultPath()101 QString defaultPath()
102 {
103 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/resources/database");
104 }
105
106 const char *overrideFlagProperty = "org.kde.KActivities.ResourcesDatabase.overrideDatabase";
107 const char *overrideFileProperty = "org.kde.KActivities.ResourcesDatabase.overrideDatabaseFile";
108
path()109 QString path()
110 {
111 auto app = QCoreApplication::instance();
112
113 return (app->property(overrideFlagProperty).toBool()) ? app->property(overrideFileProperty).toString() : defaultPath();
114 }
115
overridePath(const QString & path)116 void overridePath(const QString &path)
117 {
118 auto app = QCoreApplication::instance();
119
120 app->setProperty(overrideFlagProperty, true);
121 app->setProperty(overrideFileProperty, path);
122 }
123
initSchema(Database & database)124 void initSchema(Database &database)
125 {
126 QString dbSchemaVersion;
127
128 auto query = database.execQuery(QStringLiteral("SELECT value FROM SchemaInfo WHERE key = 'version'"),
129 /* ignore error */ true);
130
131 if (query.next()) {
132 dbSchemaVersion = query.value(0).toString();
133 }
134
135 // Early bail-out if the schema is up-to-date
136 if (dbSchemaVersion == version()) {
137 return;
138 }
139
140 // Transition to KF5:
141 // We left the world of Nepomuk, and all the ontologies went
142 // across the sea to the Undying Lands.
143 // This needs to be done before executing the schema() queries
144 // so that we do not create new (empty) tables and block these
145 // queries from being executed.
146 if (dbSchemaVersion < QStringLiteral("2014.04.14")) {
147 database.execQuery(QStringLiteral("ALTER TABLE nuao_DesktopEvent RENAME TO ResourceEvent"),
148 /* ignore error */ true);
149 database.execQuery(QStringLiteral("ALTER TABLE kext_ResourceScoreCache RENAME TO ResourceScoreCache"),
150 /* ignore error */ true);
151 }
152
153 database.execQueries(ResourcesDatabaseSchema::schema());
154
155 // We are asking for trouble. If the database is corrupt,
156 // some of these should fail.
157 // WARNING: Sqlite specific!
158 database.execQueries(QStringList{
159 ".tables",
160 "SELECT count(*) FROM SchemaInfo",
161 "SELECT count(*) FROM ResourceEvent",
162 "SELECT count(*) FROM ResourceScoreCache",
163 "SELECT count(*) FROM ResourceLink",
164 "SELECT count(*) FROM ResourceInfo",
165 });
166
167 // We can not allow empty fields for activity and agent, they need to
168 // be at least magic values. These do not change the structure
169 // of the database, but the old data.
170 if (dbSchemaVersion < QStringLiteral("2015.02.09")) {
171 const QString updateActivity = QStringLiteral(
172 "SET usedActivity=':global' "
173 "WHERE usedActivity IS NULL OR usedActivity = ''");
174
175 const QString updateAgent = QStringLiteral(
176 "SET initiatingAgent=':global' "
177 "WHERE initiatingAgent IS NULL OR initiatingAgent = ''");
178
179 // When the activity field was empty, it meant the file was
180 // linked to all activities (aka :global)
181 database.execQuery("UPDATE ResourceLink " + updateActivity);
182
183 // When the agent field was empty, it meant the file was not
184 // linked to a specified agent (aka :global)
185 database.execQuery("UPDATE ResourceLink " + updateAgent);
186
187 // These were not supposed to be empty, but in the case they were,
188 // deal with them as well
189 database.execQuery("UPDATE ResourceEvent " + updateActivity);
190 database.execQuery("UPDATE ResourceEvent " + updateAgent);
191 database.execQuery("UPDATE ResourceScoreCache " + updateActivity);
192 database.execQuery("UPDATE ResourceScoreCache " + updateAgent);
193 }
194 }
195
196 } // namespace Common
197 } // namespace ResourcesDatabaseSchema
198