Skip to content

Commit 1caafca

Browse files
committed
fix: download in chunks
1 parent f24c499 commit 1caafca

File tree

1 file changed

+54
-9
lines changed

1 file changed

+54
-9
lines changed

src/wasm/src/funcs.rs

+54-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
22
fs::{self, OpenOptions},
3-
io::Cursor,
3+
io::{Cursor, Write},
44
};
55

66
use anyhow::{anyhow, Context, Result};
@@ -21,8 +21,12 @@ use crate::{
2121
DownloadProgressEvent, DownloadProgressPhase, InterfaceEvent,
2222
};
2323

24+
/// The URL to get the latest available cycle number
2425
const LATEST_CYCLE_ENDPOINT: &str = "https://navdata.api.navigraph.com/info";
2526

27+
/// The max size in bytes of each request during the download function (set to 4MB curently)
28+
const DOWNLOAD_CHUNK_SIZE_BYTES: usize = 4 * 1024 * 1024;
29+
2630
/// The trait definition for a function that can be called through the navigation data interface
2731
trait Function: DeserializeOwned {
2832
type ReturnType: Serialize;
@@ -70,13 +74,54 @@ impl Function for DownloadNavigationData {
7074
unzipped: None,
7175
})?;
7276

73-
// Download the data
74-
let data = NetworkRequestBuilder::new(&self.url)
75-
.context("can't create new NetworkRequestBuilder")?
76-
.get()
77-
.context(".get() returned None")?
78-
.wait_for_data()
79-
.await?;
77+
// We need to download the data in chunks of DOWNLOAD_CHUNK_SIZE_BYTES to avoid a timeout, so we need to keep track of a "working" accumulation of all responses
78+
let mut bytes = vec![];
79+
80+
let mut current_byte_index = 0;
81+
loop {
82+
// Dispatch the request
83+
let range_end = current_byte_index + DOWNLOAD_CHUNK_SIZE_BYTES - 1;
84+
let request = NetworkRequestBuilder::new(&self.url)
85+
.context("can't create new NetworkRequestBuilder")?
86+
.with_header(&format!("Range: bytes={current_byte_index}-{range_end}"))
87+
.context(".with_header() returned None")?
88+
.get()
89+
.context(".get() returned None")?;
90+
91+
request.wait_for_data().await?;
92+
93+
// Get the size of actual data. The response will be as long as the requested range is, but content-length contains the amount we actually want to read
94+
let content_length = request
95+
.header_section("content-length")
96+
.context("no content-length header")?
97+
.trim()
98+
.parse::<usize>()?;
99+
100+
// Check if we somehow have no more data (file size would be a perfect multiple of DOWNLOAD_CHUNK_SIZE_BYTES)
101+
if content_length == 0 {
102+
break;
103+
}
104+
105+
let data = request.data().ok_or(anyhow!("no data"))?;
106+
107+
// Make sure we don't panic if server sent less data than claimed (should never happen, but avoid a panic)
108+
if data.len() < content_length {
109+
return Err(anyhow!(
110+
"Received less data ({}) than content-length ({})",
111+
data.len(),
112+
content_length
113+
));
114+
}
115+
116+
bytes.write_all(&data[..content_length])?;
117+
118+
// Check if we have hit the last chunk
119+
if content_length < DOWNLOAD_CHUNK_SIZE_BYTES {
120+
break;
121+
}
122+
123+
current_byte_index += content_length;
124+
}
80125

81126
// Only close connection if DATABASE_STATE has already been initialized - otherwise we end up unnecessarily copying the bundled data and instantly replacing it (due to initialization logic in database state)
82127
if Lazy::get(&DATABASE_STATE).is_some() {
@@ -103,7 +148,7 @@ impl Function for DownloadNavigationData {
103148
})?;
104149

105150
// Load the zip archive
106-
let mut zip = ZipArchive::new(Cursor::new(data))?;
151+
let mut zip = ZipArchive::new(Cursor::new(bytes))?;
107152

108153
// Ensure parent folder exists (ignore the result as it will return an error if it already exists)
109154
let _ = fs::create_dir_all(WORK_NAVIGATION_DATA_FOLDER);

0 commit comments

Comments
 (0)