nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1use data_encoding::BASE64;
2use digest::{Digest, Update};
3use serde::{Deserialize, Serialize};
4use sha1::Sha1;
5use sha2::{Sha256, Sha512};
6use std::{
7 fmt::Write as FmtWrite,
8 fs::{self, File},
9 io::Write,
10 path::PathBuf,
11};
12use url::Url;
13
14#[allow(clippy::struct_field_names)]
15#[derive(Serialize, Deserialize)]
16pub(super) struct Key {
17 pub(super) key: String,
18 pub(super) integrity: String,
19 pub(super) time: u8,
20 pub(super) size: usize,
21 pub(super) metadata: Metadata,
22}
23
24#[derive(Serialize, Deserialize)]
25pub(super) struct Metadata {
26 pub(super) url: Url,
27 pub(super) options: Options,
28}
29
30#[derive(Serialize, Deserialize)]
31pub(super) struct Options {
32 pub(super) compress: bool,
33}
34
35pub struct Cache(PathBuf);
36
37fn push_hash_segments(path: &mut PathBuf, hash: &str) {
38 path.push(&hash[0..2]);
39 path.push(&hash[2..4]);
40 path.push(&hash[4..]);
41}
42
43impl Cache {
44 pub fn new(path: PathBuf) -> Cache {
45 Cache(path)
46 }
47
48 pub fn init(&self) -> anyhow::Result<()> {
49 fs::create_dir_all(self.0.join("content-v2"))?;
50 fs::create_dir_all(self.0.join("index-v5"))?;
51
52 Ok(())
53 }
54
55 pub fn put(
56 &self,
57 key: String,
58 url: Url,
59 data: &[u8],
60 integrity: Option<String>,
61 ) -> anyhow::Result<()> {
62 let (algo, hash, integrity) = if let Some(integrity) = integrity {
63 let (algo, hash) = integrity
64 .split_once('-')
65 .expect("hash should be SRI format");
66
67 (algo.to_string(), BASE64.decode(hash.as_bytes())?, integrity)
68 } else {
69 let hash = Sha512::new().chain(data).finalize();
70
71 (
72 String::from("sha512"),
73 hash.to_vec(),
74 format!("sha512-{}", BASE64.encode(&hash)),
75 )
76 };
77
78 let content_path = {
79 let mut p = self.0.join("content-v2");
80
81 p.push(algo);
82
83 push_hash_segments(
84 &mut p,
85 &hash.into_iter().fold(String::new(), |mut out, n| {
86 let _ = write!(out, "{n:02x}");
87 out
88 }),
89 );
90
91 p
92 };
93
94 fs::create_dir_all(content_path.parent().unwrap())?;
95
96 fs::write(content_path, data)?;
97
98 let index_path = {
99 let mut p = self.0.join("index-v5");
100
101 push_hash_segments(
102 &mut p,
103 &format!("{:x}", Sha256::new().chain(&key).finalize()),
104 );
105
106 p
107 };
108
109 fs::create_dir_all(index_path.parent().unwrap())?;
110
111 let data = serde_json::to_string(&Key {
112 key,
113 integrity,
114 time: 0,
115 size: data.len(),
116 metadata: Metadata {
117 url,
118 options: Options { compress: true },
119 },
120 })?;
121
122 let mut file = File::options().append(true).create(true).open(index_path)?;
123
124 write!(file, "{:x}\t{data}", Sha1::new().chain(&data).finalize())?;
125
126 Ok(())
127 }
128}