312 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			312 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|   | /**
 | ||
|  |  * 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); | ||
|  | 
 | ||
|  |     qDebug() << "exec" << sqlite3_expanded_sql(stmt); | ||
|  | 
 | ||
|  |     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(); | ||
|  | } | ||
|  | 
 |