QGIS API Documentation 4.1.0-Master (60fea48833c)
Loading...
Searching...
No Matches
qgshistoryproviderregistry.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgshistoryproviderregistry.cpp
3 -------------------------
4 begin : April 2019
5 copyright : (C) 2019 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8/***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
18
19#include <sqlite3.h>
20
21#include "qgsapplication.h"
23#include "qgshistoryentry.h"
24#include "qgshistoryprovider.h"
25#include "qgslogger.h"
27#include "qgsprocessingutils.h"
28#include "qgsruntimeprofiler.h"
29#include "qgsxmlutils.h"
30
31#include <QFile>
32#include <QString>
33
34#include "moc_qgshistoryproviderregistry.cpp"
35
36using namespace Qt::StringLiterals;
37
38QgsHistoryProviderRegistry::QgsHistoryProviderRegistry( QObject *parent, bool useMemoryDatabase )
39 : QObject( parent )
40{
41 QgsScopedRuntimeProfile profile( tr( "Load history database" ) );
42 const QString historyFilename = userHistoryDbPath();
43
44 // create history db if it doesn't exist
45 QString error;
46 if ( useMemoryDatabase )
47 {
48 createDatabase( u":memory:"_s, error );
49 }
50 else
51 {
52 if ( !QFile::exists( historyFilename ) )
53 {
54 createDatabase( historyFilename, error );
55 }
56 else
57 {
58 openDatabase( historyFilename, error );
59 }
60 }
61}
62
64{
65 qDeleteAll( mProviders );
66}
67
73
75{
76 if ( mProviders.contains( provider->id() ) )
77 return false;
78
79 mProviders.insert( provider->id(), provider );
80 return true;
81}
82
84{
85 return mProviders.value( id );
86}
87
89{
90 if ( !mProviders.contains( id ) )
91 return false;
92
93 delete mProviders.take( id );
94 return true;
95}
96
98{
99 return mProviders.keys();
100}
101
102long long QgsHistoryProviderRegistry::addEntry( const QString &providerId, const QVariantMap &entry, bool &ok, QgsHistoryProviderRegistry::HistoryEntryOptions options )
103{
104 return addEntry( QgsHistoryEntry( providerId, QDateTime::currentDateTime(), entry ), ok, options );
105}
106
108{
109 ok = true;
110 long long id = -1;
112 {
113 QDomDocument xmlDoc;
114 const QVariant cleanedMap = QgsProcessingUtils::removePointerValuesFromMap( entry.entry );
115 xmlDoc.appendChild( QgsXmlUtils::writeVariant( cleanedMap, xmlDoc ) );
116 const QString entryXml = xmlDoc.toString();
117 const QString dateTime = entry.timestamp.toString( u"yyyy-MM-dd HH:mm:ss"_s );
118
119 QString query = qgs_sqlite3_mprintf( "INSERT INTO history VALUES (NULL, '%q', '%q', '%q');", entry.providerId.toUtf8().constData(), entryXml.toUtf8().constData(), dateTime.toUtf8().constData() );
120 if ( !runEmptyQuery( query ) )
121 {
122 QgsDebugError( u"Couldn't story history entry in database!"_s );
123 ok = false;
124 return -1;
125 }
126 id = static_cast<int>( sqlite3_last_insert_rowid( mLocalDB.get() ) );
127
128 QgsHistoryEntry addedEntry( entry );
129 addedEntry.id = id;
130
132 }
133
134 return id;
135}
136
137bool QgsHistoryProviderRegistry::addEntries( const QList<QgsHistoryEntry> &entries, HistoryEntryOptions options )
138{
139 bool ok = true;
141 {
142 runEmptyQuery( u"BEGIN TRANSACTION;"_s );
143 for ( const QgsHistoryEntry &entry : entries )
144 addEntry( entry, ok, options );
145 runEmptyQuery( u"COMMIT TRANSACTION;"_s );
146 }
147
148 return ok;
149}
150
152{
153 ok = false;
154 switch ( backend )
155 {
157 {
158 if ( !mLocalDB )
159 {
160 QgsDebugError( u"Cannot open database to query history entries"_s );
161 return QgsHistoryEntry( QVariantMap() );
162 }
163
164 QString sql = u"SELECT provider_id, xml, timestamp FROM history WHERE id=%1"_s.arg( id );
165
166 int nErr;
167 sqlite3_statement_unique_ptr statement = mLocalDB.prepare( sql, nErr );
168
169 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
170 {
171 QDomDocument doc;
172 if ( !doc.setContent( statement.columnAsText( 1 ) ) )
173 {
174 QgsDebugError( u"Cannot read history entry"_s );
175 return QgsHistoryEntry( QVariantMap() );
176 }
177
178 ok = true;
180 = QgsHistoryEntry( statement.columnAsText( 0 ), QDateTime::fromString( statement.columnAsText( 2 ), u"yyyy-MM-dd HH:mm:ss"_s ), QgsXmlUtils::readVariant( doc.documentElement() ).toMap() );
181 res.id = id;
182 return res;
183 }
184
185 QgsDebugError( u"Cannot find history item with matching ID"_s );
186 return QgsHistoryEntry( QVariantMap() );
187 }
188 }
190}
191
192bool QgsHistoryProviderRegistry::updateEntry( long long id, const QVariantMap &entry, Qgis::HistoryProviderBackend backend )
193{
194 switch ( backend )
195 {
197 {
198 const QVariantMap cleanedMap = QgsProcessingUtils::removePointerValuesFromMap( entry );
199 QDomDocument xmlDoc;
200 xmlDoc.appendChild( QgsXmlUtils::writeVariant( cleanedMap, xmlDoc ) );
201 const QString entryXml = xmlDoc.toString();
202
203 QString query = qgs_sqlite3_mprintf( "UPDATE history SET xml='%q' WHERE id = %d;", entryXml.toUtf8().constData(), id );
204 if ( !runEmptyQuery( query ) )
205 {
206 QgsDebugError( u"Couldn't update history entry in database!"_s );
207 return false;
208 }
209
211 return true;
212 }
213 }
215}
216
217QList<QgsHistoryEntry> QgsHistoryProviderRegistry::queryEntries( const QDateTime &start, const QDateTime &end, const QString &providerId, Qgis::HistoryProviderBackends backends ) const
218{
219 QList<QgsHistoryEntry> entries;
221 {
222 if ( !mLocalDB )
223 {
224 QgsDebugError( u"Cannot open database to query history entries"_s );
225 return {};
226 }
227
228 QString sql = u"SELECT id, provider_id, xml, timestamp FROM history"_s;
229 QStringList whereClauses;
230 if ( !providerId.isEmpty() )
231 {
232 whereClauses.append( u"provider_id='%1'"_s.arg( providerId ) );
233 }
234 if ( start.isValid() )
235 {
236 whereClauses.append( u"timestamp>='%1'"_s.arg( start.toString( u"yyyy-MM-dd HH:mm:ss"_s ) ) );
237 }
238 if ( end.isValid() )
239 {
240 whereClauses.append( u"timestamp<='%1'"_s.arg( end.toString( u"yyyy-MM-dd HH:mm:ss"_s ) ) );
241 }
242
243 if ( !whereClauses.empty() )
244 sql += u" WHERE ("_s + whereClauses.join( ") AND ("_L1 ) + ')';
245
246 int nErr;
247 sqlite3_statement_unique_ptr statement = mLocalDB.prepare( sql, nErr );
248
249 while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
250 {
251 QDomDocument doc;
252 if ( !doc.setContent( statement.columnAsText( 2 ) ) )
253 {
254 QgsDebugError( u"Cannot read history entry"_s );
255 continue;
256 }
257
258 QgsHistoryEntry entry( statement.columnAsText( 1 ), QDateTime::fromString( statement.columnAsText( 3 ), u"yyyy-MM-dd HH:mm:ss"_s ), QgsXmlUtils::readVariant( doc.documentElement() ).toMap() );
259 entry.id = statement.columnAsInt64( 0 );
260
261 entries.append( entry );
262 }
263 }
264
265 return entries;
266}
267
269{
270 return QgsApplication::qgisSettingsDirPath() + u"user-history.db"_s;
271}
272
274{
275 switch ( backend )
276 {
278 {
279 if ( providerId.isEmpty() )
280 runEmptyQuery( u"DELETE from history;"_s );
281 else
282 runEmptyQuery( u"DELETE from history WHERE provider_id='%1'"_s.arg( providerId ) );
283 break;
284 }
285 }
286 emit historyCleared( backend, providerId );
287 return true;
288}
289
290bool QgsHistoryProviderRegistry::createDatabase( const QString &filename, QString &error )
291{
292 error.clear();
293 if ( !openDatabase( filename, error ) )
294 {
295 QgsDebugError( error );
296 return false;
297 }
298
299 createTables();
300
301 return true;
302}
303
304bool QgsHistoryProviderRegistry::openDatabase( const QString &filename, QString &error )
305{
306 int rc = mLocalDB.open( filename );
307 if ( rc )
308 {
309 error = tr( "Couldn't open the history database: %1" ).arg( mLocalDB.errorMessage() );
310 return false;
311 }
312
313 return true;
314}
315
316void QgsHistoryProviderRegistry::createTables()
317{
318 QString query = qgs_sqlite3_mprintf(
319 "CREATE TABLE history("
320 "id INTEGER PRIMARY KEY,"
321 "provider_id TEXT,"
322 "xml TEXT,"
323 "timestamp DATETIME);"
324 "CREATE INDEX provider_index ON history(provider_id);"
325 "CREATE INDEX timestamp_index ON history(timestamp);"
326 );
327
328 runEmptyQuery( query );
329}
330
331bool QgsHistoryProviderRegistry::runEmptyQuery( const QString &query )
332{
333 if ( !mLocalDB )
334 return false;
335
336 char *zErr = nullptr;
337 int nErr = sqlite3_exec( mLocalDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
338
339 if ( nErr != SQLITE_OK )
340 {
341 QgsDebugError( zErr );
342 sqlite3_free( zErr );
343 }
344
345 return nErr == SQLITE_OK;
346}
HistoryProviderBackend
History provider backends.
Definition qgis.h:3617
@ LocalProfile
Local profile.
Definition qgis.h:3618
QFlags< HistoryProviderBackend > HistoryProviderBackends
Definition qgis.h:3622
Abstract base class for objects which track user history (i.e.
virtual QString id() const =0
Returns the provider's unique id, which is used to associate existing history entries with the provid...
static QString qgisSettingsDirPath()
Returns the path to the settings directory in user's home dir.
History provider for operations database queries.
Encapsulates a history entry.
long long id
Entry ID.
Contains options for storing history entries.
Qgis::HistoryProviderBackends storageBackends
Target storage backends.
QgsAbstractHistoryProvider * providerById(const QString &id)
Returns the provider with matching id, or nullptr if no matching provider is registered.
bool updateEntry(long long id, const QVariantMap &entry, Qgis::HistoryProviderBackend backend=Qgis::HistoryProviderBackend::LocalProfile)
Updates the existing entry with matching id.
static QString userHistoryDbPath()
Returns the path to user's local history database.
bool clearHistory(Qgis::HistoryProviderBackend backend, const QString &providerId=QString())
Clears the history for the specified backend.
void entryAdded(long long id, const QgsHistoryEntry &entry, Qgis::HistoryProviderBackend backend)
Emitted when an entry is added.
bool addProvider(QgsAbstractHistoryProvider *provider)
Adds a provider to the registry.
bool addEntries(const QList< QgsHistoryEntry > &entries, QgsHistoryProviderRegistry::HistoryEntryOptions options=QgsHistoryProviderRegistry::HistoryEntryOptions())
Adds a list of entries to the history logs.
QgsHistoryProviderRegistry(QObject *parent=nullptr, bool useMemoryDatabase=false)
Creates a new empty history provider registry.
bool removeProvider(const QString &id)
Removes the provider with matching id.
long long addEntry(const QString &providerId, const QVariantMap &entry, bool &ok, QgsHistoryProviderRegistry::HistoryEntryOptions options=QgsHistoryProviderRegistry::HistoryEntryOptions())
Adds an entry to the history logs.
QgsHistoryEntry entry(long long id, bool &ok, Qgis::HistoryProviderBackend backend=Qgis::HistoryProviderBackend::LocalProfile) const
Returns the entry with matching ID, from the specified backend.
QList< QgsHistoryEntry > queryEntries(const QDateTime &start=QDateTime(), const QDateTime &end=QDateTime(), const QString &providerId=QString(), Qgis::HistoryProviderBackends backends=Qgis::HistoryProviderBackend::LocalProfile) const
Queries history entries which occurred between the specified start and end times.
void entryUpdated(long long id, const QVariantMap &entry, Qgis::HistoryProviderBackend backend)
Emitted when an entry is updated.
void addDefaultProviders()
Adds the default history providers to the registry.
QStringList providerIds() const
Returns a list of the registered provider IDs.
void historyCleared(Qgis::HistoryProviderBackend backend, const QString &providerId)
Emitted when the history is cleared for a backend.
History provider for operations performed through the Processing framework.
static QVariantMap removePointerValuesFromMap(const QVariantMap &map)
Removes any raw pointer values from an input map, replacing them with appropriate string values where...
Scoped object for logging of the runtime for a single operation or group of operations.
static QDomElement writeVariant(const QVariant &value, QDomDocument &doc)
Write a QVariant to a QDomElement.
static QVariant readVariant(const QDomElement &element)
Read a QVariant from a QDomElement.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
qlonglong columnAsInt64(int column) const
Gets column value from the current statement row as a long long integer (64 bits).
#define BUILTIN_UNREACHABLE
Definition qgis.h:7540
#define QgsDebugError(str)
Definition qgslogger.h:59
QString qgs_sqlite3_mprintf(const char *format,...)
Wraps sqlite3_mprintf() by automatically freeing the memory.