gix_odb/store_impls/loose/
write.rs

1use 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/// Returned by the [`gix_object::Write`] trait implementation of [`Store`]
11#[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    /// Write the given buffer in `from` to disk in one syscall at best.
47    ///
48    /// This will cost at least 4 IO operations.
49    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    /// Write the given stream in `from` to disk with at least one syscall.
68    ///
69    /// This will cost at least 4 IO operations.
70    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
98/// Access
99impl Store {
100    /// Return the path to the object with `id`.
101    ///
102    /// Note that is may not exist yet.
103    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        // On windows, we assume that such errors are due to its special filesystem semantics,
146        // on any other platform that would be a legitimate error though.
147        #[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}