Skip to main content

oxihuman_core/
archive_writer.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! ZIP archive writer stub.
6
7/// A pending write entry.
8#[derive(Debug, Clone)]
9pub struct WriteEntry {
10    pub name: String,
11    pub data: Vec<u8>,
12    pub compression_level: u8,
13}
14
15impl WriteEntry {
16    pub fn new(name: &str, data: Vec<u8>) -> Self {
17        WriteEntry {
18            name: name.to_string(),
19            data,
20            compression_level: 6,
21        }
22    }
23
24    pub fn with_compression(mut self, level: u8) -> Self {
25        self.compression_level = level.min(9);
26        self
27    }
28}
29
30/// Archive writer stub — accumulates entries in memory.
31pub struct ArchiveWriter {
32    destination: String,
33    entries: Vec<WriteEntry>,
34    comment: String,
35}
36
37impl ArchiveWriter {
38    pub fn new(destination: &str) -> Self {
39        ArchiveWriter {
40            destination: destination.to_string(),
41            entries: Vec::new(),
42            comment: String::new(),
43        }
44    }
45
46    pub fn add_entry(&mut self, entry: WriteEntry) {
47        self.entries.push(entry);
48    }
49
50    pub fn add_bytes(&mut self, name: &str, data: Vec<u8>) {
51        self.entries.push(WriteEntry::new(name, data));
52    }
53
54    pub fn add_text(&mut self, name: &str, text: &str) {
55        self.add_bytes(name, text.as_bytes().to_vec());
56    }
57
58    pub fn set_comment(&mut self, comment: &str) {
59        self.comment = comment.to_string();
60    }
61
62    pub fn entry_count(&self) -> usize {
63        self.entries.len()
64    }
65
66    pub fn total_uncompressed_size(&self) -> u64 {
67        self.entries.iter().map(|e| e.data.len() as u64).sum()
68    }
69
70    /// Finalize (stub — returns byte count that would be written).
71    pub fn finalize(&self) -> u64 {
72        self.total_uncompressed_size()
73    }
74
75    pub fn destination(&self) -> &str {
76        &self.destination
77    }
78
79    pub fn has_entry(&self, name: &str) -> bool {
80        self.entries.iter().any(|e| e.name == name)
81    }
82}
83
84impl Default for ArchiveWriter {
85    fn default() -> Self {
86        Self::new("/tmp/out.zip")
87    }
88}
89
90/// Create a new archive writer.
91pub fn new_archive_writer(path: &str) -> ArchiveWriter {
92    ArchiveWriter::new(path)
93}
94
95/// Write a directory of entries (stub: just adds them all).
96pub fn write_entries(writer: &mut ArchiveWriter, entries: Vec<WriteEntry>) {
97    for e in entries {
98        writer.add_entry(e);
99    }
100}
101
102/// Build an archive from name/data pairs.
103pub fn build_archive(path: &str, items: &[(&str, &[u8])]) -> ArchiveWriter {
104    let mut w = new_archive_writer(path);
105    for (name, data) in items {
106        w.add_bytes(name, data.to_vec());
107    }
108    w
109}
110
111/// Total bytes that would be written.
112pub fn estimate_size(writer: &ArchiveWriter) -> u64 {
113    writer.total_uncompressed_size()
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_new_writer() {
122        let w = new_archive_writer("/tmp/x.zip");
123        assert_eq!(w.entry_count(), 0);
124        assert_eq!(w.destination(), "/tmp/x.zip");
125    }
126
127    #[test]
128    fn test_add_bytes() {
129        let mut w = new_archive_writer("/tmp/x.zip");
130        w.add_bytes("a.txt", b"hello".to_vec());
131        assert_eq!(w.entry_count(), 1);
132        assert!(w.has_entry("a.txt"));
133    }
134
135    #[test]
136    fn test_add_text() {
137        let mut w = new_archive_writer("/tmp/x.zip");
138        w.add_text("note.txt", "text content");
139        assert!(w.has_entry("note.txt"));
140    }
141
142    #[test]
143    fn test_total_uncompressed_size() {
144        let mut w = new_archive_writer("/tmp/x.zip");
145        w.add_bytes("a", vec![0u8; 100]);
146        w.add_bytes("b", vec![0u8; 200]);
147        assert_eq!(w.total_uncompressed_size(), 300);
148    }
149
150    #[test]
151    fn test_finalize() {
152        let mut w = new_archive_writer("/tmp/x.zip");
153        w.add_bytes("f", vec![0u8; 50]);
154        assert_eq!(w.finalize(), 50);
155    }
156
157    #[test]
158    fn test_set_comment() {
159        let mut w = new_archive_writer("/tmp/x.zip");
160        w.set_comment("archive comment");
161        assert_eq!(w.comment, "archive comment");
162    }
163
164    #[test]
165    fn test_build_archive() {
166        let w = build_archive("/tmp/out.zip", &[("a.txt", b"aa"), ("b.txt", b"bb")]);
167        assert_eq!(w.entry_count(), 2);
168    }
169
170    #[test]
171    fn test_write_entry_compression_level_clamped() {
172        let e = WriteEntry::new("f", vec![]).with_compression(20);
173        assert_eq!(e.compression_level, 9);
174    }
175
176    #[test]
177    fn test_estimate_size() {
178        let mut w = new_archive_writer("/tmp/x.zip");
179        w.add_bytes("x", vec![0u8; 77]);
180        assert_eq!(estimate_size(&w), 77);
181    }
182
183    #[test]
184    fn test_has_entry_false() {
185        let w = new_archive_writer("/tmp/x.zip");
186        assert!(!w.has_entry("nonexistent"));
187    }
188}