diff --git a/Cargo.lock b/Cargo.lock index fa5e26b..f838599 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -1459,6 +1459,7 @@ dependencies = [ name = "gdn-app" version = "0.0.0" dependencies = [ + "rand 0.9.0", "serde", "serde_json", "tauri", @@ -3993,7 +3994,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -4176,6 +4177,17 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy 0.8.17", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -4196,6 +4208,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -4214,6 +4236,16 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy 0.8.17", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -6616,7 +6648,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +dependencies = [ + "zerocopy-derive 0.8.17", ] [[package]] @@ -6630,6 +6671,17 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index 5a79fef..8a9758d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ clap = { version = "4.5.28", features = ["derive", "deprecated"] } directories = "6.0.0" gdn = { path = "gdn" } gix = "0.70.0" +rand = "0.9.0" serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.138" tauri = { version = "2.2.5", features = [] } diff --git a/gdn-app/src-tauri/Cargo.toml b/gdn-app/src-tauri/Cargo.toml index b065a67..f689f7b 100644 --- a/gdn-app/src-tauri/Cargo.toml +++ b/gdn-app/src-tauri/Cargo.toml @@ -14,6 +14,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { workspace = true } [dependencies] +rand = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tauri = { workspace = true } diff --git a/gdn-app/src-tauri/src/ids.rs b/gdn-app/src-tauri/src/ids.rs new file mode 100644 index 0000000..ddd5f34 --- /dev/null +++ b/gdn-app/src-tauri/src/ids.rs @@ -0,0 +1,99 @@ +use std::{fmt, ops::Shl, str::FromStr, time::SystemTime}; + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// A timestamp- and randomness-based id. +/// +/// The id consists of 8 bytes. The first 5 bytes are an epoch timestamp in +/// seconds, while the remaining 3 bytes are a random number, meant to avoid +/// collisions when two ids are created at the same time on different devices. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct TimestampId(u64); + +impl TimestampId { + fn new() -> Self { + let secs = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("duration is positive") + .as_secs(); + + let random = rand::random::(); + + // Zeroing the last three bytes just in case. They should already be + // zero under normal circumstances. + let first_part = secs.shl(3 * 8) & 0xFFFFFFFF_FF000000_u64; + let second_part = random & 0x00000000_00FFFFFF_u64; + + Self(first_part | second_part) + } +} + +impl fmt::Display for TimestampId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:016X}", self.0) + } +} + +impl FromStr for TimestampId { + type Err = (); + + fn from_str(s: &str) -> Result { + if s.len() != 16 { + return Err(()); + } + + if s.chars().any(|c| c.is_lowercase()) { + return Err(()); + } + + let n = u64::from_str_radix(s, 16).map_err(|_| ())?; + + Ok(Self(n)) + } +} + +/// The id for a single note. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct NoteId(TimestampId); + +impl NoteId { + #[allow(clippy::new_without_default)] // Because side-effects + pub fn new() -> Self { + Self(TimestampId::new()) + } +} + +impl fmt::Debug for NoteId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "NoteId({self})") + } +} + +impl fmt::Display for NoteId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "n{}", self.0) + } +} + +impl FromStr for NoteId { + type Err = (); + + fn from_str(s: &str) -> Result { + let s = s.strip_prefix("n").ok_or(())?; + Ok(Self(s.parse()?)) + } +} + +impl Serialize for NoteId { + fn serialize(&self, serializer: S) -> Result { + format!("{self}").serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for NoteId { + fn deserialize>(deserializer: D) -> Result { + <&'de str as Deserialize<'de>>::deserialize(deserializer)? + .parse() + .map_err(|()| serde::de::Error::custom("invalid note id")) + } +} diff --git a/gdn-app/src-tauri/src/lib.rs b/gdn-app/src-tauri/src/lib.rs index 4a277ef..e393a70 100644 --- a/gdn-app/src-tauri/src/lib.rs +++ b/gdn-app/src-tauri/src/lib.rs @@ -1,3 +1,5 @@ +mod ids; + // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ #[tauri::command] fn greet(name: &str) -> String { diff --git a/gdn-app/src-tauri/src/main.rs b/gdn-app/src-tauri/src/main.rs index 11042ad..9e160aa 100644 --- a/gdn-app/src-tauri/src/main.rs +++ b/gdn-app/src-tauri/src/main.rs @@ -1,5 +1,7 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +// This warning does not apply here - the lib uses the dependencies. +#![allow(unused_crate_dependencies)] fn main() { gdn_app_lib::run()