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
193
194
195
196
197
198
199
200
201
202
203
//! Streaming ZIP upload and extraction for Amazon S3.
//!
//! `s3-unspool` zips local directories and existing S3 prefixes into local or
//! S3 ZIP files, and unzips local or S3 ZIP files into local directories or S3
//! prefixes.
//!
//! Local directory and S3-prefix zip operations generate the ZIP once. S3 ZIP
//! destinations are streamed with multipart upload, and local ZIP destinations
//! are written through a temporary sibling file before being renamed into place.
//! Empty local directories and zero-byte S3 marker objects are preserved as ZIP
//! directory entries.
//!
//! Extraction compares ZIP entries with destination objects listed by
//! `ListObjectsV2`. Missing objects are uploaded with `If-None-Match: *`, and
//! changed objects are uploaded with `If-Match` against the listed destination
//! ETag so newer destination data is not overwritten accidentally.
//! Conditional write conflicts are recorded and skipped by default; set
//! [`SyncOptions::fail_on_conflict`] or [`LocalZipSyncOptions::fail_on_conflict`]
//! to return an error on the first observed conflict.
//!
//! Conditional overwrites require `s3:GetObject` permission on destination
//! objects as well as `s3:PutObject`. `s3-unspool` does not issue per-file
//! destination `HeadObject` requests or read destination object bodies, but S3
//! authorizes `If-Match` writes against object-read permission.
//!
//! Destination `PutObject` bodies are fed by a source-range scheduler, so a body
//! can pause while waiting for planned ZIP bytes. For high-concurrency extracts,
//! consider relaxing or disabling AWS SDK upload stalled-stream protection on
//! the destination client. Keep download stalled-stream protection enabled for
//! source reads. The repository CLI and Lambda example configure this split.
//! Library users can call [`sync_zip_to_s3_with_clients`] when source reads and
//! destination writes need separate S3 client configuration.
//!
//! [`inspect_s3_zip`] reads source ZIP size and file count without downloading
//! the archive. It is useful before choosing memory-sensitive scheduler options
//! with [`AdaptiveSourceWindow`] or
//! [`SyncOptions::with_source_window_memory_budget_mb`].
//!
//! ZIPs created with [`upload_directory_zip_to_s3`], [`zip_directory_to_file`],
//! [`zip_s3_prefix_to_s3`], or [`zip_s3_prefix_to_file`] include an embedded catalog at
//! [`EMBEDDED_CATALOG_PATH`] by default. The catalog stores each file path and
//! MD5 digest so later extracts can skip unchanged files before decompressing
//! them. Set [`SyncOptions::force_hash_comparison`] when you need to measure
//! or force the fallback extract-and-hash path.
//!
//! Set [`SyncOptions::with_selection`] or the equivalent local unzip option
//! when only a subset of ZIP paths should be restored. Selection patterns use
//! gitignore-style syntax and are applied before source range planning, so
//! ranged `GetObject` requests are planned only for selected entries that still
//! need source bytes. Exclude-only selections restore every non-excluded ZIP
//! path. Selected extracts reject [`SyncOptions::delete_extra_objects`] because
//! unselected destination objects are outside the restore scope.
//!
//! ZIP directory entries round-trip as zero-byte S3 marker objects whose keys
//! end in `/`. Local upload preserves empty directories as ZIP directory
//! entries, and S3-prefix upload preserves zero-byte S3 marker objects as ZIP
//! directory entries while rejecting nonzero trailing-slash S3 objects as
//! ambiguous.
//!
//! # Extract an S3 ZIP to a Destination Prefix
//!
//! ```no_run
//! use aws_config::BehaviorVersion;
//! use aws_sdk_s3::Client;
//! use s3_unspool::{S3Object, S3Prefix, SyncOptions, sync_zip_to_s3};
//!
//! # async fn run() -> s3_unspool::Result<()> {
//! let config = aws_config::load_defaults(BehaviorVersion::latest()).await;
//! let client = Client::new(&config);
//!
//! let extract = SyncOptions::new(
//! S3Object::parse("s3://my-bucket/releases/site.zip")?,
//! S3Prefix::parse("s3://my-bucket/www/")?,
//! )
//! .delete_extra_objects();
//!
//! let report = sync_zip_to_s3(&client, extract).await?;
//! println!("uploaded changed files: {}", report.summary.uploaded_changed);
//! # Ok(())
//! # }
//! ```
//!
//! # Extract Selected Entries from an S3 ZIP
//!
//! ```no_run
//! use aws_config::BehaviorVersion;
//! use aws_sdk_s3::Client;
//! use s3_unspool::{S3Object, S3Prefix, SyncOptions, UnzipSelection, sync_zip_to_s3};
//!
//! # async fn run() -> s3_unspool::Result<()> {
//! let config = aws_config::load_defaults(BehaviorVersion::latest()).await;
//! let client = Client::new(&config);
//!
//! let extract = SyncOptions::new(
//! S3Object::parse("s3://my-bucket/releases/site.zip")?,
//! S3Prefix::parse("s3://my-bucket/www/")?,
//! )
//! .with_selection(
//! UnzipSelection::new()
//! .include("index.md")
//! .include("docs/**/*.md")
//! .exclude("docs/drafts/**"),
//! );
//!
//! let report = sync_zip_to_s3(&client, extract).await?;
//! println!("processed entries: {}", report.summary.zip_files);
//! # Ok(())
//! # }
//! ```
//!
//! # Upload a Directory as a Cataloged ZIP
//!
//! ```no_run
//! use aws_config::BehaviorVersion;
//! use aws_sdk_s3::Client;
//! use s3_unspool::{S3Object, UploadOptions, upload_directory_zip_to_s3};
//!
//! # async fn run() -> s3_unspool::Result<()> {
//! let config = aws_config::load_defaults(BehaviorVersion::latest()).await;
//! let client = Client::new(&config);
//!
//! let upload = UploadOptions::new(
//! "./site",
//! S3Object::parse("s3://my-bucket/releases/site.zip")?,
//! );
//! let report = upload_directory_zip_to_s3(&client, upload).await?;
//! println!("uploaded files: {}", report.files);
//! # Ok(())
//! # }
//! ```
//!
//! # Upload an S3 Prefix as a Cataloged ZIP
//!
//! ```no_run
//! use aws_config::BehaviorVersion;
//! use aws_sdk_s3::Client;
//! use s3_unspool::{S3Object, S3Prefix, S3PrefixUploadOptions, zip_s3_prefix_to_s3};
//!
//! # async fn run() -> s3_unspool::Result<()> {
//! let config = aws_config::load_defaults(BehaviorVersion::latest()).await;
//! let client = Client::new(&config);
//!
//! let upload = S3PrefixUploadOptions::new(
//! S3Prefix::parse("s3://my-bucket/www/")?,
//! S3Object::parse("s3://my-bucket/releases/site.zip")?,
//! );
//! let report = zip_s3_prefix_to_s3(&client, upload).await?;
//! println!("uploaded files: {}", report.files);
//! # Ok(())
//! # }
//! ```
//!
//! # Assumptions
//!
//! The crate assumes destination objects use single-part S3 ETags that match the
//! MD5 digest of the object body. Multipart destination objects and SSE-C
//! destination ETags are intentionally out of scope for comparison.
pub use EMBEDDED_CATALOG_PATH;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;