QGIS API Documentation 3.41.0-Master (45a0abf3bec)
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');",
114 entry.providerId.toUtf8().constData(), entryXml.toUtf8().constData(), dateTime.toUtf8().constData() );
115 if ( !runEmptyQuery( query ) )
116 {
117 QgsDebugError( QStringLiteral( "Couldn't story history entry in database!" ) );
118 ok = false;
119 return -1;
120 }
121 id = static_cast< int >( sqlite3_last_insert_rowid( mLocalDB.get() ) );
122
123 QgsHistoryEntry addedEntry( entry );
124 addedEntry.id = id;
125
127 }
128
129 return id;
130}
131
132bool QgsHistoryProviderRegistry::addEntries( const QList<QgsHistoryEntry> &entries, HistoryEntryOptions options )
133{
134 bool ok = true;
136 {
137 runEmptyQuery( QStringLiteral( "BEGIN TRANSACTION;" ) );
138 for ( const QgsHistoryEntry &entry : entries )
139 addEntry( entry, ok, options );
140 runEmptyQuery( QStringLiteral( "COMMIT TRANSACTION;" ) );
141 }
142
143 return ok;
144}
145
147{
148 ok = false;
149 switch ( backend )
150 {
152 {
153 if ( !mLocalDB )
154 {
155 QgsDebugError( QStringLiteral( "Cannot open database to query history entries" ) );
156 return QgsHistoryEntry( QVariantMap() );
157 }
158
159 QString sql = QStringLiteral( "SELECT provider_id, xml, timestamp FROM history WHERE id=%1" ).arg( id );
160
161 int nErr;
162 sqlite3_statement_unique_ptr statement = mLocalDB.prepare( sql, nErr );
163
164 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
165 {
166 QDomDocument doc;
167 if ( !doc.setContent( statement.columnAsText( 1 ) ) )
168 {
169 QgsDebugError( QStringLiteral( "Cannot read history entry" ) );
170 return QgsHistoryEntry( QVariantMap() );
171 }
172
173 ok = true;
175 statement.columnAsText( 0 ),
176 QDateTime::fromString( statement.columnAsText( 2 ), QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ),
177 QgsXmlUtils::readVariant( doc.documentElement() ).toMap()
178 );
179 res.id = id;
180 return res;
181 }
182
183 QgsDebugError( QStringLiteral( "Cannot find history item with matching ID" ) );
184 return QgsHistoryEntry( QVariantMap() );
185 }
186 }
188}
189
190bool QgsHistoryProviderRegistry::updateEntry( long long id, const QVariantMap &entry, Qgis::HistoryProviderBackend backend )
191{
192 switch ( backend )
193 {
195 {
196 const QVariantMap cleanedMap = QgsProcessingUtils::removePointerValuesFromMap( entry );
197 QDomDocument xmlDoc;
198 xmlDoc.appendChild( QgsXmlUtils::writeVariant( cleanedMap, xmlDoc ) );
199 const QString entryXml = xmlDoc.toString();
200
201 QString query = qgs_sqlite3_mprintf( "UPDATE history SET xml='%q' WHERE id = %d;",
202 entryXml.toUtf8().constData(), id );
203 if ( !runEmptyQuery( query ) )
204 {
205 QgsDebugError( QStringLiteral( "Couldn't update history entry in database!" ) );
206 return false;
207 }
208
210 return true;
211 }
212 }
214}
215
216QList<QgsHistoryEntry> QgsHistoryProviderRegistry::queryEntries( const QDateTime &start, const QDateTime &end, const QString &providerId, Qgis::HistoryProviderBackends backends ) const
217{
218 QList<QgsHistoryEntry> entries;
220 {
221 if ( !mLocalDB )
222 {
223 QgsDebugError( QStringLiteral( "Cannot open database to query history entries" ) );
224 return {};
225 }
226
227 QString sql = QStringLiteral( "SELECT id, provider_id, xml, timestamp FROM history" );
228 QStringList whereClauses;
229 if ( !providerId.isEmpty() )
230 {
231 whereClauses.append( QStringLiteral( "provider_id='%1'" ).arg( providerId ) );
232 }
233 if ( start.isValid() )
234 {
235 whereClauses.append( QStringLiteral( "timestamp>='%1'" ).arg( start.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ) ) );
236 }
237 if ( end.isValid() )
238 {
239 whereClauses.append( QStringLiteral( "timestamp<='%1'" ).arg( end.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ) ) );
240 }
241
242 if ( !whereClauses.empty() )
243 sql += QStringLiteral( " WHERE (" ) + whereClauses.join( QLatin1String( ") AND (" ) ) + ')';
244
245 int nErr;
246 sqlite3_statement_unique_ptr statement = mLocalDB.prepare( sql, nErr );
247
248 while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
249 {
250 QDomDocument doc;
251 if ( !doc.setContent( statement.columnAsText( 2 ) ) )
252 {
253 QgsDebugError( QStringLiteral( "Cannot read history entry" ) );
254 continue;
255 }
256
258 statement.columnAsText( 1 ),
259 QDateTime::fromString( statement.columnAsText( 3 ), QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ),
260 QgsXmlUtils::readVariant( doc.documentElement() ).toMap()
261 );
262 entry.id = statement.columnAsInt64( 0 );
263
264 entries.append( entry );
265 }
266 }
267
268 return entries;
269}
270
272{
273 return QgsApplication::qgisSettingsDirPath() + QStringLiteral( "user-history.db" );
274}
275
277{
278 switch ( backend )
279 {
281 {
282 if ( providerId.isEmpty() )
283 runEmptyQuery( QStringLiteral( "DELETE from history;" ) );
284 else
285 runEmptyQuery( QStringLiteral( "DELETE from history WHERE provider_id='%1'" )
286 .arg( providerId ) );
287 break;
288 }
289 }
290 emit historyCleared( backend, providerId );
291 return true;
292}
293
294bool QgsHistoryProviderRegistry::createDatabase( const QString &filename, QString &error )
295{
296 error.clear();
297 if ( !openDatabase( filename, error ) )
298 {
299 QgsDebugError( error );
300 return false;
301 }
302
303 createTables();
304
305 return true;
306}
307
308bool QgsHistoryProviderRegistry::openDatabase( const QString &filename, QString &error )
309{
310 int rc = mLocalDB.open( filename );
311 if ( rc )
312 {
313 error = tr( "Couldn't open the history database: %1" ).arg( mLocalDB.errorMessage() );
314 return false;
315 }
316
317 return true;
318}
319
320void QgsHistoryProviderRegistry::createTables()
321{
322 QString query = qgs_sqlite3_mprintf( "CREATE TABLE history("\
323 "id INTEGER PRIMARY KEY,"\
324 "provider_id TEXT,"\
325 "xml TEXT,"\
326 "timestamp DATETIME);" \
327 "CREATE INDEX provider_index ON history(provider_id);"\
328 "CREATE INDEX timestamp_index ON history(timestamp);"
329 );
330
331 runEmptyQuery( query );
332}
333
334bool QgsHistoryProviderRegistry::runEmptyQuery( const QString &query )
335{
336 if ( !mLocalDB )
337 return false;
338
339 char *zErr = nullptr;
340 int nErr = sqlite3_exec( mLocalDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
341
342 if ( nErr != SQLITE_OK )
343 {
344 QgsDebugError( zErr );
345 sqlite3_free( zErr );
346 }
347
348 return nErr == SQLITE_OK;
349}
350
HistoryProviderBackend
History provider backends.
Definition qgis.h:3254
@ LocalProfile
Local profile.
QFlags< HistoryProviderBackend > HistoryProviderBackends
Definition qgis.h:3259
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:6612
#define QgsDebugError(str)
Definition qgslogger.h:38
QString qgs_sqlite3_mprintf(const char *format,...)
Wraps sqlite3_mprintf() by automatically freeing the memory.