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