QGIS API Documentation 3.29.0-Master (8c80f25a4f)
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
19#include "qgsapplication.h"
20#include "qgsruntimeprofiler.h"
21#include "qgslogger.h"
22#include "qgsxmlutils.h"
24
25#include <QFile>
26#include <sqlite3.h>
27
28//
29// QgsHistoryEntry
30//
31
32QgsHistoryEntry::QgsHistoryEntry( const QString &providerId, const QDateTime &timestamp, const QVariantMap &entry )
33 : timestamp( timestamp )
34 , providerId( providerId )
35 , entry( entry )
36{
37
38}
39
40QgsHistoryEntry::QgsHistoryEntry( const QVariantMap &entry )
41 : timestamp( QDateTime::currentDateTime() )
42 , entry( entry )
43{
44
45}
46
47//
48// QgsHistoryProviderRegistry
49//
50
51QgsHistoryProviderRegistry::QgsHistoryProviderRegistry( QObject *parent, bool useMemoryDatabase )
52 : QObject( parent )
53{
54 QgsScopedRuntimeProfile profile( tr( "Load history database" ) );
55 const QString historyFilename = userHistoryDbPath();
56
57 // create history db if it doesn't exist
58 QString error;
59 if ( useMemoryDatabase )
60 {
61 createDatabase( QStringLiteral( ":memory:" ), error );
62 }
63 else
64 {
65 if ( !QFile::exists( historyFilename ) )
66 {
67 createDatabase( historyFilename, error );
68 }
69 else
70 {
71 openDatabase( historyFilename, error );
72 }
73 }
74}
75
77{
78 qDeleteAll( mProviders );
79}
80
82{
84}
85
87{
88 if ( mProviders.contains( provider->id() ) )
89 return false;
90
91 mProviders.insert( provider->id(), provider );
92 return true;
93}
94
96{
97 return mProviders.value( id );
98}
99
101{
102 if ( !mProviders.contains( id ) )
103 return false;
104
105 delete mProviders.take( id );
106 return true;
107}
108
110{
111 return mProviders.keys();
112}
113
114long long QgsHistoryProviderRegistry::addEntry( const QString &providerId, const QVariantMap &entry, bool &ok, QgsHistoryProviderRegistry::HistoryEntryOptions options )
115{
116 return addEntry( QgsHistoryEntry( providerId, QDateTime::currentDateTime(), entry ), ok, options );
117}
118
120{
121 ok = true;
122 long long id = -1;
124 {
125 QDomDocument xmlDoc;
126 xmlDoc.appendChild( QgsXmlUtils::writeVariant( entry.entry, xmlDoc ) );
127 const QString entryXml = xmlDoc.toString();
128 const QString dateTime = entry.timestamp.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) );
129
130 QString query = qgs_sqlite3_mprintf( "INSERT INTO history VALUES (NULL, '%q', '%q', '%q');",
131 entry.providerId.toUtf8().constData(), entryXml.toUtf8().constData(), dateTime.toUtf8().constData() );
132 if ( !runEmptyQuery( query ) )
133 {
134 QgsDebugMsg( QStringLiteral( "Couldn't story history entry in database!" ) );
135 ok = false;
136 return -1;
137 }
138 id = static_cast< int >( sqlite3_last_insert_rowid( mLocalDB.get() ) );
139 }
140
141 return id;
142}
143
144bool QgsHistoryProviderRegistry::addEntries( const QList<QgsHistoryEntry> &entries, HistoryEntryOptions options )
145{
146 bool ok = true;
148 {
149 runEmptyQuery( QStringLiteral( "BEGIN TRANSACTION;" ) );
150 for ( const QgsHistoryEntry &entry : entries )
151 addEntry( entry, ok, options );
152 runEmptyQuery( QStringLiteral( "COMMIT TRANSACTION;" ) );
153 }
154
155 return ok;
156}
157
159{
160 ok = false;
161 switch ( backend )
162 {
164 {
165 if ( !mLocalDB )
166 {
167 QgsDebugMsg( QStringLiteral( "Cannot open database to query history entries" ) );
168 return QgsHistoryEntry( QVariantMap() );
169 }
170
171 QString sql = QStringLiteral( "SELECT provider_id, xml, timestamp FROM history WHERE id=%1" ).arg( id );
172
173 int nErr;
174 sqlite3_statement_unique_ptr statement = mLocalDB.prepare( sql, nErr );
175
176 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
177 {
178 QDomDocument doc;
179 if ( !doc.setContent( statement.columnAsText( 1 ) ) )
180 {
181 QgsDebugMsg( QStringLiteral( "Cannot read history entry" ) );
182 return QgsHistoryEntry( QVariantMap() );
183 }
184
185 ok = true;
186 return QgsHistoryEntry(
187 statement.columnAsText( 0 ),
188 QDateTime::fromString( statement.columnAsText( 2 ), QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ),
189 QgsXmlUtils::readVariant( doc.documentElement() ).toMap()
190 );
191 }
192
193 QgsDebugMsg( QStringLiteral( "Cannot find history item with matching ID" ) );
194 return QgsHistoryEntry( QVariantMap() );
195 }
196 }
198}
199
200bool QgsHistoryProviderRegistry::updateEntry( long long id, const QVariantMap &entry, Qgis::HistoryProviderBackend backend )
201{
202 switch ( backend )
203 {
205 {
206 QDomDocument xmlDoc;
207 xmlDoc.appendChild( QgsXmlUtils::writeVariant( entry, xmlDoc ) );
208 const QString entryXml = xmlDoc.toString();
209
210 QString query = qgs_sqlite3_mprintf( "UPDATE history SET xml='%q' WHERE id = %d;",
211 entryXml.toUtf8().constData(), id );
212 if ( !runEmptyQuery( query ) )
213 {
214 QgsDebugMsg( QStringLiteral( "Couldn't update history entry in database!" ) );
215 return false;
216 }
217 return true;
218 }
219 }
221}
222
223QList<QgsHistoryEntry> QgsHistoryProviderRegistry::queryEntries( const QDateTime &start, const QDateTime &end, const QString &providerId, Qgis::HistoryProviderBackends backends ) const
224{
225 QList<QgsHistoryEntry> entries;
227 {
228 if ( !mLocalDB )
229 {
230 QgsDebugMsg( QStringLiteral( "Cannot open database to query history entries" ) );
231 return {};
232 }
233
234 QString sql = QStringLiteral( "SELECT provider_id, xml, timestamp FROM history" );
235 QStringList whereClauses;
236 if ( !providerId.isEmpty() )
237 {
238 whereClauses.append( QStringLiteral( "provider_id='%1'" ).arg( providerId ) );
239 }
240 if ( start.isValid() )
241 {
242 whereClauses.append( QStringLiteral( "timestamp>='%1'" ).arg( start.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ) ) );
243 }
244 if ( end.isValid() )
245 {
246 whereClauses.append( QStringLiteral( "timestamp<='%1'" ).arg( end.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ) ) );
247 }
248
249 if ( !whereClauses.empty() )
250 sql += QStringLiteral( " WHERE (" ) + whereClauses.join( QLatin1String( ") AND (" ) ) + ')';
251
252 int nErr;
253 sqlite3_statement_unique_ptr statement = mLocalDB.prepare( sql, nErr );
254
255 while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
256 {
257 QDomDocument doc;
258 if ( !doc.setContent( statement.columnAsText( 1 ) ) )
259 {
260 QgsDebugMsg( QStringLiteral( "Cannot read history entry" ) );
261 continue;
262 }
263
264 entries.append( QgsHistoryEntry(
265 statement.columnAsText( 0 ),
266 QDateTime::fromString( statement.columnAsText( 2 ), QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ),
267 QgsXmlUtils::readVariant( doc.documentElement() ).toMap()
268 ) );
269 }
270 }
271
272 return entries;
273}
274
276{
277 return QgsApplication::qgisSettingsDirPath() + QStringLiteral( "user-history.db" );
278}
279
281{
282 switch ( backend )
283 {
285 runEmptyQuery( QStringLiteral( "DELETE from history;" ) );
286 break;
287 }
288 return true;
289}
290
291bool QgsHistoryProviderRegistry::createDatabase( const QString &filename, QString &error )
292{
293 error.clear();
294 if ( !openDatabase( filename, error ) )
295 {
296 QgsDebugMsg( error );
297 return false;
298 }
299
300 createTables();
301
302 return true;
303}
304
305bool QgsHistoryProviderRegistry::openDatabase( const QString &filename, QString &error )
306{
307 int rc = mLocalDB.open( filename );
308 if ( rc )
309 {
310 error = tr( "Couldn't open the history database: %1" ).arg( mLocalDB.errorMessage() );
311 return false;
312 }
313
314 return true;
315}
316
317void QgsHistoryProviderRegistry::createTables()
318{
319 QString query = qgs_sqlite3_mprintf( "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 QgsDebugMsg( zErr );
342 sqlite3_free( zErr );
343 }
344
345 return nErr == SQLITE_OK;
346}
347
HistoryProviderBackend
History provider backends.
Definition: qgis.h:2074
@ LocalProfile
Local profile.
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.
Encapsulates a history entry.
QDateTime timestamp
Entry timestamp.
QString providerId
Associated history provider ID.
QgsHistoryEntry(const QString &providerId, const QDateTime &timestamp, const QVariantMap &entry)
Constructor for QgsHistoryEntry entry, with the specified providerId and timestamp.
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 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.
bool clearHistory(Qgis::HistoryProviderBackend backend)
Clears the history for the specified backend.
void addDefaultProviders()
Adds the default history providers to the registry.
QStringList providerIds() const
Returns a list of the registered provider IDs.
History provider for operations performed through the Processing framework.
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.
#define BUILTIN_UNREACHABLE
Definition: qgis.h:3586
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QString qgs_sqlite3_mprintf(const char *format,...)
Wraps sqlite3_mprintf() by automatically freeing the memory.