feat(index): Add support for raw index metadata and expose index functions

This commit is contained in:
Kat Marchán 2023-02-19 01:18:29 -08:00
parent 70b3d5e695
commit 9991f56b4c
No known key found for this signature in database
GPG Key ID: AEB529C08A3C7E9E
3 changed files with 36 additions and 6 deletions

View File

@ -1,3 +1,5 @@
//! Raw access to the cache index. Use with caution!
use std::collections::HashSet; use std::collections::HashSet;
use std::fs::{self, OpenOptions}; use std::fs::{self, OpenOptions};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
@ -34,6 +36,8 @@ pub struct Metadata {
pub size: usize, pub size: usize,
/// Arbitrary JSON associated with this entry. /// Arbitrary JSON associated with this entry.
pub metadata: Value, pub metadata: Value,
/// Raw metadata in binary form. Can be different from JSON metadata.
pub raw_metadata: Option<Vec<u8>>,
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
@ -43,6 +47,7 @@ struct SerializableMetadata {
time: u128, time: u128,
size: usize, size: usize,
metadata: Value, metadata: Value,
raw_metadata: Option<Vec<u8>>,
} }
impl PartialEq for SerializableMetadata { impl PartialEq for SerializableMetadata {
@ -59,6 +64,7 @@ impl Hash for SerializableMetadata {
} }
} }
/// Raw insertion into the cache index.
pub fn insert(cache: &Path, key: &str, opts: WriteOpts) -> Result<Integrity> { pub fn insert(cache: &Path, key: &str, opts: WriteOpts) -> Result<Integrity> {
let bucket = bucket_path(cache, key); let bucket = bucket_path(cache, key);
fs::create_dir_all(bucket.parent().unwrap()).with_context(|| { fs::create_dir_all(bucket.parent().unwrap()).with_context(|| {
@ -73,6 +79,7 @@ pub fn insert(cache: &Path, key: &str, opts: WriteOpts) -> Result<Integrity> {
time: opts.time.unwrap_or_else(now), time: opts.time.unwrap_or_else(now),
size: opts.size.unwrap_or(0), size: opts.size.unwrap_or(0),
metadata: opts.metadata.unwrap_or(serde_json::Value::Null), metadata: opts.metadata.unwrap_or(serde_json::Value::Null),
raw_metadata: opts.raw_metadata,
}) })
.with_context(|| format!("Failed to serialize entry with key `{key}`"))?; .with_context(|| format!("Failed to serialize entry with key `{key}`"))?;
@ -93,6 +100,7 @@ pub fn insert(cache: &Path, key: &str, opts: WriteOpts) -> Result<Integrity> {
.unwrap()) .unwrap())
} }
/// Asynchronous raw insertion into the cache index.
pub async fn insert_async<'a>(cache: &'a Path, key: &'a str, opts: WriteOpts) -> Result<Integrity> { pub async fn insert_async<'a>(cache: &'a Path, key: &'a str, opts: WriteOpts) -> Result<Integrity> {
let bucket = bucket_path(cache, key); let bucket = bucket_path(cache, key);
crate::async_lib::create_dir_all(bucket.parent().unwrap()) crate::async_lib::create_dir_all(bucket.parent().unwrap())
@ -109,6 +117,7 @@ pub async fn insert_async<'a>(cache: &'a Path, key: &'a str, opts: WriteOpts) ->
time: opts.time.unwrap_or_else(now), time: opts.time.unwrap_or_else(now),
size: opts.size.unwrap_or(0), size: opts.size.unwrap_or(0),
metadata: opts.metadata.unwrap_or(serde_json::Value::Null), metadata: opts.metadata.unwrap_or(serde_json::Value::Null),
raw_metadata: opts.raw_metadata,
}) })
.with_context(|| format!("Failed to serialize entry with key `{key}`"))?; .with_context(|| format!("Failed to serialize entry with key `{key}`"))?;
@ -132,6 +141,7 @@ pub async fn insert_async<'a>(cache: &'a Path, key: &'a str, opts: WriteOpts) ->
.unwrap()) .unwrap())
} }
/// Raw index Metadata access.
pub fn find(cache: &Path, key: &str) -> Result<Option<Metadata>> { pub fn find(cache: &Path, key: &str) -> Result<Option<Metadata>> {
let bucket = bucket_path(cache, key); let bucket = bucket_path(cache, key);
Ok(bucket_entries(&bucket) Ok(bucket_entries(&bucket)
@ -150,6 +160,7 @@ pub fn find(cache: &Path, key: &str) -> Result<Option<Metadata>> {
size: entry.size, size: entry.size,
time: entry.time, time: entry.time,
metadata: entry.metadata, metadata: entry.metadata,
raw_metadata: entry.raw_metadata,
}) })
} else { } else {
None None
@ -160,6 +171,7 @@ pub fn find(cache: &Path, key: &str) -> Result<Option<Metadata>> {
})) }))
} }
/// Asynchronous raw index Metadata access.
pub async fn find_async(cache: &Path, key: &str) -> Result<Option<Metadata>> { pub async fn find_async(cache: &Path, key: &str) -> Result<Option<Metadata>> {
let bucket = bucket_path(cache, key); let bucket = bucket_path(cache, key);
Ok(bucket_entries_async(&bucket) Ok(bucket_entries_async(&bucket)
@ -179,6 +191,7 @@ pub async fn find_async(cache: &Path, key: &str) -> Result<Option<Metadata>> {
size: entry.size, size: entry.size,
time: entry.time, time: entry.time,
metadata: entry.metadata, metadata: entry.metadata,
raw_metadata: entry.raw_metadata,
}) })
} else { } else {
None None
@ -189,6 +202,7 @@ pub async fn find_async(cache: &Path, key: &str) -> Result<Option<Metadata>> {
})) }))
} }
/// Deletes an index entry, without deleting the actual cache data entry.
pub fn delete(cache: &Path, key: &str) -> Result<()> { pub fn delete(cache: &Path, key: &str) -> Result<()> {
insert( insert(
cache, cache,
@ -199,11 +213,14 @@ pub fn delete(cache: &Path, key: &str) -> Result<()> {
sri: None, sri: None,
time: None, time: None,
metadata: None, metadata: None,
raw_metadata: None,
}, },
) )
.map(|_| ()) .map(|_| ())
} }
/// Asynchronously deletes an index entry, without deleting the actual cache
/// data entry.
pub async fn delete_async(cache: &Path, key: &str) -> Result<()> { pub async fn delete_async(cache: &Path, key: &str) -> Result<()> {
insert( insert(
cache, cache,
@ -214,11 +231,13 @@ pub async fn delete_async(cache: &Path, key: &str) -> Result<()> {
sri: None, sri: None,
time: None, time: None,
metadata: None, metadata: None,
raw_metadata: None,
}, },
) )
.map(|_| ()) .map(|_| ())
} }
/// Lists raw index Metadata entries.
pub fn ls(cache: &Path) -> impl Iterator<Item = Result<Metadata>> { pub fn ls(cache: &Path) -> impl Iterator<Item = Result<Metadata>> {
let cache_path = cache.join(format!("index-v{INDEX_VERSION}")); let cache_path = cache.join(format!("index-v{INDEX_VERSION}"));
let cloned = cache_path.clone(); let cloned = cache_path.clone();
@ -258,6 +277,7 @@ pub fn ls(cache: &Path) -> impl Iterator<Item = Result<Metadata>> {
time: se.time, time: se.time,
size: se.size, size: se.size,
metadata: se.metadata, metadata: se.metadata,
raw_metadata: se.raw_metadata,
}) })
} else { } else {
None None
@ -363,7 +383,7 @@ mod tests {
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
use tokio::test as async_test; use tokio::test as async_test;
const MOCK_ENTRY: &str = "\n251d18a2b33264ea8655695fd23c88bd874cdea2c3dc9d8f9b7596717ad30fec\t{\"key\":\"hello\",\"integrity\":\"sha1-deadbeef\",\"time\":1234567,\"size\":0,\"metadata\":null}"; const MOCK_ENTRY: &str = "\n9cbbfe2553e7c7e1773f53f0f643fdd72008faa38da53ebcb055e5e20321ae47\t{\"key\":\"hello\",\"integrity\":\"sha1-deadbeef\",\"time\":1234567,\"size\":0,\"metadata\":null,\"raw_metadata\":null}";
fn ls_entries(dir: &Path) -> Vec<String> { fn ls_entries(dir: &Path) -> Vec<String> {
let mut entries = ls(dir) let mut entries = ls(dir)
@ -417,7 +437,8 @@ mod tests {
integrity: sri, integrity: sri,
time, time,
size: 0, size: 0,
metadata: json!(null) metadata: json!(null),
raw_metadata: None,
} }
); );
} }
@ -471,7 +492,8 @@ mod tests {
integrity: sri, integrity: sri,
time, time,
size: 0, size: 0,
metadata: json!(null) metadata: json!(null),
raw_metadata: None,
} }
); );
} }
@ -496,7 +518,8 @@ mod tests {
integrity: sri, integrity: sri,
time, time,
size: 0, size: 0,
metadata: json!(null) metadata: json!(null),
raw_metadata: None,
} }
); );
} }

