QGIS API Documentation 3.41.0-Master (af5edcb665c)
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#include "moc_qgshistoryproviderregistry.cpp"
19#include "qgshistoryprovider.h"
20#include "qgsapplication.h"
21#include "qgsruntimeprofiler.h"
22#include "qgslogger.h"
23#include "qgsxmlutils.h"
25#include "qgsprocessingutils.h"
26#include "qgshistoryentry.h"
28
29#include <QFile>
30#include <sqlite3.h>
31
32QgsHistoryProviderRegistry::QgsHistoryProviderRegistry( QObject *parent, bool useMemoryDatabase )
33 : QObject( parent )
34{
35 QgsScopedRuntimeProfile profile( tr( "Load history database" ) );
36 const QString historyFilename = userHistoryDbPath();
37
38 // create history db if it doesn't exist
39 QString error;
40 if ( useMemoryDatabase )
41 {
42 createDatabase( QStringLiteral( ":memory:" ), error );
43 }
44 else
45 {
46 if ( !QFile::exists( historyFilename ) )
47 {
48 createDatabase( historyFilename, error );
49 }
50 else
51 {
52 openDatabase( historyFilename, error );
53 }
54 }
55}
56
58{
59 qDeleteAll( mProviders );
60}
61
67
69{
70 if ( mProviders.contains( provider->id() ) )
71 return false;
72
73 mProviders.insert( provider->id(), provider );
74 return true;
75}
76
78{
79 return mProviders.value( id );
80}
81
83{
84 if ( !mProviders.contains( id ) )
85 return false;
86
87 delete mProviders.take( id );
88 return true;
89}
90
92{
93 return mProviders.keys();
94}
95
96long long QgsHistoryProviderRegistry::addEntry( const QString &providerId, const QVariantMap &entry, bool &ok, QgsHistoryProviderRegistry::HistoryEntryOptions options )
97{
98 return addEntry( QgsHistoryEntry( providerId, QDateTime::currentDateTime(), entry ), ok, options );
99}
100
102{
103 ok = true;
104 long long id = -1;
106 {
107 QDomDocument xmlDoc;
108 const QVariant cleanedMap = QgsProcessingUtils::removePointerValuesFromMap( entry.entry );
109 xmlDoc.appendChild( QgsXmlUtils::writeVariant( cleanedMap, xmlDoc ) );
110 const QString entryXml = xmlDoc.toString();
111 const QString dateTime = entry.timestamp.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) );
112
113 QString query = qgs_sqlite3_mprintf( "INSERT INTO history VALUES (NULL, '%q', '%q', '%q');", entry.providerId.toUtf8().constData(), entryXml.toUtf8().constData(), dateTime.toUtf8().constData() );
114 if ( !runEmptyQuery( query ) )
115 {
116 QgsDebugError( QStringLiteral( "Couldn't story history entry in database!" ) );
117 ok = false;
118 return -1;
119 }
120 id = static_cast<int>( sqlite3_last_insert_rowid( mLocalDB.get() ) );
121
122 QgsHistoryEntry addedEntry( entry );
123 addedEntry.id = id;
124
126 }
127
128 return id;
129}
130
131bool QgsHistoryProviderRegistry::addEntries( const QList<QgsHistoryEntry> &entries, HistoryEntryOptions options )
132{
133 bool ok = true;
135 {
136 runEmptyQuery( QStringLiteral( "BEGIN TRANSACTION;" ) );
137 for ( const QgsHistoryEntry &entry : entries )
138 addEntry( entry, ok, options );
139 runEmptyQuery( QStringLiteral( "COMMIT TRANSACTION;" ) );
140 }
141
142 return ok;
143}
144
146{
147 ok = false;
148 switch ( backend )
149 {
151 {
152 if ( !mLocalDB )
153 {
154 QgsDebugError( QStringLiteral( "Cannot open database to query history entries" ) );
155 return QgsHistoryEntry( QVariantMap() );
156 }
157
158 QString sql = QStringLiteral( "SELECT provider_id, xml, timestamp FROM history WHERE id=%1" ).arg( id );
159
160 int nErr;
161 sqlite3_statement_unique_ptr statement = mLocalDB.prepare( sql, nErr );
162
163 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
164 {
165 QDomDocument doc;
166 if ( !doc.setContent( statement.columnAsText( 1 ) ) )
167 {
168 QgsDebugError( QStringLiteral( "Cannot read history entry" ) );
169 return QgsHistoryEntry( QVariantMap() );
170 }
171
172 ok = true;
174 statement.columnAsText( 0 ),
175 QDateTime::fromString( statement.columnAsText( 2 ), QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ),
176 QgsXmlUtils::readVariant( doc.documentElement() ).toMap()
177 );
178 res.id = id;
179 return res;
180 }
181
182 QgsDebugError( QStringLiteral( "Cannot find history item with matching ID" ) );
183 return QgsHistoryEntry( QVariantMap() );
184 }
185 }
187}
188
189bool QgsHistoryProviderRegistry::updateEntry( long long id, const QVariantMap &entry, Qgis::HistoryProviderBackend backend )
190{
191 switch ( backend )
192 {
194 {
195 const QVariantMap cleanedMap = QgsProcessingUtils::removePointerValuesFromMap( entry );
196 QDomDocument xmlDoc;
197 xmlDoc.appendChild( QgsXmlUtils::writeVariant( cleanedMap, xmlDoc ) );
198 const QString entryXml = xmlDoc.toString();
199
200 QString query = qgs_sqlite3_mprintf( "UPDATE history SET xml='%q' WHERE id = %d;", entryXml.toUtf8().constData(), id );
201 if ( !runEmptyQuery( query ) )
202 {
203 QgsDebugError( QStringLiteral( "Couldn't update history entry in database!" ) );
204 return false;
205 }
206
208 return true;
209 }
210 }
212}
213
214QList<QgsHistoryEntry> QgsHistoryProviderRegistry::queryEntries( const QDateTime &start, const QDateTime &end, const QString &providerId, Qgis::HistoryProviderBackends backends ) const
215{
216 QList<QgsHistoryEntry> entries;
218 {
219 if ( !mLocalDB )
220 {
221 QgsDebugError( QStringLiteral( "Cannot open database to query history entries" ) );
222 return {};
223 }
224
225 QString sql = QStringLiteral( "SELECT id, provider_id, xml, timestamp FROM history" );
226 QStringList whereClauses;
227 if ( !providerId.isEmpty() )
228 {
229 whereClauses.append( QStringLiteral( "provider_id='%1'" ).arg( providerId ) );
230 }
231 if ( start.isValid() )
232 {
233 whereClauses.append( QStringLiteral( "timestamp>='%1'" ).arg( start.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ) ) );
234 }
235 if ( end.isValid() )
236 {
237 whereClauses.append( QStringLiteral( "timestamp<='%1'" ).arg( end.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ) ) );
238 }
239
240 if ( !whereClauses.empty() )
241 sql += QStringLiteral( " WHERE (" ) + whereClauses.join( QLatin1String( ") AND (" ) ) + ')';
242
243 int nErr;
244 sqlite3_statement_unique_ptr statement = mLocalDB.prepare( sql, nErr );
245
246 while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
247 {
248 QDomDocument doc;
249 if ( !doc.setContent( statement.columnAsText( 2 ) ) )
250 {
251 QgsDebugError( QStringLiteral( "Cannot read history entry" ) );
252 continue;
253 }
254
256 statement.columnAsText( 1 ),
257 QDateTime::fromString( statement.columnAsText( 3 ), QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ),
258 QgsXmlUtils::readVariant( doc.documentElement() ).toMap()
259 );
260 entry.id = statement.columnAsInt64( 0 );
261
262 entries.append( entry );
263 }
264 }
265
266 return entries;
267}
268
270{
271 return QgsApplication::qgisSettingsDirPath() + QStringLiteral( "user-history.db" );
272}
273
275{
276 switch ( backend )
277 {
279 {
280 if ( providerId.isEmpty() )
281 runEmptyQuery( QStringLiteral( "DELETE from history;" ) );
282 else
283 runEmptyQuery( QStringLiteral( "DELETE from history WHERE provider_id='%1'" )
284 .arg( providerId ) );
285 break;
286 }
287 }
288 emit historyCleared( backend, providerId );
289 return true;
290}
291
292bool QgsHistoryProviderRegistry::createDatabase( const QString &filename, QString &error )
293{
294 error.clear();
295 if ( !openDatabase( filename, error ) )
296 {
297 QgsDebugError( error );
298 return false;
299 }
300
301 createTables();
302
303 return true;
304}
305
306bool QgsHistoryProviderRegistry::openDatabase( const QString &filename, QString &error )
307{
308 int rc = mLocalDB.open( filename );
309 if ( rc )
310 {
311 error = tr( "Couldn't open the history database: %1" ).arg( mLocalDB.errorMessage() );
312 return false;
313 }
314
315 return true;
316}
317
318void QgsHistoryProviderRegistry::createTables()
319{
320 QString query = qgs_sqlite3_mprintf( "CREATE TABLE history("
321 "id INTEGER PRIMARY KEY,"
322 "provider_id TEXT,"
323 "xml TEXT,"
324 "timestamp DATETIME);"
325 "CREATE INDEX provider_index ON history(provider_id);"
326 "CREATE INDEX timestamp_index ON history(timestamp);"
327 );
328
329 runEmptyQuery( query );
330}
331
332bool QgsHistoryProviderRegistry::runEmptyQuery( const QString &query )
333{
334 if ( !mLocalDB )
335 return false;
336
337 char *zErr = nullptr;
338 int nErr = sqlite3_exec( mLocalDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
339
340 if ( nErr != SQLITE_OK )
341 {
342 QgsDebugError( zErr );
343 sqlite3_free( zErr );
344 }
345
346 return nErr == SQLITE_OK;
347}
HistoryProviderBackend
History provider backends.
Definition qgis.h:3317
@ LocalProfile
Local profile.
QFlags< HistoryProviderBackend > HistoryProviderBackends
Definition qgis.h:3322
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.
QDateTime timestamp
Entry timestamp.
QString providerId
Associated history provider ID.
long long id
Entry ID.
QVariantMap entry
Entry details.
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.
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
int open(const QString &path)
Opens the database at the specified file path.
QString errorMessage() const
Returns the most recent error message encountered by the database.
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:6720
#define QgsDebugError(str)
Definition qgslogger.h:38
QString qgs_sqlite3_mprintf(const char *format,...)
Wraps sqlite3_mprintf() by automatically freeing the memory.