Skip to content

Commit b192059

Browse files
committed
fix: use file instead of in-memory buffer for download
1 parent b6cba8f commit b192059

File tree

1 file changed

+49
-21
lines changed

1 file changed

+49
-21
lines changed

src/wasm/src/funcs.rs

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
2-
fs::{self, OpenOptions},
3-
io::{Cursor, Write},
2+
fs::{self, File, OpenOptions},
3+
io::{BufReader, Write},
44
};
55

66
use anyhow::{anyhow, Context, Result};
@@ -24,6 +24,9 @@ use crate::{
2424
/// The URL to get the latest available cycle number
2525
const LATEST_CYCLE_ENDPOINT: &str = "https://navdata.api.navigraph.com/info";
2626

27+
/// The path to the temporary download file
28+
const DOWNLOAD_TEMP_FILE_PATH: &str = "\\work/ng_download.temp";
29+
2730
/// The max size in bytes of each request during the download function (set to 4MB curently)
2831
const DOWNLOAD_CHUNK_SIZE_BYTES: usize = 4 * 1024 * 1024;
2932

@@ -66,6 +69,35 @@ impl Function for DownloadNavigationData {
6669
type ReturnType = ();
6770

6871
async fn run(&mut self) -> Result<Self::ReturnType> {
72+
self.download_to_temp().await?;
73+
74+
// 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)
75+
if Lazy::get(&DATABASE_STATE).is_some() {
76+
// Drop the current database. We don't do this before the download as there is a chance it will fail, and then we end up with no database open.
77+
DATABASE_STATE
78+
.try_lock()
79+
.map_err(|_| anyhow!("can't lock DATABASE_STATE"))?
80+
.close_connection()?;
81+
}
82+
83+
self.extract_navigation_data().await?;
84+
85+
// Open the connection
86+
DATABASE_STATE
87+
.try_lock()
88+
.map_err(|_| anyhow!("can't lock DATABASE_STATE"))?
89+
.open_connection()?;
90+
91+
// Remove the temp file
92+
fs::remove_file(DOWNLOAD_TEMP_FILE_PATH)?;
93+
94+
Ok(())
95+
}
96+
}
97+
98+
impl DownloadNavigationData {
99+
/// Download the navigation data zip file to the temp file location
100+
async fn download_to_temp(&self) -> Result<()> {
69101
// Figure out total size of download (this request is acting like a HEAD since we don't have those in this environment. Nothing actually gets downloaded since we are constraining the range)
70102
let request = NetworkRequestBuilder::new(&self.url)
71103
.context("can't create new NetworkRequestBuilder")?
@@ -86,11 +118,15 @@ impl Function for DownloadNavigationData {
86118
.ok_or(anyhow!("invalid content-range"))?
87119
.parse::<usize>()?;
88120

89-
// Total amount of chunks to download
121+
// Total amount of chunks to download. 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
90122
let total_chunks = total_bytes.div_ceil(DOWNLOAD_CHUNK_SIZE_BYTES);
91123

92-
// 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
93-
let mut bytes = vec![];
124+
// Store the download to a file to avoid holding in-memory
125+
let mut download_file = OpenOptions::new()
126+
.write(true)
127+
.create(true)
128+
.truncate(true)
129+
.open(DOWNLOAD_TEMP_FILE_PATH)?;
94130

95131
for i in 0..total_chunks {
96132
// Calculate the range for the current chunk
@@ -115,20 +151,18 @@ impl Function for DownloadNavigationData {
115151
.wait_for_data()
116152
.await?;
117153

118-
bytes.write_all(&data)?;
154+
// Write and force flush to limit how much data we hold in memory at a time (will be a max of DOWNLOAD_CHUNK_SIZE_BYTES)
155+
download_file.write_all(&data)?;
156+
download_file.flush()?;
119157
}
120158

121-
// 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)
122-
if Lazy::get(&DATABASE_STATE).is_some() {
123-
// Drop the current database. We don't do this before the download as there is a chance it will fail, and then we end up with no database open.
124-
DATABASE_STATE
125-
.try_lock()
126-
.map_err(|_| anyhow!("can't lock DATABASE_STATE"))?
127-
.close_connection()?;
128-
}
159+
Ok(())
160+
}
129161

162+
/// Extract the navigation data files from the zip file located in the temp location
163+
async fn extract_navigation_data(&self) -> Result<()> {
130164
// Load the zip archive
131-
let mut zip = ZipArchive::new(Cursor::new(bytes))?;
165+
let mut zip = ZipArchive::new(BufReader::new(File::open(DOWNLOAD_TEMP_FILE_PATH)?))?;
132166

133167
// Ensure parent folder exists (ignore the result as it will return an error if it already exists)
134168
let _ = fs::create_dir_all(WORK_NAVIGATION_DATA_FOLDER);
@@ -160,12 +194,6 @@ impl Function for DownloadNavigationData {
160194

161195
std::io::copy(&mut zip.by_name(&db_name)?, &mut db_file)?;
162196

163-
// Open the connection
164-
DATABASE_STATE
165-
.try_lock()
166-
.map_err(|_| anyhow!("can't lock DATABASE_STATE"))?
167-
.open_connection()?;
168-
169197
Ok(())
170198
}
171199
}

0 commit comments

Comments
 (0)