QGIS API Documentation 3.99.0-Master (26c88405ac0)
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
33#include "moc_qgshistoryproviderregistry.cpp"
34
35QgsHistoryProviderRegistry::QgsHistoryProviderRegistry( QObject *parent, bool useMemoryDatabase )
36 : QObject( parent )
37{
38 QgsScopedRuntimeProfile profile( tr( "Load history database" ) );
39 const QString historyFilename = userHistoryDbPath();
40
41 // create history db if it doesn't exist
42 QString error;
43 if ( useMemoryDatabase )
44 {
45 createDatabase( QStringLiteral( ":memory:" ), error );
46 }
47 else
48 {
49 if ( !QFile::exists( historyFilename ) )
50 {
51 createDatabase( historyFilename, error );
52 }
53 else
54 {
55 openDatabase( historyFilename, error );
56 }
57 }
58}
59
61{
62 qDeleteAll( mProviders );
63}
64
70
72{
73 if ( mProviders.contains( provider->id() ) )
74 return false;
75
76 mProviders.insert( provider->id(), provider );
77 return true;
78}
79
81{
82 return mProviders.value( id );
83}
84
86{
87 if ( !mProviders.contains( id ) )
88 return false;
89
90 delete mProviders.take( id );
91 return true;
92}
93
95{
96 return mProviders.keys();
97}
98
99long long QgsHistoryProviderRegistry::addEntry( const QString &providerId, const QVariantMap &entry, bool &ok, QgsHistoryProviderRegistry::HistoryEntryOptions options )
100{
101 return addEntry( QgsHistoryEntry( providerId, QDateTime::currentDateTime(), entry ), ok, options );
102}
103
105{
106 ok = true;
107 long long id = -1;
109 {
110 QDomDocument xmlDoc;
111 const QVariant cleanedMap = QgsProcessingUtils::removePointerValuesFromMap( entry.entry );
112 xmlDoc.appendChild( QgsXmlUtils::writeVariant( cleanedMap, xmlDoc ) );
113 const QString entryXml = xmlDoc.toString();
114 const QString dateTime = entry.timestamp.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) );
115
116 QString query = qgs_sqlite3_mprintf( "INSERT INTO history VALUES (NULL, '%q', '%q', '%q');", entry.providerId.toUtf8().constData(), entryXml.toUtf8().constData(), dateTime.toUtf8().constData() );
117 if ( !runEmptyQuery( query ) )
118 {
119 QgsDebugError( QStringLiteral( "Couldn't story history entry in database!" ) );
120 ok = false;
121 return -1;
122 }
123 id = static_cast<int>( sqlite3_last_insert_rowid( mLocalDB.get() ) );
124
125 QgsHistoryEntry addedEntry( entry );
126 addedEntry.id = id;
127
129 }
130
131 return id;
132}
133
134bool QgsHistoryProviderRegistry::addEntries( const QList<QgsHistoryEntry> &entries, HistoryEntryOptions options )
135{
136 bool ok = true;
138 {
139 runEmptyQuery( QStringLiteral( "BEGIN TRANSACTION;" ) );
140 for ( const QgsHistoryEntry &entry : entries )
141 addEntry( entry, ok, options );
142 runEmptyQuery( QStringLiteral( "COMMIT TRANSACTION;" ) );
143 }
144
145 return ok;
146}
147
149{
150 ok = false;
151 switch ( backend )
152 {
154 {
155 if ( !mLocalDB )
156 {
157 QgsDebugError( QStringLiteral( "Cannot open database to query history entries" ) );
158 return QgsHistoryEntry( QVariantMap() );
159 }
160
161 QString sql = QStringLiteral( "SELECT provider_id, xml, timestamp FROM history WHERE id=%1" ).arg( id );
162
163 int nErr;
164 sqlite3_statement_unique_ptr statement = mLocalDB.prepare( sql, nErr );
165
166 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
167 {
168 QDomDocument doc;
169 if ( !doc.setContent( statement.columnAsText( 1 ) ) )
170 {
171 QgsDebugError( QStringLiteral( "Cannot read history entry" ) );
172 return QgsHistoryEntry( QVariantMap() );
173 }
174
175 ok = true;
177 statement.columnAsText( 0 ),
178 QDateTime::fromString( statement.columnAsText( 2 ), QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ),
179 QgsXmlUtils::readVariant( doc.documentElement() ).toMap()
180 );
181 res.id = id;
182 return res;
183 }
184
185 QgsDebugError( QStringLiteral( "Cannot find history item with matching ID" ) );
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( QStringLiteral( "Couldn't update history entry in database!" ) );
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( QStringLiteral( "Cannot open database to query history entries" ) );
225 return {};
226 }
227
228 QString sql = QStringLiteral( "SELECT id, provider_id, xml, timestamp FROM history" );
229 QStringList whereClauses;
230 if ( !providerId.isEmpty() )
231 {
232 whereClauses.append( QStringLiteral( "provider_id='%1'" ).arg( providerId ) );
233 }
234 if ( start.isValid() )
235 {
236 whereClauses.append( QStringLiteral( "timestamp>='%1'" ).arg( start.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ) ) );
237 }
238 if ( end.isValid() )
239 {
240 whereClauses.append( QStringLiteral( "timestamp<='%1'" ).arg( end.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ) ) );
241 }
242
243 if ( !whereClauses.empty() )
244 sql += QStringLiteral( " WHERE (" ) + whereClauses.join( QLatin1String( ") AND (" ) ) + ')';
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( QStringLiteral( "Cannot read history entry" ) );
255 continue;
256 }
257
259 statement.columnAsText( 1 ),
260 QDateTime::fromString( statement.columnAsText( 3 ), QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ),
261 QgsXmlUtils::readVariant( doc.documentElement() ).toMap()
262 );
263 entry.id = statement.columnAsInt64( 0 );
264
265 entries.append( entry );
266 }
267 }
268
269 return entries;
270}
271
273{
274 return QgsApplication::qgisSettingsDirPath() + QStringLiteral( "user-history.db" );
275}
276
278{
279 switch ( backend )
280 {
282 {
283 if ( providerId.isEmpty() )
284 runEmptyQuery( QStringLiteral( "DELETE from history;" ) );
285 else
286 runEmptyQuery( QStringLiteral( "DELETE from history WHERE provider_id='%1'" )
287 .arg( providerId ) );
288 break;
289 }
290 }
291 emit historyCleared( backend, providerId );
292 return true;
293}
294
295bool QgsHistoryProviderRegistry::createDatabase( const QString &filename, QString &error )
296{
297 error.clear();
298 if ( !openDatabase( filename, error ) )
299 {
300 QgsDebugError( error );
301 return false;
302 }
303
304 createTables();
305
306 return true;
307}
308
309bool QgsHistoryProviderRegistry::openDatabase( const QString &filename, QString &error )
310{
311 int rc = mLocalDB.open( filename );
312 if ( rc )
313 {
314 error = tr( "Couldn't open the history database: %1" ).arg( mLocalDB.errorMessage() );
315 return false;
316 }
317
318 return true;
319}
320
321void QgsHistoryProviderRegistry::createTables()
322{
323 QString query = qgs_sqlite3_mprintf( "CREATE TABLE history("
324 "id INTEGER PRIMARY KEY,"
325 "provider_id TEXT,"
326 "xml TEXT,"
327 "timestamp DATETIME);"
328 "CREATE INDEX provider_index ON history(provider_id);"
329 "CREATE INDEX timestamp_index ON history(timestamp);"
330 );
331
332 runEmptyQuery( query );
333}
334
335bool QgsHistoryProviderRegistry::runEmptyQuery( const QString &query )
336{
337 if ( !mLocalDB )
338 return false;
339
340 char *zErr = nullptr;
341 int nErr = sqlite3_exec( mLocalDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
342
343 if ( nErr != SQLITE_OK )
344 {
345 QgsDebugError( zErr );
346 sqlite3_free( zErr );
347 }
348
349 return nErr == SQLITE_OK;
350}
HistoryProviderBackend
History provider backends.
Definition qgis.h:3503
@ LocalProfile
Local profile.
Definition qgis.h:3504
QFlags< HistoryProviderBackend > HistoryProviderBackends
Definition qgis.h:3508
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:7208
#define QgsDebugError(str)
Definition qgslogger.h:57
QString qgs_sqlite3_mprintf(const char *format,...)
Wraps sqlite3_mprintf() by automatically freeing the memory.