1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//! Provides extension traits for [`Archive<fs::File>`].
use super::private;
use libpna::Archive;
use std::path::Path;
use std::{fs, io};
/// [`Archive`] filesystem extension trait.
pub trait ArchiveFsExt: private::Sealed {
/// Creates a new archive file at `path` and writes the archive header.
///
/// # Errors
///
/// Returns an error if creating the archive fails.
fn create<P: AsRef<Path>>(path: P) -> io::Result<Self>
where
Self: Sized;
/// Opens an existing archive file at `path` and reads the header.
///
/// # Errors
///
/// Returns an error if opening the archive fails.
fn open<P: AsRef<Path>>(path: P) -> io::Result<Self>
where
Self: Sized;
/// Opens an existing archive for appending entries.
///
/// # Errors
///
/// Returns an error if opening the archive fails.
fn open_for_append<P: AsRef<Path>>(path: P) -> io::Result<Self>
where
Self: Sized;
/// Opens all parts of a split archive, leaving the last part ready for appending entries.
///
/// # Errors
///
/// Returns an error if opening any archive part fails or if reading archive headers fails.
fn open_multipart_for_append<P, F, N>(path: P, next_part_path: F) -> io::Result<Self>
where
Self: Sized,
P: AsRef<Path>,
F: FnMut(&Path, usize) -> N,
N: AsRef<Path>;
}
impl ArchiveFsExt for Archive<fs::File> {
/// Creates a new archive file at `path` and writes the archive header.
///
/// Equivalent to calling [`Archive::write_header`] with a newly
/// created [`fs::File`].
///
/// Returns an `Archive<fs::File>` ready for writing entries.
///
/// # Examples
/// ```no_run
/// # use std::io;
/// use pna::Archive;
/// use pna::prelude::*;
///
/// # fn main() -> io::Result<()> {
/// let mut archive = Archive::create("archive.pna")?;
/// archive.finalize()?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// Returns any error from [`fs::File::create`] or any error from [`Archive::write_header`].
#[inline]
fn create<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let file = fs::File::create(path)?;
Archive::write_header(file)
}
/// Opens an existing archive file at `path` and reads the header.
///
/// Equivalent to calling [`Archive::read_header`] with a file
/// opened via [`fs::File::open`].
///
/// Returns an `Archive<fs::File>` ready for reading entries.
///
/// # Examples
/// ```no_run
/// # use std::io;
/// use pna::Archive;
/// use pna::prelude::*;
///
/// # fn main() -> io::Result<()> {
/// let mut archive = Archive::open("archive.pna")?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// Returns any error from [`fs::File::open`] or any error from [`Archive::read_header`].
#[inline]
fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let file = fs::File::open(path)?;
Archive::read_header(file)
}
/// Opens an existing archive for appending entries.
///
/// This opens the file with read/write permissions, reads the archive
/// header, and seeks to the end-of-archive marker using
/// [`Archive::seek_to_end`], so that new entries can be appended safely.
///
/// # Examples
/// ```no_run
/// # use std::io;
/// use pna::Archive;
/// use pna::prelude::*;
///
/// # fn main() -> io::Result<()> {
/// let mut archive = Archive::open_for_append("archive.pna")?;
/// // archive.add_entry(...)?;
/// archive.finalize()?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// Returns any error from [`fs::OpenOptions::open`], [`Archive::read_header`]
/// or [`Archive::seek_to_end`].
#[inline]
fn open_for_append<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let file = fs::OpenOptions::new().read(true).write(true).open(path)?;
let mut archive = Archive::read_header(file)?;
archive.seek_to_end()?;
Ok(archive)
}
/// Opens all parts of a split archive, leaving the last part ready for appending entries.
///
/// This behaves like [`ArchiveFsExt::open_for_append`] but is aware of multipart archives.
/// The first part is opened with read/write access and rewound to just before the end marker.
/// While an `ANXT` chunk is present, the provided `next_part_path` closure is invoked with the
/// original first-part path and the next one-based part index to resolve the subsequent file
/// to open. Each part is read, validated, and rewound in turn so that the final [`Archive`]
/// returned is positioned to accept new entries safely.
///
/// ```no_run
/// use pna::Archive;
/// use pna::prelude::ArchiveFsExt;
/// use std::io;
///
/// # fn main() -> io::Result<()> {
/// let mut archive = Archive::open_multipart_for_append("example.part1.pna", |base, index| {
/// let mut next = base.to_path_buf();
/// next.set_file_name(format!("example.part{index}.pna"));
/// next
/// })?;
/// // archive.add_entry(...)?;
/// archive.finalize()?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// Returns any error from [`fs::OpenOptions::open`], [`Archive::read_header`],
/// [`Archive::seek_to_end`], or [`Archive::read_next_archive`].
#[inline]
fn open_multipart_for_append<P, F, N>(path: P, mut next_part_path: F) -> io::Result<Self>
where
P: AsRef<Path>,
F: FnMut(&Path, usize) -> N,
N: AsRef<Path>,
{
let base = path.as_ref();
let mut part_index = 1;
let mut archive = Self::open_for_append(base)?;
while archive.has_next_archive() {
part_index += 1;
let next_path = next_part_path(base, part_index);
let file = fs::OpenOptions::new()
.read(true)
.write(true)
.open(next_path.as_ref())?;
archive = archive.read_next_archive(file)?;
archive.seek_to_end()?;
}
Ok(archive)
}
}