| 
									
										
										
										
											2019-01-02 12:27:16 -05:00
										 |  |  | /**
 | 
					
						
							|  |  |  |  * This file is part of JS8Call. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  |  * it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  |  * the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  |  * (at your option) any later version. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  |  * GNU General Public License for more details. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  |  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * (C) 2018 Jordan Sherer <kn4crd@gmail.com> - All Rights Reserved | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  **/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "Inbox.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <QDebug>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const char* SCHEMA = "CREATE TABLE IF NOT EXISTS inbox_v1 (" | 
					
						
							|  |  |  |                      "  id INTEGER PRIMARY KEY AUTOINCREMENT, " | 
					
						
							|  |  |  |                      "  blob TEXT" | 
					
						
							|  |  |  |                      ");" | 
					
						
							|  |  |  |                      "CREATE INDEX IF NOT EXISTS idx_inbox_v1__type ON" | 
					
						
							|  |  |  |                      "  inbox_v1(json_extract(blob, '$.type'));" | 
					
						
							|  |  |  |                      "CREATE INDEX IF NOT EXISTS idx_inbox_v1__params_from ON" | 
					
						
							|  |  |  |                      "  inbox_v1(json_extract(blob, '$.params.FROM'));" | 
					
						
							|  |  |  |                      "CREATE INDEX IF NOT EXISTS idx_inbox_v1__params_to ON" | 
					
						
							|  |  |  |                      "  inbox_v1(json_extract(blob, '$.params.TO'))"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Inbox::Inbox(QString path) : | 
					
						
							|  |  |  |     path_{ path }, | 
					
						
							|  |  |  |     db_{ nullptr } | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Inbox::~Inbox(){ | 
					
						
							|  |  |  |     close(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  * Low-Level Interface | 
					
						
							|  |  |  |  **/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool Inbox::isOpen(){ | 
					
						
							|  |  |  |     return db_ != nullptr; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool Inbox::open(){ | 
					
						
							|  |  |  |     int rc = sqlite3_open(path_.toLocal8Bit().data(), &db_); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         close(); | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rc = sqlite3_exec(db_, SCHEMA, nullptr, nullptr, nullptr); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Inbox::close(){ | 
					
						
							|  |  |  |     if(db_){ | 
					
						
							|  |  |  |         sqlite3_close(db_); | 
					
						
							|  |  |  |         db_ = nullptr; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QString Inbox::error(){ | 
					
						
							|  |  |  |     if(db_){ | 
					
						
							|  |  |  |         return QString::fromLocal8Bit(sqlite3_errmsg(db_)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return ""; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int Inbox::count(QString type, QString query, QString match){ | 
					
						
							|  |  |  |     if(!isOpen()){ | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const char* sql = "SELECT COUNT(*) FROM inbox_v1 " | 
					
						
							|  |  |  |                       "WHERE json_extract(blob, '$.type') = ? " | 
					
						
							|  |  |  |                       "AND json_extract(blob, ?) LIKE ?;"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sqlite3_stmt *stmt; | 
					
						
							|  |  |  |     int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto t8 = type.toLocal8Bit(); | 
					
						
							|  |  |  |     auto q8 = query.toLocal8Bit(); | 
					
						
							|  |  |  |     auto m8 = match.toLocal8Bit(); | 
					
						
							|  |  |  |     rc = sqlite3_bind_text(stmt, 1, t8.data(),  -1, nullptr); | 
					
						
							|  |  |  |     rc = sqlite3_bind_text(stmt, 2, q8.data(), -1, nullptr); | 
					
						
							|  |  |  |     rc = sqlite3_bind_text(stmt, 3, m8.data(), -1, nullptr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     int count = 0; | 
					
						
							|  |  |  |     rc = sqlite3_step(stmt); | 
					
						
							|  |  |  |     if(rc == SQLITE_ROW) { | 
					
						
							|  |  |  |         count = sqlite3_column_int(stmt, 0); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rc = sqlite3_finalize(stmt); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return count; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QList<QPair<int, Message> > Inbox::values(QString type, QString query, QString match, int offset, int limit){ | 
					
						
							|  |  |  |     if(!isOpen()){ | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const char* sql = "SELECT id, blob FROM inbox_v1 " | 
					
						
							|  |  |  |                       "WHERE json_extract(blob, '$.type') = ? " | 
					
						
							|  |  |  |                       "AND json_extract(blob, ?) LIKE ? " | 
					
						
							|  |  |  |                       "ORDER BY id ASC " | 
					
						
							|  |  |  |                       "LIMIT ? OFFSET ?;"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sqlite3_stmt *stmt; | 
					
						
							|  |  |  |     int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto t8 = type.toLocal8Bit(); | 
					
						
							|  |  |  |     auto q8 = query.toLocal8Bit(); | 
					
						
							|  |  |  |     auto m8 = match.toLocal8Bit(); | 
					
						
							|  |  |  |     rc = sqlite3_bind_text(stmt, 1, t8.data(),  -1, nullptr); | 
					
						
							|  |  |  |     rc = sqlite3_bind_text(stmt, 2, q8.data(), -1, nullptr); | 
					
						
							|  |  |  |     rc = sqlite3_bind_text(stmt, 3, m8.data(), -1, nullptr); | 
					
						
							|  |  |  |     rc = sqlite3_bind_int(stmt, 4, limit); | 
					
						
							|  |  |  |     rc = sqlite3_bind_int(stmt, 5, offset); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-06 22:50:51 -05:00
										 |  |  |     //qDebug() << "exec" << sqlite3_expanded_sql(stmt);
 | 
					
						
							| 
									
										
										
										
											2019-01-02 12:27:16 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     QList<QPair<int, Message>> v; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { | 
					
						
							|  |  |  |         Message m; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         int i = sqlite3_column_int(stmt, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         auto msg = QByteArray((const char*)sqlite3_column_text(stmt, 1), sqlite3_column_bytes(stmt, 1)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         QJsonParseError e; | 
					
						
							|  |  |  |         QJsonDocument d = QJsonDocument::fromJson(msg, &e); | 
					
						
							|  |  |  |         if(e.error != QJsonParseError::NoError){ | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if(!d.isObject()){ | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         m.read(d.object()); | 
					
						
							|  |  |  |         v.append({ i, m }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rc = sqlite3_finalize(stmt); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return v; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Message Inbox::value(int key){ | 
					
						
							|  |  |  |     if(!isOpen()){ | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const char* sql = "SELECT blob FROM inbox_v1 WHERE id = ? LIMIT 1;"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sqlite3_stmt *stmt; | 
					
						
							|  |  |  |     int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rc = sqlite3_bind_int(stmt, 1, key); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Message m; | 
					
						
							|  |  |  |     while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { | 
					
						
							|  |  |  |         auto msg = QByteArray((const char*)sqlite3_column_text(stmt, 0), sqlite3_column_bytes(stmt, 0)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         QJsonParseError e; | 
					
						
							|  |  |  |         QJsonDocument d = QJsonDocument::fromJson(msg, &e); | 
					
						
							|  |  |  |         if(e.error != QJsonParseError::NoError){ | 
					
						
							|  |  |  |             return {}; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if(!d.isObject()){ | 
					
						
							|  |  |  |             return {}; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         m.read(d.object()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rc = sqlite3_finalize(stmt); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return m; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int Inbox::append(Message value){ | 
					
						
							|  |  |  |     if(!isOpen()){ | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const char* sql = "INSERT INTO inbox_v1 (blob) VALUES (?);"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sqlite3_stmt *stmt; | 
					
						
							|  |  |  |     int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return -2; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto j8 = value.toJson(); | 
					
						
							|  |  |  |     rc = sqlite3_bind_text(stmt, 1, j8.data(), -1, nullptr); | 
					
						
							|  |  |  |     rc = sqlite3_step(stmt); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rc = sqlite3_finalize(stmt); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return sqlite3_last_insert_rowid(db_); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool Inbox::set(int key, Message value){ | 
					
						
							|  |  |  |     if(!isOpen()){ | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const char* sql = "UPDATE inbox_v1 SET blob = ? WHERE id = ?;"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sqlite3_stmt *stmt; | 
					
						
							|  |  |  |     int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto j8 = value.toJson(); | 
					
						
							|  |  |  |     rc = sqlite3_bind_text(stmt, 1, j8.data(), -1, nullptr); | 
					
						
							|  |  |  |     rc = sqlite3_bind_int(stmt, 2, key); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rc = sqlite3_step(stmt); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rc = sqlite3_finalize(stmt); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool Inbox::del(int key){ | 
					
						
							|  |  |  |     if(!isOpen()){ | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const char* sql = "DELETE FROM inbox_v1 WHERE id = ?;"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sqlite3_stmt *stmt; | 
					
						
							|  |  |  |     int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rc = sqlite3_bind_int(stmt, 1, key); | 
					
						
							|  |  |  |     rc = sqlite3_step(stmt); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rc = sqlite3_finalize(stmt); | 
					
						
							|  |  |  |     if(rc != SQLITE_OK){ | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  * High-Level Interface | 
					
						
							|  |  |  |  **/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int Inbox::countUnreadFrom(QString from){ | 
					
						
							|  |  |  |     return count("UNREAD", "$.params.FROM", from); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QPair<int, Message> Inbox::firstUnreadFrom(QString from){ | 
					
						
							|  |  |  |     auto v = values("UNREAD", "$.params.FROM", from, 0, 1); | 
					
						
							|  |  |  |     if(v.isEmpty()){ | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return v.first(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 |