The master copy of the pictures, as files in one directory named by the picture's id. A picture is written to a temporary name and then linked into place, so a reader never sees half of one and a name already kept is never replaced.
17impl DirShelf { 18 pub fn open(dir: &Path) -> Self { 19 if let Err(e) = std::fs::create_dir_all(dir) { 20 error!("cannot create the picture directory {}: {e}", dir.display()); 21 } 22 Self { dir: dir.to_owned(), counter: AtomicU64::new(0) } 23 } 24 25 pub fn dir(&self) -> &Path { 26 &self.dir 27 } 28} 29 30impl DirShelf { 31 fn put_in_place(&self, id: &SafePictureId, bytes: &[u8]) -> Result<Put, StoreError> { 32 match std::fs::OpenOptions::new().write(true).create_new(true).open(self.dir.join(id.as_str())) { 33 Ok(mut f) => match std::io::Write::write_all(&mut f, bytes).and_then(|()| f.sync_data()) { 34 Ok(()) => Ok(Put::Stored), 35 Err(e) => { 36 error!("picture {} not written: {e}", id.as_str()); 37 Err(StoreError::Unavailable) 38 } 39 }, 40 Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(Put::AlreadyKept), 41 Err(e) => { 42 error!("picture {} cannot be created: {e}", id.as_str()); 43 Err(StoreError::Unavailable) 44 } 45 } 46 } 47} 48 49impl PictureShelf for DirShelf { 50 async fn has(&self, id: &SafePictureId) -> Result<bool, StoreError> { 51 Ok(self.dir.join(id.as_str()).is_file()) 52 } 53 54 async fn put(&self, id: &SafePictureId, bytes: &[u8]) -> Result<Put, StoreError> { 55 let target = self.dir.join(id.as_str()); 56 // A name that starts with a dot is never a picture id, so this cannot be taken for one. 57 let temp = self.dir.join(format!(".put-{}-{}", std::process::id(), self.counter.fetch_add(1, Ordering::SeqCst))); 58 let written = std::fs::File::create(&temp).and_then(|mut f| std::io::Write::write_all(&mut f, bytes).and_then(|()| f.sync_data())); 59 if let Err(e) = written { 60 error!("picture {} not written: {e}", id.as_str()); 61 let _ = std::fs::remove_file(&temp); 62 return Err(StoreError::Unavailable); 63 } 64 let linked = std::fs::hard_link(&temp, &target); 65 let _ = std::fs::remove_file(&temp); 66 match linked { 67 Ok(()) => { 68 debug!("picture {} stored ({} bytes)", id.as_str(), bytes.len()); 69 Ok(Put::Stored) 70 } 71 Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => { 72 trace!("picture {} already kept", id.as_str()); 73 Ok(Put::AlreadyKept) 74 } 75 Err(e) => { 76 // A filesystem without hard links: write in place, which never replaces a name but can 77 // be seen half-written by a reader that arrives at that instant. 78 warn!("picture {}: cannot link it into place ({e}); writing it in place", id.as_str()); 79 self.put_in_place(id, bytes) 80 } 81 } 82 } 83 84 async fn get(&self, id: &SafePictureId) -> Result<Option<Vec<u8>>, StoreError> { 85 match std::fs::read(self.dir.join(id.as_str())) { 86 Ok(bytes) => { 87 trace!("picture {} read ({} bytes)", id.as_str(), bytes.len()); 88 Ok(Some(bytes)) 89 } 90 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), 91 Err(e) => { 92 error!("picture {} unreadable: {e}", id.as_str()); 93 Err(StoreError::Unavailable) 94 } 95 } 96 } 97}