butterfly_dl/lib.rs
1//! # Butterfly-dl Library
2//!
3//! A high-performance, memory-efficient library for downloading OpenStreetMap data files
4//! with intelligent source routing and minimal memory usage.
5//!
6//! ## Features
7//!
8//! - **Smart source routing**: S3 for planet files, HTTP for regional extracts
9//! - **Memory efficient**: <1GB RAM usage regardless of file size
10//! - **Streaming support**: Download directly to any AsyncWrite destination
11//! - **Progress tracking**: Optional progress callbacks for custom UIs
12//! - **Feature flags**: Optional S3 support to minimize dependencies
13//!
14//! ## Basic Usage
15//!
16//! ```rust,no_run
17//! #[tokio::main]
18//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
19//! // Download to file with auto-generated filename
20//! butterfly_dl::get("europe/belgium", None).await?;
21//!
22//! // Download to specific file
23//! butterfly_dl::get("planet", Some("./planet.pbf")).await?;
24//!
25//! // Stream download
26//! let mut stream = butterfly_dl::get_stream("europe/monaco").await?;
27//! // Use stream with any AsyncRead-compatible code
28//!
29//! Ok(())
30//! }
31//! ```
32//!
33//! ## Progress Tracking
34//!
35//! ```rust,no_run
36//! #[tokio::main]
37//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
38//! butterfly_dl::get_with_progress(
39//! "europe/belgium",
40//! Some("belgium.pbf"),
41//! |downloaded, total| {
42//! println!("Progress: {}/{} bytes", downloaded, total);
43//! }
44//! ).await?;
45//!
46//! Ok(())
47//! }
48//! ```
49
50use std::sync::Arc;
51use tokio::io::AsyncRead;
52
53// Re-export core types that users might need
54pub use butterfly_common::{Error, Result};
55pub use crate::core::stream::{DownloadOptions, OverwriteBehavior};
56
57// Internal modules
58mod core;
59
60// C-compatible FFI bindings (optional)
61#[cfg(feature = "c-bindings")]
62pub mod ffi;
63
64/// Download a file to a destination
65///
66/// # Arguments
67/// * `source` - Source identifier (e.g., "planet", "europe", "europe/belgium")
68/// * `dest` - Optional destination file path. If None, auto-generates filename
69///
70/// # Examples
71/// ```rust,no_run
72/// # #[tokio::main]
73/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
74/// // Download with auto-generated filename
75/// butterfly_dl::get("europe/belgium", None).await?;
76///
77/// // Download to specific file
78/// butterfly_dl::get("planet", Some("./my-planet.pbf")).await?;
79/// # Ok(())
80/// # }
81/// ```
82pub async fn get(source: &str, dest: Option<&str>) -> Result<()> {
83 let downloader = core::Downloader::new();
84 let options = DownloadOptions::default();
85
86 let file_path = match dest {
87 Some(path) => path.to_string(),
88 None => core::resolve_output_filename(source),
89 };
90
91 downloader
92 .download_to_file(source, &file_path, &options)
93 .await
94}
95
96/// Download and return a stream
97///
98/// Returns an AsyncRead stream that can be used with any compatible code.
99/// The stream implements AsyncRead + Send + Unpin.
100///
101/// # Arguments
102/// * `source` - Source identifier (e.g., "planet", "europe", "europe/belgium")
103///
104/// # Returns
105/// * `impl AsyncRead + Send + Unpin` - Stream of the downloaded data
106///
107/// # Examples
108/// ```rust,no_run
109/// use tokio::io::AsyncReadExt;
110///
111/// # #[tokio::main]
112/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
113/// let mut stream = butterfly_dl::get_stream("europe/monaco").await?;
114/// let mut buffer = Vec::new();
115/// stream.read_to_end(&mut buffer).await?;
116/// println!("Downloaded {} bytes", buffer.len());
117/// # Ok(())
118/// # }
119/// ```
120pub async fn get_stream(source: &str) -> Result<impl AsyncRead + Send + Unpin> {
121 let downloader = core::Downloader::new();
122 let options = DownloadOptions::default();
123
124 let (stream, _total_size) = downloader.download_stream(source, &options).await?;
125 Ok(stream)
126}
127
128/// Download with progress tracking
129///
130/// Downloads a file with a progress callback that receives (downloaded_bytes, total_bytes).
131///
132/// # Arguments
133/// * `source` - Source identifier (e.g., "planet", "europe", "europe/belgium")
134/// * `dest` - Optional destination file path. If None, auto-generates filename
135/// * `progress` - Callback function that receives (downloaded, total) bytes
136///
137/// # Examples
138/// ```rust,no_run
139/// # #[tokio::main]
140/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
141/// butterfly_dl::get_with_progress(
142/// "europe/belgium",
143/// Some("belgium.pbf"),
144/// |downloaded, total| {
145/// let percent = (downloaded as f64 / total as f64) * 100.0;
146/// println!("Progress: {:.1}%", percent);
147/// }
148/// ).await?;
149/// # Ok(())
150/// # }
151/// ```
152pub async fn get_with_progress<F>(source: &str, dest: Option<&str>, progress: F) -> Result<()>
153where
154 F: Fn(u64, u64) + Send + Sync + 'static,
155{
156 let downloader = core::Downloader::new();
157 let options = DownloadOptions {
158 progress: Some(Arc::new(progress)),
159 ..Default::default()
160 };
161
162 let file_path = match dest {
163 Some(path) => path.to_string(),
164 None => core::resolve_output_filename(source),
165 };
166
167 downloader
168 .download_to_file(source, &file_path, &options)
169 .await
170}
171
172/// Download with custom options
173///
174/// Provides full control over download options including buffer size,
175/// connection limits, and progress tracking.
176///
177/// # Arguments
178/// * `source` - Source identifier
179/// * `dest` - Optional destination file path
180/// * `options` - Download options
181///
182/// # Examples
183/// ```rust,no_run
184/// use butterfly_dl::{DownloadOptions, OverwriteBehavior};
185/// use std::sync::Arc;
186///
187/// # #[tokio::main]
188/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
189/// let options = DownloadOptions {
190/// buffer_size: 128 * 1024, // 128KB buffer
191/// max_connections: 8, // Limit to 8 connections
192/// overwrite: OverwriteBehavior::Force, // Overwrite without prompting
193/// progress: Some(Arc::new(|downloaded, total| {
194/// println!("Downloaded: {} / {}", downloaded, total);
195/// })),
196/// };
197///
198/// butterfly_dl::get_with_options("europe/belgium", None, options).await?;
199/// # Ok(())
200/// # }
201/// ```
202pub async fn get_with_options(
203 source: &str,
204 dest: Option<&str>,
205 options: DownloadOptions,
206) -> Result<()> {
207 let downloader = core::Downloader::new();
208
209 let file_path = match dest {
210 Some(path) => path.to_string(),
211 None => core::resolve_output_filename(source),
212 };
213
214 downloader
215 .download_to_file(source, &file_path, &options)
216 .await
217}
218
219/// Advanced API: Create a downloader with custom configuration
220///
221/// For advanced users who need to customize source URLs, S3 buckets, etc.
222///
223/// # Examples
224/// ```rust,no_run
225/// use butterfly_dl::{Downloader, SourceConfig};
226///
227/// # #[tokio::main]
228/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
229/// let config = SourceConfig {
230/// planet_http_url: "https://my-custom-mirror.org/planet.pbf".to_string(),
231/// ..Default::default()
232/// };
233///
234/// let downloader = Downloader::with_config(config);
235/// // Use downloader methods...
236/// # Ok(())
237/// # }
238/// ```
239pub use core::{Downloader, SourceConfig};
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use tempfile::tempdir;
245
246 #[tokio::test]
247 async fn test_get_with_tempfile() {
248 let dir = tempdir().unwrap();
249 let _file_path = dir.path().join("test.pbf");
250
251 // This would fail in real test without network, but validates the API
252 // get("europe/monaco", Some(file_path.to_str().unwrap())).await.unwrap();
253 }
254
255 #[test]
256 fn test_resolve_output_filename() {
257 assert_eq!(
258 core::resolve_output_filename("planet"),
259 "planet-latest.osm.pbf"
260 );
261 assert_eq!(
262 core::resolve_output_filename("europe"),
263 "europe-latest.osm.pbf"
264 );
265 assert_eq!(
266 core::resolve_output_filename("europe/belgium"),
267 "belgium-latest.osm.pbf"
268 );
269 }
270}