QGIS API Documentation 3.99.0-Master (8e76e220402)
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 statement.columnAsText( 0 ),
181 QDateTime::fromString( statement.columnAsText( 2 ), u"yyyy-MM-dd HH:mm:ss"_s ),
182 QgsXmlUtils::readVariant( doc.documentElement() ).toMap()
183 );
184 res.id = id;
185 return res;
186 }
187
188 QgsDebugError( u"Cannot find history item with matching ID"_s );
189 return QgsHistoryEntry( QVariantMap() );
190 }
191 }
193}
194
195bool QgsHistoryProviderRegistry::updateEntry( long long id, const QVariantMap &entry, Qgis::HistoryProviderBackend backend )
196{
197 switch ( backend )
198 {
200 {
201 const QVariantMap cleanedMap = QgsProcessingUtils::removePointerValuesFromMap( entry );
202 QDomDocument xmlDoc;
203 xmlDoc.appendChild( QgsXmlUtils::writeVariant( cleanedMap, xmlDoc ) );
204 const QString entryXml = xmlDoc.toString();
205
206 QString query = qgs_sqlite3_mprintf( "UPDATE history SET xml='%q' WHERE id = %d;", entryXml.toUtf8().constData(), id );
207 if ( !runEmptyQuery( query ) )
208 {
209 QgsDebugError( u"Couldn't update history entry in database!"_s );
210 return false;
211 }
212
214 return true;
215 }
216 }
218}
219
220QList<QgsHistoryEntry> QgsHistoryProviderRegistry::queryEntries( const QDateTime &start, const QDateTime &end, const QString &providerId, Qgis::HistoryProviderBackends backends ) const
221{
222 QList<QgsHistoryEntry> entries;
224 {
225 if ( !mLocalDB )
226 {
227 QgsDebugError( u"Cannot open database to query history entries"_s );
228 return {};
229 }
230
231 QString sql = u"SELECT id, provider_id, xml, timestamp FROM history"_s;
232 QStringList whereClauses;
233 if ( !providerId.isEmpty() )
234 {
235 whereClauses.append( u"provider_id='%1'"_s.arg( providerId ) );
236 }
237 if ( start.isValid() )
238 {
239 whereClauses.append( u"timestamp>='%1'"_s.arg( start.toString( u"yyyy-MM-dd HH:mm:ss"_s ) ) );
240 }
241 if ( end.isValid() )
242 {
243 whereClauses.append( u"timestamp<='%1'"_s.arg( end.toString( u"yyyy-MM-dd HH:mm:ss"_s ) ) );
244 }
245
246 if ( !whereClauses.empty() )
247 sql += u" WHERE ("_s + whereClauses.join( ") AND ("_L1 ) + ')';
248
249 int nErr;
250 sqlite3_statement_unique_ptr statement = mLocalDB.prepare( sql, nErr );
251
252 while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
253 {
254 QDomDocument doc;
255 if ( !doc.setContent( statement.columnAsText( 2 ) ) )
256 {
257 QgsDebugError( u"Cannot read history entry"_s );
258 continue;
259 }
260
262 statement.columnAsText( 1 ),
263 QDateTime::fromString( statement.columnAsText( 3 ), u"yyyy-MM-dd HH:mm:ss"_s ),
264 QgsXmlUtils::readVariant( doc.documentElement() ).toMap()
265 );
266 entry.id = statement.columnAsInt64( 0 );
267
268 entries.append( entry );
269 }
270 }
271
272 return entries;
273}
274
276{
277 return QgsApplication::qgisSettingsDirPath() + u"user-history.db"_s;
278}
279
281{
282 switch ( backend )
283 {
285 {
286 if ( providerId.isEmpty() )
287 runEmptyQuery( u"DELETE from history;"_s );
288 else
289 runEmptyQuery( u"DELETE from history WHERE provider_id='%1'"_s
290 .arg( providerId ) );
291 break;
292 }
293 }
294 emit historyCleared( backend, providerId );
295 return true;
296}
297
298bool QgsHistoryProviderRegistry::createDatabase( const QString &filename, QString &error )
299{
300 error.clear();
301 if ( !openDatabase( filename, error ) )
302 {
303 QgsDebugError( error );
304 return false;
305 }
306
307 createTables();
308
309 return true;
310}
311
312bool QgsHistoryProviderRegistry::openDatabase( const QString &filename, QString &error )
313{
314 int rc = mLocalDB.open( filename );
315 if ( rc )
316 {
317 error = tr( "Couldn't open the history database: %1" ).arg( mLocalDB.errorMessage() );
318 return false;
319 }
320
321 return true;
322}
323
324void QgsHistoryProviderRegistry::createTables()
325{
326 QString query = qgs_sqlite3_mprintf( "CREATE TABLE history("
327 "id INTEGER PRIMARY KEY,"
328 "provider_id TEXT,"
329 "xml TEXT,"
330 "timestamp DATETIME);"
331 "CREATE INDEX provider_index ON history(provider_id);"
332 "CREATE INDEX timestamp_index ON history(timestamp);"
333 );
334
335 runEmptyQuery( query );
336}
337
338bool QgsHistoryProviderRegistry::runEmptyQuery( const QString &query )
339{
340 if ( !mLocalDB )
341 return false;
342
343 char *zErr = nullptr;
344 int nErr = sqlite3_exec( mLocalDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
345
346 if ( nErr != SQLITE_OK )
347 {
348 QgsDebugError( zErr );
349 sqlite3_free( zErr );
350 }
351
352 return nErr == SQLITE_OK;
353}
HistoryProviderBackend
History provider backends.
Definition qgis.h:3574
@ LocalProfile
Local profile.
Definition qgis.h:3575
QFlags< HistoryProviderBackend > HistoryProviderBackends
Definition qgis.h:3579
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:7524
#define QgsDebugError(str)
Definition qgslogger.h:59
QString qgs_sqlite3_mprintf(const char *format,...)
Wraps sqlite3_mprintf() by automatically freeing the memory.