File indexing completed on 2024-04-28 15:08:50
0001 /* 0002 SPDX-FileCopyrightText: 2021 Valentin Boettcher <hiro at protagon.space; @hiro98:tchncs.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #pragma once 0008 0009 #include <QSqlDatabase> 0010 #include <QSqlError> 0011 #include <exception> 0012 #include <list> 0013 #include <QString> 0014 #include <QList> 0015 #include <catalogsdb_debug.h> 0016 #include <QSqlQuery> 0017 #include <QMutex> 0018 #include <QObject> 0019 #include <QThread> 0020 0021 #include "polyfills/qstring_hash.h" 0022 #include <unordered_map> 0023 #include <queue> 0024 0025 #include <unordered_set> 0026 #include <utility> 0027 #include "catalogobject.h" 0028 #include "nan.h" 0029 #include "typedef.h" 0030 0031 namespace CatalogsDB 0032 { 0033 /** 0034 * A simple struct to hold information about catalogs. 0035 */ 0036 struct Catalog 0037 { 0038 /** 0039 * The catalog id. 0040 */ 0041 int id = -1; 0042 0043 /** 0044 * The catalog mame. 0045 */ 0046 QString name = "Unknown"; 0047 0048 /** 0049 * The precedence level of a catalog. 0050 * 0051 * If doublicate objects exist in the database, the one from the 0052 * catalog with the highest precedence winns. 0053 */ 0054 double precedence = 0; 0055 0056 /** 0057 * The author of the catalog. 0058 */ 0059 QString author = ""; 0060 0061 /** 0062 * The catalog source. 0063 */ 0064 QString source = ""; 0065 0066 /** 0067 * A (short) description for the catalog. 0068 * QT html is allowed. 0069 */ 0070 QString description = ""; 0071 0072 /** 0073 * Wether the catalog is mutable. 0074 */ 0075 bool mut = false; 0076 0077 /** 0078 * Wether the catalog is enabled. 0079 */ 0080 bool enabled = false; 0081 0082 /** 0083 * The catalog version. 0084 */ 0085 int version = -1; 0086 0087 /** 0088 * The catalog color in the form `[default color];[scheme file 0089 * name];[color]...`. 0090 */ 0091 QString color = ""; 0092 0093 /** 0094 * The catalog license. 0095 */ 0096 QString license = ""; 0097 0098 /** 0099 * The catalog maintainer. 0100 */ 0101 QString maintainer = ""; 0102 0103 /** 0104 * Build time of the catalog. Usually only catalogs with the same 0105 * timestamp can be considered dedublicated. 0106 * 0107 * A `null` timestamp indicates that the catalog has not been 0108 * built by the catalog repository. 0109 */ 0110 QDateTime timestamp{}; 0111 }; 0112 0113 const Catalog cat_defaults{}; 0114 0115 /** 0116 * Holds statistical information about the objects in a catalog. 0117 */ 0118 struct CatalogStatistics 0119 { 0120 std::map<SkyObject::TYPE, int> object_counts; 0121 int total_count = 0; 0122 }; 0123 0124 const QString db_file_extension = "kscat"; 0125 constexpr int application_id = 0x4d515158; 0126 constexpr int custom_cat_min_id = 1000; 0127 constexpr int user_catalog_id = 0; 0128 constexpr float default_maglim = 99; 0129 const QString flux_unit = "mag"; 0130 const QString flux_frequency = "400 nm"; 0131 using CatalogColorMap = std::map<QString, QColor>; 0132 using ColorMap = std::map<int, CatalogColorMap>; 0133 using CatalogObjectList = std::list<CatalogObject>; 0134 using CatalogObjectVector = std::vector<CatalogObject>; 0135 0136 /** 0137 * \returns A hash table of the form `color scheme: color` by 0138 * parsing a string of the form `[default color];[scheme file 0139 * name];[color]...`. 0140 */ 0141 CatalogColorMap parse_color_string(const QString &str); 0142 0143 /** 0144 * \returns A color string of the form`[default color];[scheme file 0145 * name];[color]...`. 0146 * 0147 * The inverse of `CatalogsDB::parse_color_string`. 0148 */ 0149 QString to_color_string(CatalogColorMap colors); 0150 0151 /** 0152 * Manages the catalog database and provides an interface to provide an 0153 * interface to query and modify the database. For more information on 0154 * how the catalog database system works see the KStars Handbook. 0155 * 0156 * The class manages a database connection which is assumed to be 0157 * working (invariant). If the database can't be accessed a 0158 * DatabaseError is thrown upon construction. The manager is designed 0159 * to hold as little state as possible because the database should be 0160 * the single source of truth. Prepared statements are made class 0161 * members, only if they are performance critical. 0162 * 0163 * Most methods in this class are thread safe. 0164 * 0165 * The intention is that you access a/the catalogs database directly 0166 * locally in the code where objects from the database are required and 0167 * not through layers of references and pointers. 0168 * 0169 * The main DSO database can be accessed as follows: 0170 * ```cpp 0171 * CatalogsDB::DBManager manager{ CatalogsDB::dso_db_path() }; 0172 * for(auto& o : manager.get_objects(10)) { 0173 * // do something 0174 * } 0175 * ``` 0176 * 0177 * To query the database, first check if the required query is already 0178 * hardcoded into the `DBManager`. If this is not the case you can either 0179 * add it (if it is performance critical and executed frequently) or use 0180 * `DBManager::general_master_query` to construct a custom `SQL` query. 0181 */ 0182 class DBManager 0183 { 0184 public: 0185 /** 0186 * Constructs a database manager from the \p filename which is 0187 * resolved to a path in the kstars data directory. 0188 * 0189 * The constructor resolves the path to the database, opens it 0190 * (throws if that does not work), checks the database version 0191 * (throws if that does not match), initializes the database, 0192 * registers the user catalog and updates the all_catalog_view. 0193 */ 0194 DBManager(const QString &filename); 0195 DBManager(const DBManager &other); 0196 0197 DBManager &operator=(DBManager other) 0198 { 0199 using std::swap; 0200 DBManager tmp{ other }; 0201 0202 m_db_file = other.m_db_file; 0203 swap(m_db, other.m_db); 0204 swap(m_q_cat_by_id, other.m_q_cat_by_id); 0205 swap(m_q_obj_by_trixel, other.m_q_obj_by_trixel); 0206 swap(m_q_obj_by_trixel_no_nulls, other.m_q_obj_by_trixel_no_nulls); 0207 swap(m_q_obj_by_trixel_null_mag, other.m_q_obj_by_trixel_null_mag); 0208 swap(m_q_obj_by_name, other.m_q_obj_by_name); 0209 swap(m_q_obj_by_name_exact, other.m_q_obj_by_name_exact); 0210 swap(m_q_obj_by_lim, other.m_q_obj_by_lim); 0211 swap(m_q_obj_by_maglim, other.m_q_obj_by_maglim); 0212 swap(m_q_obj_by_maglim_and_type, other.m_q_obj_by_maglim_and_type); 0213 swap(m_q_obj_by_oid, other.m_q_obj_by_oid); 0214 0215 return *this; 0216 }; 0217 0218 ~DBManager() 0219 { 0220 m_db.commit(); 0221 m_db.close(); 0222 } 0223 0224 /** 0225 * @return the filename of the database 0226 */ 0227 const QString &db_file_name() const { return m_db_file; }; 0228 0229 /** 0230 * @return wether the catalog with the \p id has been found and 0231 * the catalog. 0232 * 0233 * @todo use std::optional when transitioning to c++17 0234 */ 0235 const std::pair<bool, Catalog> get_catalog(const int id); 0236 0237 /** 0238 * \return a vector with all catalogs from the database. If \p 0239 * include_disabled is `true`, disabled catalogs will be included. 0240 */ 0241 const std::vector<Catalog> get_catalogs(bool include_disabled = false); 0242 0243 /** 0244 * @return true if the catalog with \p id exists 0245 * 0246 * @todo use std::optional when transitioning to c++17 0247 */ 0248 bool catalog_exists(const int id); 0249 0250 /** 0251 * @return return a vector of objects in the trixel with \p id. 0252 */ 0253 inline CatalogObjectVector get_objects_in_trixel(const int trixel) { 0254 return _get_objects_in_trixel_generic(m_q_obj_by_trixel, trixel); 0255 } 0256 0257 /** 0258 * @return return a vector of objects of known mag in the trixel with \p id. 0259 */ 0260 inline CatalogObjectVector get_objects_in_trixel_no_nulls(const int trixel) { 0261 return _get_objects_in_trixel_generic(m_q_obj_by_trixel_no_nulls, trixel); 0262 } 0263 0264 /** 0265 * @return return a vector of objects of unknown mag in the trixel with \p id. 0266 */ 0267 inline CatalogObjectVector get_objects_in_trixel_null_mag(const int trixel) { 0268 return _get_objects_in_trixel_generic(m_q_obj_by_trixel_null_mag, trixel); 0269 } 0270 0271 /** 0272 * \brief Find an objects by name. 0273 * 0274 * This will search the `name`, `long_name` and `catalog_identifier` 0275 * fields in all enabled catalogs for \p `name` and then return a new 0276 * instance of `CatalogObject` sourced from the master catalog. 0277 * 0278 * \param limit Upper limit to the quanitity of results. `-1` means "no 0279 * limit" 0280 * \param exactMatchOnly If true, the supplied name must match exactly 0281 * 0282 * \return a list of matching objects 0283 */ 0284 CatalogObjectList find_objects_by_name(const QString &name, const int limit = -1, 0285 const bool exactMatchOnly = false); 0286 0287 /** 0288 * \brief Find an objects by name in the catalog with \p `catalog_id`. 0289 * 0290 * \return a list of matching objects 0291 */ 0292 CatalogObjectList find_objects_by_name(const int catalog_id, const QString &name, 0293 const int limit = -1); 0294 0295 /** 0296 * \brief Find an objects by searching the name four wildcard. See 0297 * the LIKE sqlite statement. 0298 * 0299 * \return a list of matching objects 0300 */ 0301 CatalogObjectList find_objects_by_wildcard(const QString &wildcard, 0302 const int limit = -1); 0303 /** 0304 * \brief Find an objects by searching the master catlog with a 0305 * query like `SELECT ... FROM master WHERE \p where ORDER BY \p 0306 * order_by ...`. 0307 * 0308 * To be used if performance does not matter (much). 0309 * \p order_by can be ommitted. 0310 * 0311 * \return wether the query was successful, an error message if 0312 * any and a list of matching objects 0313 */ 0314 std::tuple<bool, const QString, CatalogObjectList> 0315 general_master_query(const QString &where, const QString &order_by = "", 0316 const int limit = -1); 0317 0318 /** 0319 * \brief Get an object by \p `oid`. Optinally a \p `catalog_id` can be speicfied. 0320 * 0321 * \returns if the object was found and the object itself 0322 */ 0323 std::pair<bool, CatalogObject> get_object(const CatalogObject::oid &oid); 0324 std::pair<bool, CatalogObject> get_object(const CatalogObject::oid &oid, 0325 const int catalog_id); 0326 0327 /** 0328 * Get \p limit objects with magnitude smaller than \p maglim (smaller = 0329 * brighter) from the database. 0330 */ 0331 CatalogObjectList get_objects(float maglim = default_maglim, int limit = -1); 0332 0333 /** 0334 * Get all objects from the database. 0335 */ 0336 CatalogObjectList get_objects_all(); 0337 0338 /** 0339 * Get \p limit objects of \p type with magnitude smaller than \p 0340 * maglim (smaller = brighter) from the database. Optionally one 0341 * can filter by \p `catalog_id`. 0342 */ 0343 CatalogObjectList get_objects(SkyObject::TYPE type, float maglim = default_maglim, 0344 int limit = -1); 0345 /** 0346 * Get \p limit objects from the catalog with \p `catalog_id` of 0347 * \p type with magnitude smaller than \p maglim (smaller = 0348 * brighter) from the database. Optionally one can filter by \p 0349 * `catalog_id`. 0350 */ 0351 CatalogObjectList get_objects_in_catalog(SkyObject::TYPE type, const int catalog_id, 0352 float maglim = default_maglim, 0353 int limit = -1); 0354 0355 /** 0356 * @return return the htmesh level used by the catalog db 0357 */ 0358 int htmesh_level() const { return m_htmesh_level; }; 0359 0360 /** 0361 * \brief Enable or disable a catalog. 0362 * \return `true` in case of succes, `false` and an error message in case 0363 * of an error 0364 * 0365 * This will recreate the master table. 0366 */ 0367 std::pair<bool, QString> set_catalog_enabled(const int id, const bool enabled); 0368 0369 /** 0370 * \brief remove a catalog 0371 * \return `true` in case of succes, `false` and an error message 0372 * in case of an error 0373 * 0374 * This will recreate the master table. 0375 */ 0376 std::pair<bool, QString> remove_catalog(const int id); 0377 0378 /** 0379 * Add a `CatalogObject` to a table with \p `catalog_id`. For the rest of 0380 * the arguments see `CatalogObject::CatalogObject`. 0381 * 0382 * \returns wether the operation was successful and if not, an error 0383 * message 0384 */ 0385 std::pair<bool, QString> add_object(const int catalog_id, const SkyObject::TYPE t, 0386 const CachingDms &r, const CachingDms &d, 0387 const QString &n, const float m = NaN::f, 0388 const QString &lname = QString(), 0389 const QString &catalog_identifier = QString(), 0390 const float a = 0.0, const float b = 0.0, 0391 const double pa = 0.0, const float flux = 0); 0392 0393 /** 0394 * Add the \p `object` to a table with \p `catalog_id`. For the 0395 * rest of the arguments see `CatalogObject::CatalogObject`. 0396 * 0397 * \returns wether the operation was successful and if not, an 0398 * error message 0399 */ 0400 std::pair<bool, QString> add_object(const int catalog_id, const CatalogObject &obj); 0401 0402 /** 0403 * Add the \p `objects` to a table with \p `catalog_id`. For the 0404 * rest of the arguments see `CatalogObject::CatalogObject`. 0405 * 0406 * \returns wether the operation was successful and if not, an 0407 * error message 0408 */ 0409 std::pair<bool, QString> add_objects(const int catalog_id, 0410 const CatalogObjectVector &objects); 0411 0412 /** 0413 * Remove the catalog object with the \p `oid` from the catalog with the 0414 * \p `catalog_id`. 0415 * 0416 * Refreshes the master catalog. 0417 * 0418 * \returns wether the operation was successful and if not, an 0419 * error message 0420 */ 0421 std::pair<bool, QString> remove_object(const int catalog_id, 0422 const CatalogObject::oid &id); 0423 0424 /** 0425 * Dumps the catalog with \p `id` into the file under the path \p 0426 * `file_path`. This file can then be imported with 0427 * `import_catalog`. If the file already exists, it will be 0428 * overwritten. 0429 * 0430 * The `user_version` and `application_id` pragmas are set to special 0431 * values, but otherwise the dump format is equal to the internal 0432 * database format. 0433 * 0434 * \returns wether the operation was successful and if not, an error 0435 * message 0436 */ 0437 std::pair<bool, QString> dump_catalog(int catalog_id, QString file_path); 0438 0439 /** 0440 * Loads a dumped catalog from path \p `file_path`. Will overwrite 0441 * an existing catalog if \p `overwrite` is set to true. Immutable 0442 * catalogs are overwritten by default. 0443 * 0444 * Checks if the pragma `application_id` matches 0445 * `CatalogsDB::application_id` and the pragma `user_version` to match 0446 * the database format version. 0447 * 0448 * \returns wether the operation was successful and if not, an error 0449 * message 0450 */ 0451 std::pair<bool, QString> import_catalog(const QString &file_path, 0452 const bool overwrite = false); 0453 /** 0454 * Registers a new catalog in the database. 0455 * 0456 * For the parameters \sa Catalog. The catalog gets inserted into 0457 * `m_catalogs`. The `all_catalog_view` is updated. 0458 * 0459 * \return true in case of success, false in case of an error 0460 * (along with the error) 0461 */ 0462 std::pair<bool, QString> 0463 register_catalog(const int id, const QString &name, const bool mut, 0464 const bool enabled, const double precedence, 0465 const QString &author = cat_defaults.author, 0466 const QString &source = cat_defaults.source, 0467 const QString &description = cat_defaults.description, 0468 const int version = cat_defaults.version, 0469 const QString &color = cat_defaults.color, 0470 const QString &license = cat_defaults.license, 0471 const QString &maintainer = cat_defaults.maintainer, 0472 const QDateTime ×tamp = cat_defaults.timestamp); 0473 0474 std::pair<bool, QString> register_catalog(const Catalog &cat); 0475 0476 /** 0477 * Update the metatadata \p `catalog`. 0478 * 0479 * The updated fields are: title, author, source, description. 0480 * 0481 * \return true in case of success, false in case of an error 0482 * (along with the error). 0483 */ 0484 std::pair<bool, QString> update_catalog_meta(const Catalog &cat); 0485 0486 /** 0487 * Clone objects from the catalog with \p `id_1` to another with `id_2`. Useful to create a 0488 * custom catalog from an immutable one. 0489 */ 0490 std::pair<bool, QString> copy_objects(const int id_1, const int id_2); 0491 0492 /** 0493 * Finds the smallest free id for a catalog. 0494 */ 0495 int find_suitable_catalog_id(); 0496 0497 /** 0498 * \returns statistics about the master catalog. 0499 */ 0500 const std::pair<bool, CatalogStatistics> get_master_statistics(); 0501 0502 /** 0503 * \returns statistics about the catalog with \p `catalog_id`. 0504 */ 0505 const std::pair<bool, CatalogStatistics> get_catalog_statistics(const int catalog_id); 0506 0507 /** 0508 * Compiles the master catalog by merging the individual catalogs based 0509 * on `oid` and precedence and creates an index by (trixel, magnitude) on 0510 * the master table. **Caution** you may want to call 0511 * `update_catalog_views` beforhand. 0512 * 0513 * @return true in case of success, false in case of an error 0514 */ 0515 bool compile_master_catalog(); 0516 0517 /** 0518 * Updates the all_catalog_view so that it includes all known 0519 * catalogs. 0520 * 0521 * @return true in case of success, false in case of an error 0522 */ 0523 bool update_catalog_views(); 0524 0525 /** \returns the catalog colors as a hash table of the form `catalog id: 0526 * scheme: color`. 0527 * 0528 * The colors are loaded from the `Catalog::color` field and the 0529 * `SqlStatements::color_table` in that order. 0530 */ 0531 ColorMap get_catalog_colors(); 0532 0533 /** \returns the catalog colors as a hash table of for the catalog 0534 * with \p id in the form `scheme: color`. 0535 * 0536 * The colors are loaded from the `Catalog::color` field and the 0537 * `SqlStatements::color_table` in that order. 0538 */ 0539 CatalogColorMap get_catalog_colors(const int id); 0540 0541 /** Saves the configures colors of the catalog with id \p id in \p 0542 * colors into the database. \returns wether the insertion was 0543 * possible and an error message if not. 0544 */ 0545 std::pair<bool, QString> insert_catalog_colors(const int id, 0546 const CatalogColorMap &colors); 0547 0548 private: 0549 /** 0550 * The backing catalog database. 0551 */ 0552 QSqlDatabase m_db; 0553 0554 /** 0555 * The filename of the database. 0556 * 0557 * Will be a reference to a member of `m_db_paths`. 0558 */ 0559 QString m_db_file; 0560 0561 //@{ 0562 /** 0563 * Some performance criticall sql queries are prepared stored as memebers. 0564 * When using those queries `m_mutex` should be locked! 0565 * 0566 * \sa prepare_queries 0567 */ 0568 0569 QSqlQuery m_q_cat_by_id; 0570 QSqlQuery m_q_obj_by_trixel; 0571 QSqlQuery m_q_obj_by_trixel_null_mag; 0572 QSqlQuery m_q_obj_by_trixel_no_nulls; 0573 QSqlQuery m_q_obj_by_name; 0574 QSqlQuery m_q_obj_by_name_exact; 0575 QSqlQuery m_q_obj_by_lim; 0576 QSqlQuery m_q_obj_by_maglim; 0577 QSqlQuery m_q_obj_by_maglim_and_type; 0578 QSqlQuery m_q_obj_by_oid; 0579 //@} 0580 0581 /** 0582 * The level of the htmesh used to index the catalog entries. 0583 * 0584 * If the htmesh level of a catalog is different, the catalog will 0585 * be reindexed upon importing it. 0586 * 0587 * A value of -1 means that the htmesh-level has not been 0588 * deterined yet. 0589 */ 0590 int m_htmesh_level = -1; 0591 0592 /** 0593 * The version of the database. 0594 * 0595 * A value of -1 means that the htmesh-level has not been 0596 * deterined yet. 0597 */ 0598 int m_db_version = -1; 0599 0600 /** 0601 * A simple mutex to be locked when using prepared statements, 0602 * that are stored in the class. 0603 */ 0604 QMutex m_mutex; 0605 0606 //@{ 0607 /** 0608 * Helpers 0609 */ 0610 0611 /** 0612 * Initializes the database with the minimum viable tables. 0613 * 0614 * The catalog registry is created and the database version is set 0615 * to SqlStatements::current_db_version and the htmesh-level is 0616 * set to SqlStatements::default_htmesh_level if they don't exist. 0617 * 0618 * @return true in case of success, false in case of an error 0619 */ 0620 bool initialize_db(); 0621 0622 /** 0623 * Reads the database version and the htmesh level from the 0624 * database. If the meta table does not exist, the default vaulues 0625 * SqlStatements::current_db_version and 0626 * SqlStatements::default_htmesh_level. 0627 * 0628 * @return [version, htmesh-level, is-init?] 0629 */ 0630 std::tuple<int, int, bool> get_db_meta(); 0631 0632 /** 0633 * Gets a vector of catalog ids of catalogs. If \p include_disabled is 0634 * `true`, disabled catalogs will be included. 0635 */ 0636 std::vector<int> get_catalog_ids(bool include_enabled = false); 0637 0638 /** 0639 * Prepares performance critical sql queries. 0640 * 0641 * @return [success, error] 0642 */ 0643 std::pair<bool, QSqlError> prepare_queries(); 0644 0645 /** 0646 * Read a `CatalogObject` from the tip of the \p query. 0647 */ 0648 CatalogObject read_catalogobject(const QSqlQuery &query) const; 0649 0650 /** 0651 * Read the first `CatalogObject` from the tip of the \p `query` 0652 * that hasn't been exec'd yet. 0653 */ 0654 std::pair<bool, CatalogObject> read_first_object(QSqlQuery &query) const; 0655 0656 /** 0657 * Read all `CatalogObject`s from the \p query. 0658 */ 0659 CatalogObjectList fetch_objects(QSqlQuery &query) const; 0660 0661 /** 0662 * Internal implementation to forcably remove a catalog (even the 0663 * user catalog, use with caution!) 0664 */ 0665 std::pair<bool, QString> remove_catalog_force(const int id); 0666 0667 /** 0668 * 0669 */ 0670 CatalogObjectVector _get_objects_in_trixel_generic(QSqlQuery &query, const int trixel); 0671 0672 //@} 0673 }; 0674 0675 /** 0676 * Database related error, thrown when database access fails or an 0677 * action does not succeed. 0678 * 0679 * QSqlError is not used here to encapsulate the database further. 0680 */ 0681 class DatabaseError : std::exception 0682 { 0683 public: 0684 enum class ErrorType 0685 { 0686 OPEN, 0687 VERSION, 0688 INIT, 0689 CREATE_CATALOG, 0690 CREATE_MASTER, 0691 NOT_FOUND, 0692 PREPARE, 0693 UNKNOWN 0694 }; 0695 0696 DatabaseError(QString message, ErrorType type = ErrorType::UNKNOWN, 0697 const QSqlError &error = QSqlError()) 0698 : m_message{ std::move(message) }, m_type{ type }, m_error{ error }, m_report{ 0699 m_message.toStdString() + 0700 (error.text().length() > 0 ? "\nSQL ERROR: " + error.text().toStdString() : 0701 std::string("")) 0702 } {}; 0703 0704 const char *what() const noexcept override { return m_report.c_str(); } 0705 const QString &message() const noexcept { return m_message; } 0706 ErrorType type() const noexcept { return m_type; } 0707 0708 private: 0709 const QString m_message; 0710 const ErrorType m_type; 0711 const QSqlError m_error; 0712 const std::string m_report; 0713 }; 0714 0715 /** \returns the path to the dso database */ 0716 QString dso_db_path(); 0717 0718 /** \returns true and a catalog if the catalog metadata (name, author, 0719 ...) can be read */ 0720 std::pair<bool, Catalog> read_catalog_meta_from_file(const QString &path); 0721 0722 0723 0724 0725 /** 0726 * A concurrent wrapper around \sa CatalogsDB::DBManager 0727 * 0728 * This wrapper can be instantiated from the main thread. It spawns 0729 * its own thread and moves itself to the thread, allowing the main 0730 * thread to call DB operations without blocking the user 0731 * interface. It provides a generic wrapper interface, \sa 0732 * AsyncDBManager::execute(), to call the methods of DBManager. 0733 * 0734 * Since it is hard to use signal-slot communication with a generic 0735 * wrapper like \sa AsyncDBManager::execute(), a wrapper is provided 0736 * for the two most likely overloads of \sa 0737 * DBManager::find_objects_by_name, which are time-consuming and 0738 * frequently used, and these can be directly invoked from 0739 * QMetaObject::invokeMethod or an appropriate signal 0740 * 0741 * The void override of \sa AsyncDBManager::init() does the most 0742 * commonly done thing, which is to open the DSO database 0743 * 0744 */ 0745 class AsyncDBManager : public QObject { 0746 // Note: Follows the active object pattern described here 0747 // https://youtu.be/SncJ3D-fO7g?list=PL6CJYn40gN6jgr-Rpl3J4XDQYhmUnxb-g&t=272 0748 Q_OBJECT 0749 0750 private: 0751 std::shared_ptr<DBManager> m_manager; 0752 std::queue<std::unique_ptr<CatalogObjectList>> m_result; 0753 std::shared_ptr<QThread> m_thread; 0754 QMutex m_resultMutex; 0755 0756 public: 0757 template <typename... Args> 0758 using DBManagerMethod = CatalogObjectList (DBManager::*)(Args...); 0759 0760 /** 0761 * Constructor, does nothing. Call init() to do the actual setup after the QThread stars 0762 */ 0763 template <typename... Args> 0764 AsyncDBManager(Args... args) 0765 : QObject(nullptr) 0766 , m_manager(nullptr) 0767 , m_thread(nullptr) 0768 { 0769 m_thread.reset(new QThread); 0770 moveToThread(m_thread.get()); 0771 connect(m_thread.get(), &QThread::started, [&]() { 0772 init(args...); 0773 }); 0774 m_thread->start(); 0775 0776 /* 0777 * This is an attempt to fix a bug introduced in 5a2ba9f8e8b275f44b7593a50ca66f09cb2f985d 0778 * where KStars Crashes on MacOS when launched by double clicking (NOT by Terminal or QT Creator) 0779 * and then the find dialog is accessed. For some reason, this fixes it? 0780 */ 0781 #ifdef Q_OS_OSX 0782 QThread::msleep(100); 0783 #endif 0784 } 0785 0786 ~AsyncDBManager() 0787 { 0788 QMetaObject::invokeMethod(this, "cleanup"); 0789 m_thread->wait(); 0790 } 0791 0792 /** 0793 * Return a pointer to the DBManager, for non-DB functionalities 0794 */ 0795 inline std::shared_ptr<DBManager> manager() { return m_manager; } 0796 0797 /** 0798 * Return a pointer to the QThread object 0799 */ 0800 inline std::shared_ptr<QThread> thread() { return m_thread; } 0801 0802 /** 0803 * Construct the DBManager object 0804 * 0805 * Should be done in the thread corresponding to this object 0806 */ 0807 template <typename... Args> void init(Args&&... args) 0808 { 0809 m_manager = std::make_shared<DBManager>(std::forward<Args>(args)...); 0810 } 0811 0812 /** 0813 * A generic wrapper to call any method on DBManager that returns a CatalogObjectList 0814 * 0815 * For practical use examples, see \sa 0816 * AsyncDBManager::find_objects_by_name below which uses this 0817 * wrapper 0818 */ 0819 template <typename... Args> 0820 void execute(DBManagerMethod<Args...> dbManagerMethod, Args... args) 0821 { 0822 // c.f. https://stackoverflow.com/questions/25392935/wrap-a-function-pointer-in-c-with-variadic-template 0823 0824 // N.B. The perfect forwarding idiom is not used because we 0825 // also want to be able to bind to lvalue references, whereas 0826 // the types deduced for the arguments has to match the type 0827 // deduced to identify the function overload to be used. 0828 QMutexLocker _{&m_resultMutex}; 0829 m_result.emplace( 0830 std::make_unique<CatalogObjectList>((m_manager.get()->*dbManagerMethod)(args...)) 0831 ); 0832 emit resultReady(); 0833 } 0834 0835 /** 0836 * execute(), but specialized to find_objects_by_name 0837 * 0838 * @fixme Code duplication needed to support older compilers 0839 */ 0840 void _find_objects_by_name(const QString& name, const int limit, const bool exactMatchOnly) 0841 { 0842 // FIXME: Remove this and use execute() once C++1x compilers 0843 // support variadic template type deduction properly 0844 QMutexLocker _{&m_resultMutex}; 0845 m_result.emplace( 0846 std::make_unique<CatalogObjectList>((m_manager.get()->find_objects_by_name)(name, limit, exactMatchOnly)) 0847 ); 0848 emit resultReady(); 0849 } 0850 0851 signals: 0852 void resultReady(void); 0853 void threadReady(void); 0854 0855 0856 public slots: 0857 0858 void init() 0859 { 0860 m_manager = std::make_shared<DBManager>(dso_db_path()); 0861 } 0862 0863 /** 0864 * Calls the given DBManager method, storing the result for later retrieval 0865 * 0866 * \p dbManagerMethod method to execute (must return a CatalogObjectList) 0867 * \p args arguments to supply to the method 0868 */ 0869 0870 void find_objects_by_name(const QString& name, const int limit = -1) 0871 { 0872 // N.B. One cannot have a function pointer to a function with 0873 // default arguments, so all arguments must be supplied here 0874 0875 // FIXME: Uncomment to use generic wrapper execute() once 0876 // compilers gain proper type-deduction support 0877 // execute<const QString&, const int, const bool>( 0878 // &DBManager::find_objects_by_name, 0879 // name, limit, false); 0880 0881 _find_objects_by_name(name, limit, false); 0882 } 0883 0884 void find_objects_by_name_exact(const QString &name) 0885 { 0886 // FIXME: Uncomment to use generic wrapper execute() once 0887 // compilers gain proper type-deduction support 0888 // execute<const QString&, const int, const bool>( 0889 // &DBManager::find_objects_by_name, 0890 // name, 1, true); 0891 0892 _find_objects_by_name(name, 1, true); 0893 } 0894 0895 /** 0896 * Returns the result of the previous call, or a nullptr if none exists 0897 */ 0898 std::unique_ptr<CatalogObjectList> result() 0899 { 0900 QMutexLocker _{&m_resultMutex}; 0901 if (m_result.empty()) 0902 { 0903 return std::unique_ptr<CatalogObjectList>(); 0904 } 0905 std::unique_ptr<CatalogObjectList> result = std::move(m_result.front()); 0906 m_result.pop(); 0907 return result; 0908 } 0909 0910 private slots: 0911 0912 void cleanup() 0913 { 0914 Q_ASSERT(m_manager.use_count() == 1); 0915 m_manager.reset(); 0916 m_thread->quit(); 0917 } 0918 0919 void emitReady() 0920 { 0921 emit threadReady(); 0922 } 0923 0924 }; 0925 0926 } // namespace CatalogsDB