From 44eb2acc98b242747ff09460e0c276593dfe3840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sun, 2 Jun 2019 22:40:46 +0200 Subject: [PATCH] feat(index): implemented find() --- src/index.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 102 insertions(+), 11 deletions(-) diff --git a/src/index.rs b/src/index.rs index 684c538..277bcc9 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,5 +1,5 @@ -use std::fs::OpenOptions; -use std::io::Write; +use std::fs::{self, OpenOptions}; +use std::io::{ErrorKind, Write}; use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; @@ -16,19 +16,29 @@ use ssri::Integrity; const INDEX_VERSION: &str = "5"; -#[derive(Deserialize, Serialize)] -struct Entry { +#[derive(PartialEq, Debug)] +pub struct Entry { key: String, + // TODO - implement Serialize for Integrity! integrity: String, time: u128, size: u128, metadata: Value, } +#[derive(Deserialize, Serialize, PartialEq, Debug)] +struct SerializableEntry { + key: String, + integrity: Option, + time: u128, + size: u128, + metadata: Value, +} + pub struct Inserter { cache: PathBuf, key: String, - sri: Integrity, + sri: Option, size: Option, time: Option, metadata: Option, @@ -52,6 +62,11 @@ impl Inserter { self } + pub fn integrity(mut self, integrity: Option) -> Self { + self.sri = integrity; + self + } + pub fn chown(mut self, uid: Option, gid: Option) -> Self { self.uid = uid; self.gid = gid; @@ -63,9 +78,9 @@ impl Inserter { if let Some(path) = mkdirp::mkdirp(bucket.parent().unwrap())? { chownr::chownr(path.as_path(), self.uid, self.gid)?; } - let stringified = serde_json::to_string(&Entry { + let stringified = serde_json::to_string(&SerializableEntry { key: self.key.to_owned(), - integrity: self.sri.to_string(), + integrity: self.sri.map(|x| x.to_string()), time: self.time.unwrap_or_else(now), size: self.size.unwrap_or(0), metadata: self.metadata.unwrap_or_else(|| json!(null)), @@ -86,7 +101,7 @@ pub fn insert(cache: &Path, key: &str, sri: Integrity) -> Inserter { cache: cache.to_path_buf(), key: String::from(key), size: None, - sri, + sri: Some(sri), time: None, metadata: None, uid: None, @@ -94,8 +109,25 @@ pub fn insert(cache: &Path, key: &str, sri: Integrity) -> Inserter { } } -pub fn find(_cache: &Path, _key: &str) { - unimplemented!(); +pub fn find(cache: &Path, key: &str) -> Result, Error> { + let bucket = bucket_path(cache, &key); + Ok(bucket_entries(bucket.as_path())?.into_iter().fold(None, |acc, entry| { + if entry.key == key { + if entry.integrity.is_some() { + Some(Entry { + key: entry.key, + integrity: entry.integrity.unwrap(), + size: entry.size, + time: entry.time, + metadata: entry.metadata + }) + } else { + None + } + } else { + acc + } + })) } pub fn delete(_cache: &Path, _key: &str) { @@ -136,10 +168,41 @@ fn now() -> u128 { .as_millis() } +fn bucket_entries(bucket: &Path) -> Result, Error> { + let lines = match fs::read_to_string(bucket) { + Ok(data) => Ok(data), + Err(ref e) if e.kind() == ErrorKind::NotFound => Ok(String::from("")), + err => err, + }?; + Ok(lines.split('\n').fold(vec![], |mut acc, entry: &str| { + if entry.is_empty() { return acc } + let entry_str = match entry.split('\t').collect::>()[..] { + [hash, entry_str] => { + if hash_entry(entry_str) != hash { + // Hash is no good! Corruption or malice? Doesn't matter! + // EJECT EJECT + return acc + } else { + entry_str + } + }, + // Something's wrong with the entry. Abort. + _ => return acc, + }; + if let Ok(entry) = serde_json::from_str::(entry_str) { + acc.push(entry) + } + acc + })) +} + #[cfg(test)] mod tests { use super::*; use tempfile; + + const MOCK_ENTRY: &str = "\n251d18a2b33264ea8655695fd23c88bd874cdea2c3dc9d8f9b7596717ad30fec\t{\"key\":\"hello\",\"integrity\":\"sha1-deadbeef\",\"time\":1234567,\"size\":0,\"metadata\":null}"; + #[test] fn insert_basic() { let tmp = tempfile::tempdir().unwrap(); @@ -148,7 +211,35 @@ mod tests { let time = 1_234_567; insert(&dir, "hello", sri).time(time).commit().unwrap(); let entry = std::fs::read_to_string(bucket_path(&dir, "hello")).unwrap(); + assert_eq!(entry, MOCK_ENTRY); + } + + #[test] + fn find_basic() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + let sri: Integrity = "sha1-deadbeef".parse().unwrap(); + let time = 1_234_567; + let bucket = bucket_path(dir.as_path(), "hello"); + mkdirp::mkdirp(bucket.parent().unwrap()).unwrap(); + fs::write(bucket, MOCK_ENTRY).unwrap(); + let entry = find(&dir, "hello").unwrap().unwrap(); assert_eq!( - entry, "\n251d18a2b33264ea8655695fd23c88bd874cdea2c3dc9d8f9b7596717ad30fec\t{\"key\":\"hello\",\"integrity\":\"sha1-deadbeef\",\"time\":1234567,\"size\":0,\"metadata\":null}") + entry, + Entry { + key: String::from("hello"), + integrity: sri.to_string(), + time, + size: 0, + metadata: json!(null) + } + ); + } + + #[test] + fn find_none() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().to_owned(); + assert_eq!(find(&dir, "hello").unwrap(), None); } }