QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 
32 QgsHistoryEntry::QgsHistoryEntry( const QString &providerId, const QDateTime &timestamp, const QVariantMap &entry )
33  : timestamp( timestamp )
34  , providerId( providerId )
35  , entry( entry )
36 {
37 
38 }
39 
40 QgsHistoryEntry::QgsHistoryEntry( const QVariantMap &entry )
41  : timestamp( QDateTime::currentDateTime() )
42  , entry( entry )
43 {
44 
45 }
46 
47 //
48 // QgsHistoryProviderRegistry
49 //
50 
51 QgsHistoryProviderRegistry::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 
114 long 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 
144 bool 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 
200 bool 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 
223 QList<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 
291 bool 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 
305 bool 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 
317 void 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 
331 bool 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 
Qgis::HistoryProviderBackend
HistoryProviderBackend
History provider backends.
Definition: qgis.h:1673
QgsHistoryProviderRegistry::~QgsHistoryProviderRegistry
~QgsHistoryProviderRegistry() override
Definition: qgshistoryproviderregistry.cpp:76
QgsHistoryProviderRegistry::HistoryEntryOptions::storageBackends
Qgis::HistoryProviderBackends storageBackends
Target storage backends.
Definition: qgshistoryproviderregistry.h:155
sqlite3_database_unique_ptr::open
int open(const QString &path)
Opens the database at the specified file path.
Definition: qgssqliteutils.cpp:78
qgsruntimeprofiler.h
QgsHistoryProviderRegistry::userHistoryDbPath
static QString userHistoryDbPath()
Returns the path to user's local history database.
Definition: qgshistoryproviderregistry.cpp:275
sqlite3_database_unique_ptr::prepare
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
Definition: qgssqliteutils.cpp:99
QgsHistoryProviderRegistry::addEntry
long long addEntry(const QString &providerId, const QVariantMap &entry, bool &ok, QgsHistoryProviderRegistry::HistoryEntryOptions options=QgsHistoryProviderRegistry::HistoryEntryOptions())
Adds an entry to the history logs.
Definition: qgshistoryproviderregistry.cpp:114
QgsHistoryEntry::timestamp
QDateTime timestamp
Entry timestamp.
Definition: qgshistoryproviderregistry.h:57
QgsHistoryProviderRegistry::entry
QgsHistoryEntry entry(long long id, bool &ok, Qgis::HistoryProviderBackend backend=Qgis::HistoryProviderBackend::LocalProfile) const
Returns the entry with matching ID, from the specified backend.
Definition: qgshistoryproviderregistry.cpp:158
QgsHistoryProviderRegistry::removeProvider
bool removeProvider(const QString &id)
Removes the provider with matching id.
Definition: qgshistoryproviderregistry.cpp:100
QgsHistoryEntry
Encapsulates a history entry.
Definition: qgshistoryproviderregistry.h:40
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsHistoryEntry::providerId
QString providerId
Associated history provider ID.
Definition: qgshistoryproviderregistry.h:60
QgsHistoryProviderRegistry::providerById
QgsAbstractHistoryProvider * providerById(const QString &id)
Returns the provider with matching id, or nullptr if no matching provider is registered.
Definition: qgshistoryproviderregistry.cpp:95
sqlite3_database_unique_ptr::errorMessage
QString errorMessage() const
Returns the most recent error message encountered by the database.
Definition: qgssqliteutils.cpp:94
qgs_sqlite3_mprintf
QString qgs_sqlite3_mprintf(const char *format,...)
Wraps sqlite3_mprintf() by automatically freeing the memory.
Definition: qgssqliteutils.cpp:312
QgsHistoryProviderRegistry::addDefaultProviders
void addDefaultProviders()
Adds the default history providers to the registry.
Definition: qgshistoryproviderregistry.cpp:81
QgsHistoryProviderRegistry::QgsHistoryProviderRegistry
QgsHistoryProviderRegistry(QObject *parent=nullptr, bool useMemoryDatabase=false)
Creates a new empty history provider registry.
Definition: qgshistoryproviderregistry.cpp:51
qgsapplication.h
QgsHistoryProviderRegistry::HistoryEntryOptions
Contains options for storing history entries.
Definition: qgshistoryproviderregistry.h:145
QgsApplication::qgisSettingsDirPath
static QString qgisSettingsDirPath()
Returns the path to the settings directory in user's home dir.
Definition: qgsapplication.cpp:1099
Qgis::HistoryProviderBackend::LocalProfile
@ LocalProfile
Local profile.
QgsHistoryEntry::QgsHistoryEntry
QgsHistoryEntry(const QString &providerId, const QDateTime &timestamp, const QVariantMap &entry)
Constructor for QgsHistoryEntry entry, with the specified providerId and timestamp.
Definition: qgshistoryproviderregistry.cpp:32
QgsXmlUtils::readVariant
static QVariant readVariant(const QDomElement &element)
Read a QVariant from a QDomElement.
Definition: qgsxmlutils.cpp:251
QgsHistoryProviderRegistry::updateEntry
bool updateEntry(long long id, const QVariantMap &entry, Qgis::HistoryProviderBackend backend=Qgis::HistoryProviderBackend::LocalProfile)
Updates the existing entry with matching id.
Definition: qgshistoryproviderregistry.cpp:200
QgsAbstractHistoryProvider::id
virtual QString id() const =0
Returns the provider's unique id, which is used to associate existing history entries with the provid...
BUILTIN_UNREACHABLE
#define BUILTIN_UNREACHABLE
Definition: qgis.h:2907
QgsHistoryProviderRegistry::providerIds
QStringList providerIds() const
Returns a list of the registered provider IDs.
Definition: qgshistoryproviderregistry.cpp:109
qgsxmlutils.h
QgsHistoryProviderRegistry::addProvider
bool addProvider(QgsAbstractHistoryProvider *provider)
Adds a provider to the registry.
Definition: qgshistoryproviderregistry.cpp:86
sqlite3_statement_unique_ptr::columnAsText
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
Definition: qgssqliteutils.cpp:61
QgsHistoryProviderRegistry::clearHistory
bool clearHistory(Qgis::HistoryProviderBackend backend)
Clears the history for the specified backend.
Definition: qgshistoryproviderregistry.cpp:280
qgshistoryprovider.h
QgsHistoryEntry::entry
QVariantMap entry
Entry details.
Definition: qgshistoryproviderregistry.h:68
QgsHistoryProviderRegistry::queryEntries
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.
Definition: qgshistoryproviderregistry.cpp:223
QgsScopedRuntimeProfile
Scoped object for logging of the runtime for a single operation or group of operations.
Definition: qgsruntimeprofiler.h:327
qgsprocessinghistoryprovider.h
QgsAbstractHistoryProvider
Abstract base class for objects which track user history (i.e.
Definition: qgshistoryprovider.h:35
QgsHistoryProviderRegistry::addEntries
bool addEntries(const QList< QgsHistoryEntry > &entries, QgsHistoryProviderRegistry::HistoryEntryOptions options=QgsHistoryProviderRegistry::HistoryEntryOptions())
Adds a list of entries to the history logs.
Definition: qgshistoryproviderregistry.cpp:144
qgslogger.h
QgsProcessingHistoryProvider
History provider for operations performed through the Processing framework.
Definition: qgsprocessinghistoryprovider.h:30
QgsXmlUtils::writeVariant
static QDomElement writeVariant(const QVariant &value, QDomDocument &doc)
Write a QVariant to a QDomElement.
Definition: qgsxmlutils.cpp:106
qgshistoryproviderregistry.h
sqlite3_statement_unique_ptr
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
Definition: qgssqliteutils.h:69