File indexing completed on 2025-03-23 13:45:10
0001 // SPDX-FileCopyrightText: 2020 Jonah BrĂ¼chert <jbb@kaidan.im> 0002 // SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org> 0003 // SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru> 0004 // 0005 // SPDX-License-Identifier: LicenseRef-KDE-Accepted-GPL 0006 0007 #include "call-history-database.h" 0008 0009 #include <QDebug> 0010 #include <QDir> 0011 #include <QSqlError> 0012 #include <QSqlQuery> 0013 #include <QStandardPaths> 0014 0015 constexpr auto DATABASE_REVISION = 2; // Keep MIGRATE_TO_LATEST_FROM in sync 0016 #define MIGRATE_TO(n, current) \ 0017 if (current < n) { \ 0018 qDebug() << "Running migration" << #n; \ 0019 _migrationV##n(current); \ 0020 } 0021 #define MIGRATE_TO_LATEST_FROM(current) MIGRATE_TO(2, current) 0022 0023 CallHistoryDatabase::CallHistoryDatabase(QObject *parent) 0024 : QObject(parent) 0025 , _database(QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("calls"))) 0026 { 0027 const QString databaseLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/plasmaphonedialer"); 0028 if (!QDir().mkpath(databaseLocation)) { 0029 qDebug() << "Could not create the database directory at" << databaseLocation; 0030 } 0031 0032 _database.setDatabaseName(databaseLocation + QStringLiteral("/calls.sqlite")); 0033 const bool open = _database.open(); 0034 0035 if (!open) { 0036 qWarning() << "Could not open call database" << _database.lastError(); 0037 } 0038 0039 _migrate(); 0040 } 0041 0042 void CallHistoryDatabase::exec(QSqlQuery &query) 0043 { 0044 if (query.lastQuery().isEmpty()) { 0045 // Sending empty queries doesn't make sense 0046 Q_UNREACHABLE(); 0047 } 0048 if (!query.exec()) { 0049 qWarning() << "Query" << query.lastQuery() << "resulted in" << query.lastError(); 0050 } 0051 } 0052 0053 DialerTypes::CallDataVector CallHistoryDatabase::fetchCalls() 0054 { 0055 QSqlQuery fetchCalls(_database); 0056 fetchCalls.prepare( 0057 QStringLiteral("SELECT " 0058 "id, protocol, account, provider, " 0059 "communicationWith, direction, state, stateReason, " 0060 "callAttemptDuration, startedAt, duration " 0061 "FROM History ORDER BY startedAt DESC")); 0062 exec(fetchCalls); 0063 0064 DialerTypes::CallDataVector calls; 0065 while (fetchCalls.next()) { 0066 DialerTypes::CallData call; 0067 call.id = fetchCalls.value(0).toString(); 0068 call.protocol = fetchCalls.value(1).toString(); 0069 call.account = fetchCalls.value(2).toString(); 0070 call.provider = fetchCalls.value(3).toString(); 0071 0072 call.communicationWith = fetchCalls.value(4).toString(); 0073 call.direction = fetchCalls.value(5).value<DialerTypes::CallDirection>(); 0074 call.state = fetchCalls.value(6).value<DialerTypes::CallState>(); 0075 call.stateReason = fetchCalls.value(7).value<DialerTypes::CallStateReason>(); 0076 0077 call.callAttemptDuration = fetchCalls.value(8).toInt(); 0078 call.startedAt = QDateTime::fromString(fetchCalls.value(9).toString(), QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz")); 0079 call.duration = fetchCalls.value(10).toInt(); 0080 calls.append(call); 0081 } 0082 return calls; 0083 } 0084 0085 void CallHistoryDatabase::addCall(const DialerTypes::CallData &callData) 0086 { 0087 QSqlQuery putCall(_database); 0088 putCall.prepare( 0089 QStringLiteral("INSERT INTO History " 0090 "(" 0091 "protocol, account, provider, " 0092 "communicationWith, direction, state, stateReason, " 0093 "callAttemptDuration, startedAt, duration " 0094 ") " 0095 "VALUES (" 0096 ":protocol, :account, :provider, " 0097 ":communicationWith, :direction, :state, :stateReason, " 0098 ":callAttemptDuration, :startedAt, :duration " 0099 ")")); 0100 putCall.bindValue(QStringLiteral(":protocol"), callData.protocol); 0101 putCall.bindValue(QStringLiteral(":account"), callData.account); 0102 putCall.bindValue(QStringLiteral(":provider"), callData.provider); 0103 0104 putCall.bindValue(QStringLiteral(":communicationWith"), callData.communicationWith); 0105 putCall.bindValue(QStringLiteral(":direction"), static_cast<int>(callData.direction)); 0106 putCall.bindValue(QStringLiteral(":state"), static_cast<int>(callData.state)); 0107 putCall.bindValue(QStringLiteral(":stateReason"), static_cast<int>(callData.stateReason)); 0108 0109 putCall.bindValue(QStringLiteral(":callAttemptDuration"), callData.callAttemptDuration); 0110 putCall.bindValue(QStringLiteral(":startedAt"), callData.startedAt); 0111 putCall.bindValue(QStringLiteral(":duration"), callData.duration); 0112 exec(putCall); 0113 0114 Q_EMIT callsChanged(); 0115 } 0116 0117 void CallHistoryDatabase::clear() 0118 { 0119 QSqlQuery clearCalls(_database); 0120 clearCalls.prepare(QStringLiteral("DELETE FROM History")); 0121 exec(clearCalls); 0122 0123 Q_EMIT callsChanged(); 0124 } 0125 0126 void CallHistoryDatabase::remove(const QString &id) 0127 { 0128 QSqlQuery remove(_database); 0129 remove.prepare(QStringLiteral("DELETE FROM History WHERE id=:id")); 0130 remove.bindValue(QStringLiteral(":id"), id); 0131 exec(remove); 0132 0133 Q_EMIT callsChanged(); 0134 } 0135 0136 int CallHistoryDatabase::lastId() const 0137 { 0138 QSqlQuery fetch(_database); 0139 fetch.prepare(QStringLiteral("SELECT id FROM History ORDER BY id DESC LIMIT 1")); 0140 exec(fetch); 0141 fetch.first(); 0142 0143 return fetch.value(0).toInt(); 0144 } 0145 0146 QString CallHistoryDatabase::lastCall(const QString &number, int direction) const 0147 { 0148 QSqlQuery fetch(_database); 0149 fetch.prepare(QStringLiteral("SELECT startedAt FROM History WHERE communicationWith=:number and direction = :direction ORDER BY startedAt DESC LIMIT 1")); 0150 fetch.bindValue(QStringLiteral(":number"), number); 0151 fetch.bindValue(QStringLiteral(":direction"), direction); 0152 exec(fetch); 0153 fetch.first(); 0154 0155 return fetch.value(0).toString(); 0156 } 0157 0158 uint CallHistoryDatabase::_guessPreHistoricRevision() 0159 { 0160 uint result = 0; 0161 uint columnCount = 0; 0162 QSqlQuery tableInfo(_database); 0163 tableInfo.prepare(QStringLiteral("PRAGMA table_info('History')")); // http://sqlite.org/pragma.html 0164 exec(tableInfo); 0165 while (tableInfo.next()) { 0166 qDebug() << Q_FUNC_INFO << "found column" << tableInfo.value(1).toString(); 0167 columnCount++; 0168 } 0169 if (columnCount == 11) { 0170 qDebug() << Q_FUNC_INFO << "found pre-historic revision v1"; 0171 result = 1; // the db from testing version with the v1 scheme but without migrationId 0172 } 0173 return result; 0174 } 0175 0176 void CallHistoryDatabase::_migrationV1(uint current) 0177 { 0178 if (current < 1) { 0179 QSqlQuery tempTable(_database); 0180 tempTable.prepare(QStringLiteral("CREATE TABLE temp_table AS SELECT * FROM History")); 0181 exec(tempTable); 0182 0183 QSqlQuery dropOld(_database); 0184 dropOld.prepare(QStringLiteral("DROP TABLE History")); 0185 exec(dropOld); 0186 0187 QSqlQuery createNew(_database); 0188 createNew.prepare( 0189 QStringLiteral("CREATE TABLE IF NOT EXISTS " 0190 "History( " 0191 "id INTEGER PRIMARY KEY AUTOINCREMENT, " 0192 "protocol TEXT, " 0193 "account TEXT, " 0194 "provider TEXT, " 0195 "communicationWith TEXT, " 0196 "direction INTEGER, " 0197 "state INTEGER, " 0198 "stateReason INTEGER, " 0199 "callAttemptDuration INTEGER, " 0200 "startedAt DATETIME, " 0201 "duration INTEGER " 0202 ")")); 0203 exec(createNew); 0204 0205 QSqlQuery copyTemp(_database); 0206 // clang-format off 0207 copyTemp.prepare( 0208 QStringLiteral("INSERT INTO History " 0209 "(" 0210 "protocol, account, provider, " 0211 "communicationWith, direction, state, stateReason, " 0212 "callAttemptDuration, startedAt, duration " 0213 ") " 0214 "SELECT " 0215 "'tel', 'unknown', 'unknown', " 0216 "temp_table.number, " 0217 "CASE temp_table.callType " // direction 0218 "WHEN 0 " // IncomingRejected 0219 "THEN 1 " // Incoming 0220 "WHEN 1 " // IncomingAccepted 0221 "THEN 1 " // Incoming 0222 "WHEN 2 " // Outgoing 0223 "THEN 2 " // Outgoing 0224 "ELSE 0 " // Unknown 0225 "END, " 0226 "7, " // state Terminated 0227 "CASE temp_table.callType " // stateReason 0228 "WHEN 0 " // IncomingRejected 0229 "THEN 9 " // Deflected 0230 "WHEN 1 " // IncomingAccepted 0231 "THEN 3 " // Accepted 0232 "WHEN 2 " // Outgoing 0233 "THEN 4 " // TerminatedReason 0234 "ELSE 0 " // Unknown 0235 "END, " 0236 "0, temp_table.time, temp_table.duration " 0237 "FROM temp_table")); 0238 // clang-format on 0239 exec(copyTemp); 0240 0241 QSqlQuery dropTemp(_database); 0242 dropTemp.prepare(QStringLiteral("DROP TABLE temp_table")); 0243 exec(dropTemp); 0244 } 0245 } 0246 0247 void CallHistoryDatabase::_migrationV2(uint current) 0248 { 0249 MIGRATE_TO(1, current); 0250 } 0251 0252 void CallHistoryDatabase::_migrate() 0253 { 0254 // Create migration table if necessary 0255 QSqlQuery createMetadata(_database); 0256 createMetadata.prepare(QStringLiteral("CREATE TABLE IF NOT EXISTS Metadata (migrationId INTEGER NOT NULL)")); 0257 exec(createMetadata); 0258 0259 // Find out current revision 0260 QSqlQuery currentRevision(_database); 0261 currentRevision.prepare(QStringLiteral("SELECT migrationId FROM Metadata ORDER BY migrationId DESC LIMIT 1")); 0262 exec(currentRevision); 0263 currentRevision.first(); 0264 0265 uint revision = 0; 0266 if (currentRevision.isValid()) { 0267 revision = currentRevision.value(0).toUInt(); 0268 } 0269 0270 if (revision == 0) { 0271 revision = _guessPreHistoricRevision(); 0272 } 0273 0274 qDebug() << "current database revision" << revision; 0275 0276 // Run migration if necessary 0277 if (revision >= DATABASE_REVISION) { 0278 return; 0279 } 0280 0281 MIGRATE_TO_LATEST_FROM(revision); 0282 0283 // Update migration info if necessary 0284 QSqlQuery update(_database); 0285 update.prepare(QStringLiteral("INSERT INTO Metadata (migrationId) VALUES (:migrationId)")); 0286 update.bindValue(QStringLiteral(":migrationId"), DATABASE_REVISION); 0287 exec(update); 0288 }