diff --git a/src/back_end/Cargo.lock b/src/back_end/Cargo.lock index 36efeb6..d0691c7 100644 --- a/src/back_end/Cargo.lock +++ b/src/back_end/Cargo.lock @@ -7,8 +7,8 @@ name = "back_end" version = "1.0.0" dependencies = [ "dirs", + "rusqlite", "serde", - "sqlite", "toml", "webbrowser", ] @@ -116,6 +116,24 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -141,6 +159,18 @@ name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown", +] [[package]] name = "icu_collections" @@ -307,6 +337,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "litemap" version = "0.8.0" @@ -418,6 +459,20 @@ dependencies = [ "thiserror 2.0.14", ] +[[package]] +name = "rusqlite" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "same-file" version = "1.0.6" @@ -468,34 +523,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "sqlite" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66e9c01a11936154f3910dbba732c01f8b591543bc4d6672bddee79fd9c4783" -dependencies = [ - "sqlite3-sys", -] - -[[package]] -name = "sqlite3-src" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5b6d3c860886b0a33e69e421796a5f4a27f23597a182c2450f6d7ace5103120" -dependencies = [ - "cc", - "pkg-config", -] - -[[package]] -name = "sqlite3-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7781d97adc13a1d5081127a9ee29afad8427f3757bd984daf814d8265267039" -dependencies = [ - "sqlite3-src", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -636,6 +663,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "walkdir" version = "2.5.0" diff --git a/src/back_end/Cargo.toml b/src/back_end/Cargo.toml index 7ad568b..e2f0e76 100644 --- a/src/back_end/Cargo.toml +++ b/src/back_end/Cargo.toml @@ -11,7 +11,7 @@ panic = "abort" [dependencies] dirs = "6.0.0" +rusqlite = { version = "0.37.0", features = ["bundled"] } serde = { version = "1.0.219", features = ["derive"] } -sqlite = "0.37.0" toml = "0.9.5" webbrowser = "1.0.5" diff --git a/src/back_end/src/core/sqlite.rs b/src/back_end/src/core/sqlite.rs new file mode 100644 index 0000000..6a4b5b7 --- /dev/null +++ b/src/back_end/src/core/sqlite.rs @@ -0,0 +1,118 @@ +use rusqlite::Connection; +use std::ffi::{CStr, c_char}; + +#[derive(PartialEq, Eq, Debug)] +#[repr(C)] +#[allow(non_camel_case_types)] +pub enum QueryResult { + OK, + OPEN_DATABASE_ERR, + C_STR_CONVERT_ERR, + EXECUTE_SQL_ERR, +} + +const INIT_QUERY: &str = r"--sql + CREATE TABLE IF NOT EXISTS clipboard_cache ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + content_hash TEXT NOT NULL UNIQUE, + time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + content TEXT NOT NULL, + content_type TEXT NOT NULL, + is_pinned BOOLEAN NOT NULL DEFAULT FALSE + ); +"; + +#[unsafe(no_mangle)] +/// # Safety +/// Careful with unsafe context & raw pointers. +pub unsafe extern "C" fn raw_init_clipboard_cache( + file_path: *const c_char, +) -> QueryResult { + unsafe { + use QueryResult as R; + + let Ok(file_path_cstr) = CStr::from_ptr(file_path).to_str() else { + return R::C_STR_CONVERT_ERR; + }; + + let Ok(sql) = Connection::open(file_path_cstr) else { + return R::OPEN_DATABASE_ERR; + }; + + let is_err = sql.execute(INIT_QUERY, ()).is_err(); + + if is_err { + return R::EXECUTE_SQL_ERR; + } + + R::OK + } +} + +#[repr(C)] +pub struct TextClipboard { + pub content: *const c_char, + pub content_hash: *const c_char, + pub content_type: *const c_char, + pub is_pinned: bool, +} + +/// # Safety +/// Careful with raw pointers & memory leaks. +#[must_use] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn raw_add_text_clipboard( + db_path: *const c_char, + text_clipboard: TextClipboard, +) -> QueryResult { + use QueryResult as R; + + unsafe { + let Ok(file_path_cstr) = CStr::from_ptr(db_path).to_str() else { + return R::C_STR_CONVERT_ERR; + }; + + let Ok(sql) = Connection::open(file_path_cstr) else { + return R::OPEN_DATABASE_ERR; + }; + + let Ok(content_str) = CStr::from_ptr(text_clipboard.content).to_str() + else { + return R::C_STR_CONVERT_ERR; + }; + + let Ok(content_hash_str) = + CStr::from_ptr(text_clipboard.content_hash).to_str() + else { + return R::C_STR_CONVERT_ERR; + }; + + let Ok(content_type_str) = + CStr::from_ptr(text_clipboard.content_type).to_str() + else { + return R::C_STR_CONVERT_ERR; + }; + + let query = r" + INSERT INTO clipboard_cache (content, content_hash, content_type, is_pinned) + VALUES (?, ?, ?, ?) + "; + + let binds = ( + content_str, + content_hash_str, + content_type_str, + text_clipboard.is_pinned, + ); + let is_err = sql.execute(query, binds).is_err(); + + /* For panic testing only. Don't remove it. */ + /* sql.execute(query, binds).unwrap(); */ + + if is_err { + return R::EXECUTE_SQL_ERR; + } + + R::OK + } +} diff --git a/src/back_end/src/lib.rs b/src/back_end/src/lib.rs index 95d6be0..bb901d8 100644 --- a/src/back_end/src/lib.rs +++ b/src/back_end/src/lib.rs @@ -4,6 +4,9 @@ pub mod utils { pub mod browser; pub mod fs_utils; pub mod memory; +} + +pub mod core { pub mod sqlite; } diff --git a/src/back_end/src/utils/sqlite.rs b/src/back_end/src/utils/sqlite.rs deleted file mode 100644 index 884f4eb..0000000 --- a/src/back_end/src/utils/sqlite.rs +++ /dev/null @@ -1,48 +0,0 @@ -use sqlite::Connection; -use std::ffi::{CStr, c_char}; - -#[repr(C)] -pub enum InitDatabaseStatus { - Ok, - CreateDatabaseFailed, - CStrConvertFailed, - ExecuteSqlFailed, -} - -const INIT_QUERY: &str = r"--sql - CREATE TABLE IF NOT EXISTS clipboard_cache ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - content_hash TEXT NOT NULL UNIQUE, - time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - content TEXT NOT NULL, - type TEXT NOT NULL, - is_pinned BOOLEAN NOT NULL DEFAULT FALSE - ); -"; - -#[unsafe(no_mangle)] -/// # Safety -/// Careful with unsafe context & raw pointers. -pub unsafe extern "C" fn raw_init_clipboard_cache( - file_path: *const c_char, -) -> InitDatabaseStatus { - unsafe { - use InitDatabaseStatus as Status; - - let Ok(file_path_cstr) = CStr::from_ptr(file_path).to_str() else { - return Status::CStrConvertFailed; - }; - - let Ok(sql) = Connection::open(file_path_cstr) else { - return Status::CreateDatabaseFailed; - }; - - let is_err = sql.execute(INIT_QUERY).is_err(); - - if is_err { - return Status::ExecuteSqlFailed; - } - - Status::Ok - } -} diff --git a/src/back_end/tests/sqlite.rs b/src/back_end/tests/sqlite.rs new file mode 100644 index 0000000..9548fb3 --- /dev/null +++ b/src/back_end/tests/sqlite.rs @@ -0,0 +1,41 @@ +#[test] +fn test_add_text_clipboard() { + use back_end::core::sqlite::{ + QueryResult, TextClipboard, raw_add_text_clipboard, + raw_init_clipboard_cache, + }; + use std::ffi::CString; + use std::fs; + + use QueryResult as R; + use TextClipboard as T; + + let content = CString::new("my clipboard content").unwrap(); + let content_hash = CString::new("my hash abc xyz ").unwrap(); + let content_type = CString::new("text").unwrap(); + let db_path = CString::new("test.db").unwrap(); + + let _ = fs::remove_file(db_path.to_str().unwrap()); + + let text_clipboard = T { + content: content.as_ptr(), + content_hash: content_hash.as_ptr(), + content_type: content_type.as_ptr(), + is_pinned: false, + }; + + unsafe { + let init_result = raw_init_clipboard_cache(db_path.as_ptr()); + let result = raw_add_text_clipboard(db_path.as_ptr(), text_clipboard); + + assert_ne!(init_result, R::C_STR_CONVERT_ERR); + assert_ne!(init_result, R::EXECUTE_SQL_ERR); + assert_ne!(init_result, R::OPEN_DATABASE_ERR); + assert_eq!(init_result, R::OK); + + assert_ne!(result, R::C_STR_CONVERT_ERR); + assert_ne!(result, R::EXECUTE_SQL_ERR); + assert_ne!(result, R::OPEN_DATABASE_ERR); + assert_eq!(result, R::OK); + } +} diff --git a/src/ffi/namespace/include/sqlite.hxx b/src/ffi/namespace/include/sqlite.hxx index 12be185..a3f83ad 100644 --- a/src/ffi/namespace/include/sqlite.hxx +++ b/src/ffi/namespace/include/sqlite.hxx @@ -3,7 +3,7 @@ #include "../../raw/sqlite.hxx" namespace Lazyboard::ffi { -inline auto init_clipboard_cache(const char *path) -> InitDatabaseStatus { +inline auto init_clipboard_cache(const char *path) -> QueryResult { auto status = raw_init_clipboard_cache(path); return status; diff --git a/src/ffi/raw/sqlite.hxx b/src/ffi/raw/sqlite.hxx index b44f626..a9dcb68 100644 --- a/src/ffi/raw/sqlite.hxx +++ b/src/ffi/raw/sqlite.hxx @@ -8,14 +8,14 @@ extern "C" { #include using std::uint8_t; -enum class InitDatabaseStatus : uint8_t { +enum class QueryResult : uint8_t { OK, - CREATE_DATABASE_FAILED, - C_STR_CONVERT_FAILED, - EXECUTE_SQL_FAILED + OPEN_DATABASE_ERR, + C_STR_CONVERT_ERR, + EXECUTE_SQL_ERR }; -auto raw_init_clipboard_cache(const char *path) -> InitDatabaseStatus; +auto raw_init_clipboard_cache(const char *path) -> QueryResult; #if defined(__cplusplus) } diff --git a/src/front_end/main_window_preload.cxx b/src/front_end/main_window_preload.cxx index 800fdbe..1e6e59b 100644 --- a/src/front_end/main_window_preload.cxx +++ b/src/front_end/main_window_preload.cxx @@ -88,11 +88,11 @@ void Self::on_read_exists_cfg_error(RawReadAppConfigStatus status, break; case Status::UTF_8_ERROR: - error_dialog_show(main_window, E::UTF_8_ERROR); + error_dialog_show(main_window, E::UTF_8_ERR); break; case Status::CONVERT_TO_C_STR_FAILED: - error_dialog_show(main_window, E::CONVERT_TO_C_STR_FAILED); + error_dialog_show(main_window, E::CONVERT_TO_C_STR_ERR); break; } } diff --git a/src/front_end_db/include/sqlite_manager.hxx b/src/front_end_db/include/sqlite_manager.hxx index 7f7aeea..7dc1be9 100644 --- a/src/front_end_db/include/sqlite_manager.hxx +++ b/src/front_end_db/include/sqlite_manager.hxx @@ -8,7 +8,7 @@ namespace Lazyboard::front_end_db { class SQLiteManager { private: - using InitDataResult = InitDatabaseStatus; + using InitDataResult = QueryResult; using MkdirResult = CreateFileSystemStatus; void on_create_folder_error(const MkdirResult &result) noexcept; diff --git a/src/front_end_db/sqlite_manager.cxx b/src/front_end_db/sqlite_manager.cxx index 8fd5f69..91cd439 100644 --- a/src/front_end_db/sqlite_manager.cxx +++ b/src/front_end_db/sqlite_manager.cxx @@ -24,25 +24,26 @@ using Lazyboard::front_end_utils::ErrorTypes; using Self = SQLiteManager; using std::format; using std::string; -using Status = InitDatabaseStatus; +using Status = QueryResult; void Self::on_create_clipboard_cache_error( const InitDataResult& result) noexcept { using E = ErrorTypes; + using R = InitDataResult; switch (result) { - case InitDataResult::OK: + case R::OK: break; - case InitDataResult::C_STR_CONVERT_FAILED: - error_dialog_show(this->_main_window, E::CONVERT_TO_C_STR_FAILED); + case R::C_STR_CONVERT_ERR: + error_dialog_show(this->_main_window, E::CONVERT_TO_C_STR_ERR); break; - case InitDataResult::CREATE_DATABASE_FAILED: - error_dialog_show(this->_main_window, E::CREATE_DATABASE_ERR); + case R::OPEN_DATABASE_ERR: + error_dialog_show(this->_main_window, E::OPEN_DATABASE_ERR); break; - case InitDataResult::EXECUTE_SQL_FAILED: + case R::EXECUTE_SQL_ERR: error_dialog_show(this->_main_window, E::EXECUTE_SQL_ERR); } } diff --git a/src/front_end_utils/include/error_types.hxx b/src/front_end_utils/include/error_types.hxx index df89020..c8428d8 100644 --- a/src/front_end_utils/include/error_types.hxx +++ b/src/front_end_utils/include/error_types.hxx @@ -25,11 +25,11 @@ enum class ErrorTypes : uint8_t { TOML_TO_STRING_FAILED, WRITE_FILE_FAILED, CONVERT_TO_MUT_FAILED, - CONVERT_TO_C_STR_FAILED, + CONVERT_TO_C_STR_ERR, PARSE_TOML_FAILED, READ_FILE_FAILED, - UTF_8_ERROR, - CREATE_DATABASE_ERR, + UTF_8_ERR, + OPEN_DATABASE_ERR, EXECUTE_SQL_ERR, WRAP_C_STR_ERR, }; @@ -48,14 +48,14 @@ inline constexpr Types error_types_map() noexcept { {E::TOML_TO_STRING_FAILED, "Could not convert TOML to string"}, {E::WRITE_FILE_FAILED, "Could not write file"}, {E::CONVERT_TO_MUT_FAILED, "Could not convert value to '*mut c_char'"}, - {E::CONVERT_TO_C_STR_FAILED, "Could not convert value to 'c_str'"}, + {E::CONVERT_TO_C_STR_ERR, "Could not convert value to 'c_str'"}, {E::PARSE_TOML_FAILED, "Could not parse TOML, please check your configuration and try " "again"}, {E::READ_FILE_FAILED, "Could not read file"}, - {E::UTF_8_ERROR, "UTF-8 error"}, + {E::UTF_8_ERR, "UTF-8 error"}, {E::EXECUTE_SQL_ERR, "Could not execute SQLite query"}, - {E::CREATE_DATABASE_ERR, "Could not create database"}, + {E::OPEN_DATABASE_ERR, "Could not create database"}, {E::WRAP_C_STR_ERR, "Could not wrap 'c_str'"}, }; diff --git a/src/front_end_utils/include/utils.hxx b/src/front_end_utils/include/utils.hxx index dc15b08..d07ad15 100644 --- a/src/front_end_utils/include/utils.hxx +++ b/src/front_end_utils/include/utils.hxx @@ -9,13 +9,17 @@ #include #include +#include #include #include +#include #include #include "error_types.hxx" using std::abort; +using std::for_each; +using std::forward; using std::initializer_list; using std::is_same_v; using std::stringstream; @@ -29,15 +33,14 @@ using std::source_location; #endif namespace Lazyboard::front_end_utils { - template concept is_qcolor = requires(const T& t) { QColor(t); }; template -inline constexpr bool is_valid_hex_color(const Args&... args) noexcept { +inline constexpr bool is_valid_hex_color(Args&&... args) noexcept { return ( [&]() { - QColor color(args); + QColor color(std::forward(args)); if (!color.isValid()) { return false; @@ -53,9 +56,9 @@ inline QIcon image_from_bytes(const init_list& data) noexcept { QByteArray bytes_array; bytes_array.reserve(static_cast(data.size())); - for (auto byte : data) { - bytes_array.append(static_cast(byte)); - } + for_each(data.begin(), data.end(), [&bytes_array](auto bytes) { + bytes_array.append(static_cast(bytes)); + }); QPixmap pixmap; if (pixmap.loadFromData(bytes_array)) { @@ -74,13 +77,13 @@ inline void error_dialog_show(QWidget* parent, } #if defined(LAZY_DEBUG) +const auto green = "\x1b[32m"; +const auto white = "\x1b[37m"; + using src_loc = source_location; template inline void dump_ptr_address(T* t, const src_loc& location = src_loc::current()) { - auto green = "\x1b[32m"; - auto white = "\x1b[37m"; - auto file_name = location.file_name(); auto file_line = location.line(); @@ -88,6 +91,17 @@ inline void dump_ptr_address(T* t, println("{}[MEMORY_DEBUG]{} Line: {}", green, white, file_line); println("{}[MEMORY_DEBUG]{} {}", green, white, static_cast(t)); } + +template +concept string_bound = requires(const T& t) { string(t); }; + +template +inline void debug_info(string_bound&& information) { + auto info = forward(information); + + println("{}[INFO_DEBUG]{} {}", green, white, info); +} + #endif } // namespace Lazyboard::front_end_utils diff --git a/tests-ffi/sqlite.cxx b/tests-ffi/sqlite.cxx index 12251f0..578e161 100644 --- a/tests-ffi/sqlite.cxx +++ b/tests-ffi/sqlite.cxx @@ -1,27 +1,53 @@ #include #include +#include using std::uint8_t; +using std::filesystem::remove; -extern "C" enum class InitDatabaseStatus : uint8_t { +extern "C" enum class QueryResult : uint8_t { OK, - CREATE_DATABASE_FAILED, - C_STR_CONVERT_FAILED, - EXECUTE_SQL_FAILED + OPEN_DATABASE_ERR, + C_STR_CONVERT_ERR, + EXECUTE_SQL_ERR }; -extern "C" InitDatabaseStatus raw_init_clipboard_cache(const char* path); +extern "C" struct TextClipboard { + const char* content; + const char* content_hash; + const char* content_type; + bool is_pinned; +}; + +extern "C" auto raw_init_clipboard_cache(const char* path) -> QueryResult; +extern "C" QueryResult raw_add_text_clipboard(const char* path, + TextClipboard text_clipboard); int main() { - using Status = InitDatabaseStatus; + using R = QueryResult; const char* test_path = "test_init.db"; - auto status = raw_init_clipboard_cache(test_path); + remove(test_path); + + auto text_clipboard = TextClipboard{ + .content = "test", + .content_hash = "test_hash", + .content_type = "text", + .is_pinned = true, + }; + + auto init_result = raw_init_clipboard_cache(test_path); + auto add_text_result = raw_add_text_clipboard(test_path, text_clipboard); + + assert(init_result != R::OPEN_DATABASE_ERR); + assert(init_result != R::C_STR_CONVERT_ERR); + assert(init_result != R::EXECUTE_SQL_ERR); + assert(init_result == R::OK); - assert(status != Status::CREATE_DATABASE_FAILED); - assert(status != Status::C_STR_CONVERT_FAILED); - assert(status != Status::EXECUTE_SQL_FAILED); - assert(status == Status::OK); + assert(add_text_result != R::C_STR_CONVERT_ERR); + assert(add_text_result != R::EXECUTE_SQL_ERR); + assert(add_text_result != R::OPEN_DATABASE_ERR); + assert(add_text_result == R::OK); return 0; } \ No newline at end of file