QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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 
HistoryProviderBackend
History provider backends.
Definition: qgis.h:1304
@ 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:2152
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QString qgs_sqlite3_mprintf(const char *format,...)
Wraps sqlite3_mprintf() by automatically freeing the memory.