gix_odb/store_impls/loose/
write.rs1use std::{fs, io, io::Write, path::PathBuf};
2
3use gix_features::{hash, zlib::stream::deflate};
4use gix_object::WriteTo;
5use tempfile::NamedTempFile;
6
7use super::Store;
8use crate::store_impls::loose;
9
10#[derive(thiserror::Error, Debug)]
12#[allow(missing_docs)]
13pub enum Error {
14 #[error("Could not {message} '{path}'")]
15 Io {
16 source: io::Error,
17 message: &'static str,
18 path: PathBuf,
19 },
20 #[error("An IO error occurred while writing an object")]
21 IoRaw(#[from] io::Error),
22 #[error("Could not turn temporary file into persisted file at '{target}'")]
23 Persist {
24 source: tempfile::PersistError,
25 target: PathBuf,
26 },
27}
28
29impl gix_object::Write for Store {
30 fn write(&self, object: &dyn WriteTo) -> Result<gix_hash::ObjectId, gix_object::write::Error> {
31 let mut to = self.dest()?;
32 to.write_all(&object.loose_header()).map_err(|err| Error::Io {
33 source: err,
34 message: "write header to tempfile in",
35 path: self.path.to_owned(),
36 })?;
37 object.write_to(&mut to).map_err(|err| Error::Io {
38 source: err,
39 message: "stream all data into tempfile in",
40 path: self.path.to_owned(),
41 })?;
42 to.flush().map_err(Box::new)?;
43 Ok(self.finalize_object(to).map_err(Box::new)?)
44 }
45
46 fn write_buf(&self, kind: gix_object::Kind, from: &[u8]) -> Result<gix_hash::ObjectId, gix_object::write::Error> {
50 let mut to = self.dest().map_err(Box::new)?;
51 to.write_all(&gix_object::encode::loose_header(kind, from.len() as u64))
52 .map_err(|err| Error::Io {
53 source: err,
54 message: "write header to tempfile in",
55 path: self.path.to_owned(),
56 })?;
57
58 to.write_all(from).map_err(|err| Error::Io {
59 source: err,
60 message: "stream all data into tempfile in",
61 path: self.path.to_owned(),
62 })?;
63 to.flush()?;
64 Ok(self.finalize_object(to)?)
65 }
66
67 fn write_stream(
71 &self,
72 kind: gix_object::Kind,
73 size: u64,
74 mut from: &mut dyn io::Read,
75 ) -> Result<gix_hash::ObjectId, gix_object::write::Error> {
76 let mut to = self.dest().map_err(Box::new)?;
77 to.write_all(&gix_object::encode::loose_header(kind, size))
78 .map_err(|err| Error::Io {
79 source: err,
80 message: "write header to tempfile in",
81 path: self.path.to_owned(),
82 })?;
83
84 io::copy(&mut from, &mut to)
85 .map_err(|err| Error::Io {
86 source: err,
87 message: "stream all data into tempfile in",
88 path: self.path.to_owned(),
89 })
90 .map_err(Box::new)?;
91 to.flush().map_err(Box::new)?;
92 Ok(self.finalize_object(to)?)
93 }
94}
95
96type CompressedTempfile = deflate::Write<NamedTempFile>;
97
98impl Store {
100 pub fn object_path(&self, id: &gix_hash::oid) -> PathBuf {
104 loose::hash_path(id, self.path.clone())
105 }
106}
107
108impl Store {
109 fn dest(&self) -> Result<hash::Write<CompressedTempfile>, Error> {
110 #[cfg_attr(not(unix), allow(unused_mut))]
111 let mut builder = tempfile::Builder::new();
112 #[cfg(unix)]
113 {
114 use std::os::unix::fs::PermissionsExt;
115 let perms = std::fs::Permissions::from_mode(0o444);
116 builder.permissions(perms);
117 }
118 Ok(hash::Write::new(
119 deflate::Write::new(builder.tempfile_in(&self.path).map_err(|err| Error::Io {
120 source: err,
121 message: "create named temp file in",
122 path: self.path.to_owned(),
123 })?),
124 self.object_hash,
125 ))
126 }
127
128 fn finalize_object(
129 &self,
130 hash::Write { hash, inner: file }: hash::Write<CompressedTempfile>,
131 ) -> Result<gix_hash::ObjectId, Error> {
132 let id = gix_hash::ObjectId::from(hash.digest());
133 let object_path = loose::hash_path(&id, self.path.clone());
134 let object_dir = object_path
135 .parent()
136 .expect("each object path has a 1 hex-bytes directory");
137 if let Err(err) = fs::create_dir(object_dir) {
138 match err.kind() {
139 io::ErrorKind::AlreadyExists => {}
140 _ => return Err(err.into()),
141 }
142 }
143 let file = file.into_inner();
144 let res = file.persist(&object_path);
145 #[cfg(windows)]
148 if let Err(err) = &res {
149 if err.error.kind() == std::io::ErrorKind::PermissionDenied
150 || err.error.kind() == std::io::ErrorKind::AlreadyExists
151 {
152 return Ok(id);
153 }
154 }
155 res.map_err(|err| Error::Persist {
156 source: err,
157 target: object_path,
158 })?;
159 Ok(id)
160 }
161}