View File

@ -121,7 +121,7 @@
//! Ok(()) //! Ok(())
//! } //! }
//! ``` //! ```
#![warn(missing_docs, rustdoc::missing_doc_code_examples)] #![warn(missing_docs)]
#[cfg(not(any(feature = "async-std", feature = "tokio-runtime")))] #[cfg(not(any(feature = "async-std", feature = "tokio-runtime")))]
compile_error!("Either feature \"async-std\" or \"tokio-runtime\" must be enabled for this crate."); compile_error!("Either feature \"async-std\" or \"tokio-runtime\" must be enabled for this crate.");
@ -136,7 +136,7 @@ mod async_lib;
mod content; mod content;
mod errors; mod errors;
mod index; pub mod index;
mod get; mod get;
mod ls; mod ls;

View File

@ -239,6 +239,7 @@ pub struct WriteOpts {
pub(crate) size: Option<usize>, pub(crate) size: Option<usize>,
pub(crate) time: Option<u128>, pub(crate) time: Option<u128>,
pub(crate) metadata: Option<Value>, pub(crate) metadata: Option<Value>,
pub(crate) raw_metadata: Option<Vec<u8>>,
} }
impl WriteOpts { impl WriteOpts {
@ -354,6 +355,12 @@ impl WriteOpts {
self self
} }
/// Sets arbitrary additional binary metadata to associate with the index entry.
pub fn raw_metadata(mut self, metadata: Vec<u8>) -> Self {
self.raw_metadata = Some(metadata);
self
}
/// Sets the specific time in unix milliseconds to associate with this /// Sets the specific time in unix milliseconds to associate with this
/// entry. This is usually automatically set to the write time, but can be /// entry. This is usually automatically set to the write time, but can be
/// useful to change for tests and such. /// useful to change for tests and such.