canic_template_runtime/storage/template/
chunked.rs1use canic_cdk::structures::{BTreeMap, DefaultMemoryImpl, memory::VirtualMemory};
2use canic_memory::{eager_static, ic_memory, impl_storable_unbounded};
3use canic_template_types::ids::{TemplateChunkKey, TemplateReleaseKey};
4use serde::{Deserialize, Serialize};
5use std::{cell::RefCell, collections::BTreeMap as StdBTreeMap};
6
7const TEMPLATE_CHUNK_SETS_ID: u8 = 11;
8const TEMPLATE_CHUNKS_ID: u8 = 12;
9
10eager_static! {
11 static TEMPLATE_CHUNK_SETS: RefCell<
12 BTreeMap<TemplateReleaseKey, TemplateChunkSetRecord, VirtualMemory<DefaultMemoryImpl>>
13 > = RefCell::new(
14 BTreeMap::init(ic_memory!(TemplateChunkSetStateStore, TEMPLATE_CHUNK_SETS_ID)),
15 );
16}
17
18eager_static! {
19 static TEMPLATE_CHUNKS: RefCell<
20 BTreeMap<TemplateChunkKey, TemplateChunkRecord, VirtualMemory<DefaultMemoryImpl>>
21 > = RefCell::new(
22 BTreeMap::init(ic_memory!(TemplateChunkStore, TEMPLATE_CHUNKS_ID)),
23 );
24}
25
26#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
31pub struct TemplateChunkSetRecord {
32 pub payload_hash: Vec<u8>,
33 pub payload_size_bytes: u64,
34 pub chunk_count: u32,
35 pub chunk_hashes: Vec<Vec<u8>>,
36 pub created_at: u64,
37}
38
39impl_storable_unbounded!(TemplateChunkSetRecord);
40
41#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
46pub struct TemplateChunkRecord {
47 pub bytes: Vec<u8>,
48}
49
50impl_storable_unbounded!(TemplateChunkRecord);
51
52pub struct TemplateChunkSetStateStore;
57
58impl TemplateChunkSetStateStore {
59 pub fn upsert(release: TemplateReleaseKey, record: TemplateChunkSetRecord) {
61 TEMPLATE_CHUNK_SETS.with_borrow_mut(|map| {
62 map.insert(release, record);
63 });
64 }
65
66 #[must_use]
68 pub fn get(release: &TemplateReleaseKey) -> Option<TemplateChunkSetRecord> {
69 TEMPLATE_CHUNK_SETS.with_borrow(|map| map.get(release))
70 }
71
72 #[must_use]
74 pub fn export() -> Vec<(TemplateReleaseKey, TemplateChunkSetRecord)> {
75 TEMPLATE_CHUNK_SETS.with_borrow(|map| {
76 map.iter()
77 .map(|entry| (entry.key().clone(), entry.value()))
78 .collect()
79 })
80 }
81
82 pub fn clear() {
84 TEMPLATE_CHUNK_SETS.with_borrow_mut(BTreeMap::clear);
85 }
86
87 pub fn clear_for_test() {
89 Self::clear();
90 }
91}
92
93pub struct TemplateChunkStore;
98
99impl TemplateChunkStore {
100 pub fn upsert(chunk_key: TemplateChunkKey, record: TemplateChunkRecord) {
102 TEMPLATE_CHUNKS.with_borrow_mut(|map| {
103 map.insert(chunk_key, record);
104 });
105 }
106
107 #[must_use]
109 pub fn get(chunk_key: &TemplateChunkKey) -> Option<TemplateChunkRecord> {
110 TEMPLATE_CHUNKS.with_borrow(|map| map.get(chunk_key))
111 }
112
113 #[must_use]
115 pub fn export() -> Vec<(TemplateChunkKey, TemplateChunkRecord)> {
116 TEMPLATE_CHUNKS.with_borrow(|map| {
117 map.iter()
118 .map(|entry| (entry.key().clone(), entry.value()))
119 .collect()
120 })
121 }
122
123 #[must_use]
125 pub fn count_by_release() -> StdBTreeMap<TemplateReleaseKey, u32> {
126 TEMPLATE_CHUNKS.with_borrow(|map| {
127 let mut counts: StdBTreeMap<TemplateReleaseKey, u32> = StdBTreeMap::new();
128
129 for entry in map.iter() {
130 let release = entry.key().release.clone();
131 let count = counts.entry(release).or_insert(0);
132 *count = u32::saturating_add(*count, 1);
133 }
134
135 counts
136 })
137 }
138
139 pub fn clear() {
141 TEMPLATE_CHUNKS.with_borrow_mut(BTreeMap::clear);
142 }
143
144 pub fn clear_for_test() {
146 Self::clear();
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use canic_template_types::ids::{TemplateId, TemplateVersion};
154
155 fn release() -> TemplateReleaseKey {
156 TemplateReleaseKey::new(
157 TemplateId::new("embedded:app"),
158 TemplateVersion::new("0.18.0"),
159 )
160 }
161
162 #[test]
163 fn chunk_set_store_round_trip() {
164 TemplateChunkSetStateStore::clear_for_test();
165 let record = TemplateChunkSetRecord {
166 payload_hash: vec![1; 32],
167 payload_size_bytes: 7,
168 chunk_count: 2,
169 chunk_hashes: vec![vec![2; 32], vec![3; 32]],
170 created_at: 99,
171 };
172
173 TemplateChunkSetStateStore::upsert(release(), record.clone());
174
175 assert_eq!(TemplateChunkSetStateStore::get(&release()), Some(record));
176 }
177
178 #[test]
179 fn chunk_store_round_trip() {
180 TemplateChunkStore::clear_for_test();
181 let chunk_key = TemplateChunkKey::new(release(), 0);
182 let record = TemplateChunkRecord {
183 bytes: vec![1, 2, 3],
184 };
185
186 TemplateChunkStore::upsert(chunk_key.clone(), record.clone());
187
188 assert_eq!(TemplateChunkStore::get(&chunk_key), Some(record));
189 }
190}