From a93adcd21c8eb574f53eed6521821caea19c1010 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Mon, 29 Mar 2021 12:21:23 -0600 Subject: [PATCH] finish macros for structs --- patch-db-macro-internals/src/lib.rs | 112 +++++++++++++++++++++++++++- patch-db/Cargo.toml | 1 + patch-db/src/lib.rs | 33 ++++++-- patch-db/src/test.rs | 4 +- 4 files changed, 137 insertions(+), 13 deletions(-) diff --git a/patch-db-macro-internals/src/lib.rs b/patch-db-macro-internals/src/lib.rs index 94f1e69..d0125f3 100644 --- a/patch-db-macro-internals/src/lib.rs +++ b/patch-db-macro-internals/src/lib.rs @@ -60,7 +60,27 @@ fn build_model_struct( let ident = field.ident.clone().unwrap(); child_fn_name.push(ident.clone()); let ty = &field.ty; - child_model.push(syn::parse2(quote! { patch_db::Model<#ty> }).unwrap()); // TODO: check attr + if let Some(child_model_name) = field + .attrs + .iter() + .filter(|attr| attr.path.is_ident("model")) + .filter_map(|attr| Some(attr.parse_args::().unwrap())) + .filter(|nv| nv.path.is_ident("name")) + .find_map(|nv| match nv.lit { + Lit::Str(s) => Some(s), + _ => None, + }) + { + let child_model_ty = + Ident::new(&child_model_name.value(), child_model_name.span()); + child_model + .push(syn::parse2(quote! { #child_model_ty }).expect("invalid model name")); + } else if field.attrs.iter().any(|attr| attr.path.is_ident("model")) { + child_model + .push(syn::parse2(quote! { <#ty as patch_db::HasModel>::Model }).unwrap()); + } else { + child_model.push(syn::parse2(quote! { patch_db::Model<#ty> }).unwrap()); + } let serde_rename = field .attrs .iter() @@ -107,9 +127,91 @@ fn build_model_struct( } Fields::Unnamed(f) => { if f.unnamed.len() == 1 { + let field = &f.unnamed[0]; + let ty = &field.ty; + let inner_model: Type = if let Some(child_model_name) = field + .attrs + .iter() + .filter(|attr| attr.path.is_ident("model")) + .filter_map(|attr| Some(attr.parse_args::().unwrap())) + .filter(|nv| nv.path.is_ident("name")) + .find_map(|nv| match nv.lit { + Lit::Str(s) => Some(s), + _ => None, + }) { + let child_model_ty = + Ident::new(&child_model_name.value(), child_model_name.span()); + syn::parse2(quote! { #child_model_ty }).unwrap() + } else if field.attrs.iter().any(|attr| attr.path.is_ident("model")) { + syn::parse2(quote! { <#ty as patch_db::HasModel>::Model }).unwrap() + } else { + syn::parse2(quote! { patch_db::Model<#ty> }).unwrap() + }; + return quote! { + #[derive(Debug, Clone)] + #model_vis struct #model_name(#inner_model); + impl core::ops::Deref for #model_name { + type Target = #inner_model; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + impl #model_name { + pub fn new(ptr: json_ptr::JsonPointer) -> Self { + #model_name(#inner_model::new(ptr)) + } + } + impl From> for #model_name { + fn from(model: patch_db::Model<#base_name>) -> Self { + #model_name(#inner_model::from(model)) + } + } + impl From<#inner_model> for #model_name { + fn from(model: #inner_model) -> Self { + #model_name(model) + } + } + impl patch_db::HasModel for #base_name { + type Model = #model_name; + } + }; } else if f.unnamed.len() > 1 { + for (i, field) in f.unnamed.iter().enumerate() { + child_fn_name.push(Ident::new( + &format!("idx_{}", i), + proc_macro2::Span::call_site(), + )); + let ty = &field.ty; + if let Some(child_model_name) = field + .attrs + .iter() + .filter(|attr| attr.path.is_ident("model")) + .filter_map(|attr| Some(attr.parse_args::().unwrap())) + .filter(|nv| nv.path.is_ident("name")) + .find_map(|nv| match nv.lit { + Lit::Str(s) => Some(s), + _ => None, + }) + { + let child_model_ty = + Ident::new(&child_model_name.value(), child_model_name.span()); + child_model.push( + syn::parse2(quote! { #child_model_ty }).expect("invalid model name"), + ); + } else if field.attrs.iter().any(|attr| attr.path.is_ident("model")) { + child_model.push( + syn::parse2(quote! { <#ty as patch_db::HasModel>::Model }).unwrap(), + ); + } else { + child_model.push(syn::parse2(quote! { patch_db::Model<#ty> }).unwrap()); + } + // TODO: serde rename for tuple structs? + child_path.push(LitStr::new( + &format!("{}", i), + proc_macro2::Span::call_site(), + )); + } } - todo!() } Fields::Unit => (), } @@ -126,13 +228,17 @@ fn build_model_struct( pub fn new(ptr: json_ptr::JsonPointer) -> Self { #model_name(patch_db::Model::new(ptr)) } - // foreach element, create accessor fn #( pub fn #child_fn_name(&self) -> #child_model { self.0.child(#child_path).into() } )* } + impl From> for #model_name { + fn from(model: patch_db::Model<#base_name>) -> Self { + #model_name(model) + } + } impl patch_db::HasModel for #base_name { type Model = #model_name; } diff --git a/patch-db/Cargo.toml b/patch-db/Cargo.toml index 0085fd3..a049622 100644 --- a/patch-db/Cargo.toml +++ b/patch-db/Cargo.toml @@ -20,6 +20,7 @@ serde_cbor = { path = "../cbor" } thiserror = "1.0.23" tokio = { version = "1.0.1", features = ["sync", "fs", "rt", "io-util", "macros"] } patch-db-macro = { path = "../patch-db-macro" } +lazy_static = "1.4.0" [dev-dependencies] proptest = "1.0.0" diff --git a/patch-db/src/lib.rs b/patch-db/src/lib.rs index fc7aafa..57714af 100644 --- a/patch-db/src/lib.rs +++ b/patch-db/src/lib.rs @@ -1,16 +1,17 @@ -use std::collections::HashMap; use std::fs::OpenOptions; use std::io::Error as IOError; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::path::Path; use std::sync::Arc; +use std::{collections::HashMap, path::PathBuf}; use fd_lock_rs::FdLock; use futures::future::{BoxFuture, FutureExt}; use json_patch::{AddOperation, Patch, PatchOperation, RemoveOperation, ReplaceOperation}; use json_ptr::{JsonPointer, SegList}; -use qutex_2::{QrwLock, ReadGuard, WriteGuard}; +use lazy_static::lazy_static; +use qutex_2::{Guard, QrwLock, Qutex, ReadGuard, WriteGuard}; use serde::{Deserialize, Serialize}; use serde_json::Value; use thiserror::Error; @@ -186,28 +187,43 @@ impl DiffPatch { } } +lazy_static! { + static ref OPEN_STORES: Mutex>> = Mutex::new(HashMap::new()); +} + pub struct Store { file: FdLock, + _lock: Guard<()>, cache_corrupted: Option>, data: Value, revision: u64, } impl Store { - pub async fn open + Send + 'static>(path: P) -> Result { + pub async fn open>(path: P) -> Result { + let path = tokio::fs::canonicalize(path).await?; + let _lock = { + let mut lock = OPEN_STORES.lock().await; + if let Some(open) = lock.get(&path) { + open.clone().lock().await.unwrap() + } else { + let tex = Qutex::new(()); + lock.insert(path.clone(), tex.clone()); + tex.lock().await.unwrap() + } + }; Ok(tokio::task::spawn_blocking(move || { use std::io::Write; - let p = path.as_ref(); - let bak = p.with_extension("bak"); + let bak = path.with_extension("bak"); if bak.exists() { - std::fs::rename(&bak, p)?; + std::fs::rename(&bak, &path)?; } let mut f = FdLock::lock( OpenOptions::new() .create(true) .read(true) .append(true) - .open(p)?, + .open(&path)?, fd_lock_rs::LockType::Exclusive, true, )?; @@ -238,6 +254,7 @@ impl Store { Ok::<_, Error>(Store { file: f.map(File::from_std), + _lock, cache_corrupted: None, data, revision, @@ -326,7 +343,7 @@ pub struct PatchDb { locker: Locker, } impl PatchDb { - pub async fn open + Send + 'static>(path: P) -> Result { + pub async fn open>(path: P) -> Result { let (subscriber, _) = tokio::sync::broadcast::channel(16); // TODO: make this unbounded Ok(PatchDb { diff --git a/patch-db/src/test.rs b/patch-db/src/test.rs index 6e3c147..07d5842 100644 --- a/patch-db/src/test.rs +++ b/patch-db/src/test.rs @@ -80,11 +80,11 @@ proptest! { #[derive(Debug, serde::Deserialize, serde::Serialize, HasModel)] pub struct Sample { a: String, - #[model(name = ChildModel)] + #[model(name = "ChildModel")] b: Child, } -#[derive(Debug, serde::Deserialize, serde::Serialize)] +#[derive(Debug, serde::Deserialize, serde::Serialize, HasModel)] pub struct Child { a: String, b: usize,