data/FolderSuggestionStore.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "FolderSuggestionStore.h" | ||
| 2 | |||
| 3 | #include <QDateTime> | ||
| 4 | #include <QFileInfo> | ||
| 5 | #include <QSqlError> | ||
| 6 | #include <QSqlQuery> | ||
| 7 | #include <QUuid> | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | ✗ | bool fail(const QSqlQuery &query, QString *error) { | |
| 11 | ✗ | if (error) *error = query.lastError().text(); | |
| 12 | ✗ | return false; | |
| 13 | } | ||
| 14 | } // namespace | ||
| 15 | |||
| 16 | 433 | FolderSuggestionStore::~FolderSuggestionStore() { close(); } | |
| 17 | |||
| 18 | 866 | void FolderSuggestionStore::close() { | |
| 19 |
2/2✓ Branch 3 → 4 taken 433 times.
✓ Branch 3 → 5 taken 433 times.
|
866 | if (m_connection.isEmpty()) return; |
| 20 | 433 | m_db.close(); | |
| 21 |
2/2✓ Branch 6 → 7 taken 433 times.
✓ Branch 7 → 8 taken 433 times.
|
433 | m_db = QSqlDatabase(); |
| 22 | 433 | QSqlDatabase::removeDatabase(m_connection); | |
| 23 | 433 | m_connection.clear(); | |
| 24 | } | ||
| 25 | |||
| 26 | 433 | bool FolderSuggestionStore::open(const QString &predictorPath, QString *error) { | |
| 27 |
1/1✓ Branch 2 → 3 taken 433 times.
|
433 | close(); |
| 28 | // The training session creates the predictor file; this store never does. | ||
| 29 |
3/4✓ Branch 3 → 4 taken 433 times.
✓ Branch 4 → 5 taken 433 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 16 taken 433 times.
|
433 | if (!QFileInfo(predictorPath).isFile()) { |
| 30 | ✗ | if (error) *error = QStringLiteral("Folder prediction database is unavailable"); | |
| 31 | ✗ | return false; | |
| 32 | } | ||
| 33 |
3/3✓ Branch 16 → 17 taken 433 times.
✓ Branch 17 → 18 taken 433 times.
✓ Branch 21 → 22 taken 433 times.
|
866 | m_connection = QStringLiteral("folder_suggestions_") + QUuid::createUuid().toString(QUuid::WithoutBraces); |
| 34 |
2/2✓ Branch 30 → 31 taken 433 times.
✓ Branch 31 → 32 taken 433 times.
|
866 | m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connection); |
| 35 |
1/1✓ Branch 35 → 36 taken 433 times.
|
433 | m_db.setDatabaseName(predictorPath); |
| 36 |
1/1✓ Branch 39 → 40 taken 433 times.
|
866 | m_db.setConnectOptions(QStringLiteral("QSQLITE_BUSY_TIMEOUT=200")); |
| 37 |
2/3✓ Branch 42 → 43 taken 433 times.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 53 taken 433 times.
|
433 | if (!m_db.open()) { |
| 38 | ✗ | if (error) *error = m_db.lastError().text(); | |
| 39 | ✗ | close(); | |
| 40 | ✗ | return false; | |
| 41 | } | ||
| 42 |
1/1✓ Branch 53 → 54 taken 433 times.
|
433 | QSqlQuery query(m_db); |
| 43 |
2/3✓ Branch 57 → 58 taken 433 times.
✗ Branch 60 → 61 not taken.
✓ Branch 60 → 64 taken 433 times.
|
433 | if (!query.exec(QStringLiteral( |
| 44 | "CREATE TABLE IF NOT EXISTS folder_suggestions (" | ||
| 45 | " account TEXT NOT NULL, folder_id INTEGER NOT NULL, uid_validity INTEGER NOT NULL, uid INTEGER NOT NULL," | ||
| 46 | " primary_folder TEXT NOT NULL, primary_conf REAL NOT NULL, alternate_folder TEXT NOT NULL," | ||
| 47 | " alternate_conf REAL NOT NULL, threshold REAL NOT NULL, reason INTEGER NOT NULL, support INTEGER NOT NULL," | ||
| 48 | " total INTEGER NOT NULL, stored_at INTEGER NOT NULL," | ||
| 49 | " PRIMARY KEY (account, folder_id, uid_validity, uid)) WITHOUT ROWID"))) { | ||
| 50 | ✗ | fail(query, error); | |
| 51 | ✗ | close(); | |
| 52 | ✗ | return false; | |
| 53 | } | ||
| 54 | 433 | return true; | |
| 55 | 433 | } | |
| 56 | |||
| 57 | 264 | QHash<qint64, FolderSuggestion> FolderSuggestionStore::read(const QString &account, qint64 folderId, | |
| 58 | quint32 uidValidity, const QList<qint64> &uids, | ||
| 59 | QString *error) const { | ||
| 60 | 264 | QHash<qint64, FolderSuggestion> stored; | |
| 61 |
4/7✓ Branch 2 → 3 taken 264 times.
✓ Branch 3 → 4 taken 264 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 264 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 11 taken 264 times.
|
264 | if (!m_db.isOpen() || uids.isEmpty()) return stored; |
| 62 | 264 | QStringList marks; | |
| 63 |
3/3✓ Branch 15 → 16 taken 266 times.
✓ Branch 20 → 12 taken 266 times.
✓ Branch 20 → 21 taken 264 times.
|
530 | for (int i = 0; i < uids.size(); ++i) marks.append(QStringLiteral("?")); |
| 64 |
1/1✓ Branch 21 → 22 taken 264 times.
|
264 | QSqlQuery query(m_db); |
| 65 |
1/1✓ Branch 22 → 23 taken 264 times.
|
264 | query.setForwardOnly(true); |
| 66 |
1/1✓ Branch 31 → 32 taken 264 times.
|
792 | query.prepare(QStringLiteral("SELECT uid, primary_folder, primary_conf, alternate_folder, alternate_conf, threshold, " |
| 67 | "reason, support, total FROM folder_suggestions WHERE account = ? AND folder_id = ? AND uid_validity = ? " | ||
| 68 |
2/2✓ Branch 29 → 30 taken 264 times.
✓ Branch 30 → 31 taken 264 times.
|
792 | "AND uid IN (%1)").arg(marks.join(QLatin1Char(',')))); |
| 69 |
1/1✓ Branch 38 → 39 taken 264 times.
|
264 | query.addBindValue(account); |
| 70 |
1/1✓ Branch 42 → 43 taken 264 times.
|
264 | query.addBindValue(folderId); |
| 71 |
1/1✓ Branch 46 → 47 taken 264 times.
|
264 | query.addBindValue(qint64(uidValidity)); |
| 72 |
3/3✓ Branch 53 → 54 taken 266 times.
✓ Branch 57 → 50 taken 266 times.
✓ Branch 57 → 58 taken 264 times.
|
530 | for (const qint64 uid : uids) query.addBindValue(uid); |
| 73 |
2/3✓ Branch 58 → 59 taken 264 times.
✗ Branch 59 → 60 not taken.
✓ Branch 59 → 62 taken 264 times.
|
264 | if (!query.exec()) { |
| 74 | ✗ | fail(query, error); | |
| 75 | ✗ | return {}; | |
| 76 | } | ||
| 77 |
3/3✓ Branch 101 → 102 taken 265 times.
✓ Branch 102 → 63 taken 1 time.
✓ Branch 102 → 103 taken 264 times.
|
265 | while (query.next()) { |
| 78 | 1 | FolderSuggestion suggestion; | |
| 79 |
4/4✓ Branch 63 → 64 taken 1 time.
✓ Branch 64 → 65 taken 1 time.
✓ Branch 65 → 66 taken 1 time.
✓ Branch 66 → 67 taken 1 time.
|
1 | suggestion.primary = {query.value(1).toString(), query.value(2).toDouble()}; |
| 80 |
4/4✓ Branch 73 → 74 taken 1 time.
✓ Branch 74 → 75 taken 1 time.
✓ Branch 75 → 76 taken 1 time.
✓ Branch 76 → 77 taken 1 time.
|
1 | suggestion.alternate = {query.value(3).toString(), query.value(4).toDouble()}; |
| 81 |
2/2✓ Branch 83 → 84 taken 1 time.
✓ Branch 84 → 85 taken 1 time.
|
1 | suggestion.threshold = query.value(5).toDouble(); |
| 82 |
2/2✓ Branch 86 → 87 taken 1 time.
✓ Branch 87 → 88 taken 1 time.
|
1 | suggestion.reason = query.value(6).toInt(); |
| 83 |
2/2✓ Branch 89 → 90 taken 1 time.
✓ Branch 90 → 91 taken 1 time.
|
1 | suggestion.support = query.value(7).toInt(); |
| 84 |
2/2✓ Branch 92 → 93 taken 1 time.
✓ Branch 93 → 94 taken 1 time.
|
1 | suggestion.total = query.value(8).toInt(); |
| 85 |
3/3✓ Branch 95 → 96 taken 1 time.
✓ Branch 96 → 97 taken 1 time.
✓ Branch 97 → 98 taken 1 time.
|
1 | stored.insert(query.value(0).toLongLong(), suggestion); |
| 86 | 1 | } | |
| 87 | 264 | return stored; | |
| 88 | 264 | } | |
| 89 | |||
| 90 | 388 | bool FolderSuggestionStore::write(const QString &account, qint64 folderId, quint32 uidValidity, | |
| 91 | const QList<QPair<qint64, FolderSuggestion>> &suggestions, QString *error) { | ||
| 92 |
6/7✓ Branch 2 → 3 taken 388 times.
✓ Branch 3 → 4 taken 388 times.
✗ Branch 3 → 6 not taken.
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 7 taken 385 times.
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 10 taken 385 times.
|
388 | if (!m_db.isOpen() || suggestions.isEmpty()) return true; |
| 93 |
2/3✓ Branch 10 → 11 taken 385 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 20 taken 385 times.
|
385 | if (!m_db.transaction()) { |
| 94 | ✗ | if (error) *error = m_db.lastError().text(); | |
| 95 | ✗ | return false; | |
| 96 | } | ||
| 97 |
1/1✓ Branch 20 → 21 taken 385 times.
|
385 | QSqlQuery query(m_db); |
| 98 |
1/1✓ Branch 24 → 25 taken 385 times.
|
385 | query.prepare(QStringLiteral("INSERT OR REPLACE INTO folder_suggestions (account, folder_id, uid_validity, uid, " |
| 99 | "primary_folder, primary_conf, alternate_folder, alternate_conf, threshold, reason, " | ||
| 100 | "support, total, stored_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); | ||
| 101 | 385 | const qint64 now = QDateTime::currentSecsSinceEpoch(); | |
| 102 |
2/2✓ Branch 110 → 30 taken 615 times.
✓ Branch 110 → 111 taken 385 times.
|
1000 | for (const auto &[uid, suggestion] : suggestions) { |
| 103 |
1/1✓ Branch 35 → 36 taken 615 times.
|
615 | query.addBindValue(account); |
| 104 |
1/1✓ Branch 39 → 40 taken 615 times.
|
615 | query.addBindValue(folderId); |
| 105 |
1/1✓ Branch 43 → 44 taken 615 times.
|
615 | query.addBindValue(qint64(uidValidity)); |
| 106 |
1/1✓ Branch 47 → 48 taken 615 times.
|
615 | query.addBindValue(uid); |
| 107 | // A null QString binds as SQL NULL; "no folder" is stored as ''. | ||
| 108 |
5/7✓ Branch 51 → 52 taken 275 times.
✓ Branch 51 → 55 taken 340 times.
✓ Branch 57 → 58 taken 615 times.
✓ Branch 60 → 61 taken 275 times.
✓ Branch 60 → 62 taken 340 times.
✗ Branch 157 → 158 not taken.
✗ Branch 157 → 159 not taken.
|
890 | query.addBindValue(suggestion.primary.first.isNull() ? QStringLiteral("") : suggestion.primary.first); |
| 109 |
1/1✓ Branch 64 → 65 taken 615 times.
|
615 | query.addBindValue(suggestion.primary.second); |
| 110 |
5/7✓ Branch 68 → 69 taken 515 times.
✓ Branch 68 → 72 taken 100 times.
✓ Branch 74 → 75 taken 615 times.
✓ Branch 77 → 78 taken 515 times.
✓ Branch 77 → 79 taken 100 times.
✗ Branch 172 → 173 not taken.
✗ Branch 172 → 174 not taken.
|
1130 | query.addBindValue(suggestion.alternate.first.isNull() ? QStringLiteral("") : suggestion.alternate.first); |
| 111 |
1/1✓ Branch 81 → 82 taken 615 times.
|
615 | query.addBindValue(suggestion.alternate.second); |
| 112 |
1/1✓ Branch 85 → 86 taken 615 times.
|
615 | query.addBindValue(suggestion.threshold); |
| 113 |
1/1✓ Branch 89 → 90 taken 615 times.
|
615 | query.addBindValue(suggestion.reason); |
| 114 |
1/1✓ Branch 93 → 94 taken 615 times.
|
615 | query.addBindValue(suggestion.support); |
| 115 |
1/1✓ Branch 97 → 98 taken 615 times.
|
615 | query.addBindValue(suggestion.total); |
| 116 |
1/1✓ Branch 101 → 102 taken 615 times.
|
615 | query.addBindValue(now); |
| 117 |
2/3✓ Branch 103 → 104 taken 615 times.
✗ Branch 104 → 105 not taken.
✓ Branch 104 → 108 taken 615 times.
|
615 | if (!query.exec()) { |
| 118 | ✗ | m_db.rollback(); | |
| 119 | ✗ | return fail(query, error); | |
| 120 | } | ||
| 121 | } | ||
| 122 |
2/3✓ Branch 111 → 113 taken 385 times.
✗ Branch 113 → 114 not taken.
✓ Branch 113 → 123 taken 385 times.
|
385 | if (!m_db.commit()) { |
| 123 | ✗ | if (error) *error = m_db.lastError().text(); | |
| 124 | ✗ | m_db.rollback(); | |
| 125 | ✗ | return false; | |
| 126 | } | ||
| 127 | 385 | return true; | |
| 128 | 385 | } | |
| 129 | |||
| 130 | ✗ | bool FolderSuggestionStore::prune(int maxAgeDays, QString *error) { | |
| 131 | ✗ | if (!m_db.isOpen()) return true; | |
| 132 | ✗ | QSqlQuery query(m_db); | |
| 133 | ✗ | query.prepare(QStringLiteral("DELETE FROM folder_suggestions WHERE stored_at < ?")); | |
| 134 | ✗ | query.addBindValue(QDateTime::currentSecsSinceEpoch() - qint64(maxAgeDays) * 86400); | |
| 135 | ✗ | return query.exec() || fail(query, error); | |
| 136 | ✗ | } | |
| 137 |