Skip to content

Commit df7c7e1

Browse files
authored
Merge pull request #20 from Navigraph/chunked-downloads
Download navigation data in chunks
2 parents d3d1a03 + 0f995e4 commit df7c7e1

File tree

5 files changed

+73
-72
lines changed

5 files changed

+73
-72
lines changed

example/gauge/Components/Pages/Auth/Auth.tsx

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { ComponentProps, DisplayComponent, FSComponent, VNode } from "@microsoft/msfs-sdk";
22
import {
3-
DownloadProgressPhase,
43
NavigationDataStatus,
54
NavigraphEventType,
65
NavigraphNavigationDataInterface,
@@ -29,22 +28,9 @@ export class AuthPage extends DisplayComponent<AuthPageProps> {
2928
super(props);
3029

3130
this.props.navigationDataInterface.onEvent(NavigraphEventType.DownloadProgress, data => {
32-
switch (data.phase) {
33-
case DownloadProgressPhase.Downloading:
34-
this.displayMessage("Downloading navigation data...");
35-
break;
36-
case DownloadProgressPhase.Cleaning:
37-
if (!data.deleted) return;
38-
this.displayMessage(`Cleaning destination directory. ${data.deleted} files deleted so far`);
39-
break;
40-
case DownloadProgressPhase.Extracting: {
41-
// Ensure non-null
42-
if (!data.unzipped || !data.total_to_unzip) return;
43-
const percent = Math.round((data.unzipped / data.total_to_unzip) * 100);
44-
this.displayMessage(`Unzipping files... ${percent}% complete`);
45-
break;
46-
}
47-
}
31+
this.displayMessage(
32+
`Downloaded ${data.downloaded_bytes}/${data.total_bytes} bytes (chunk ${data.current_chunk}/${data.total_chunks})`,
33+
);
4834
});
4935
}
5036

src/ts/interface/NavigationDataInterfaceTypes.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,11 @@ export enum NavigraphEventType {
1111
DownloadProgress = "DownloadProgress",
1212
}
1313

14-
export enum DownloadProgressPhase {
15-
Downloading = "Downloading",
16-
Cleaning = "Cleaning",
17-
Extracting = "Extracting",
18-
}
19-
2014
export interface DownloadProgressData {
21-
phase: DownloadProgressPhase;
22-
deleted: number | null;
23-
total_to_unzip: number | null;
24-
unzipped: number | null;
15+
total_bytes: number;
16+
downloaded_bytes: number;
17+
current_chunk: number;
18+
total_chunks: number;
2519
}
2620

2721
export enum NavigraphFunction {

src/wasm/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "msfs-navigation-data-interface"
3-
version = "1.2.0-rc1"
3+
version = "1.2.0-rc2"
44
edition = "2021"
55

66
[lib]

src/wasm/src/funcs.rs

Lines changed: 56 additions & 31 deletions
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};
@@ -18,11 +18,15 @@ use crate::{
1818
WORK_NAVIGATION_DATA_FOLDER,
1919
},
2020
futures::AsyncNetworkRequest,
21-
DownloadProgressEvent, DownloadProgressPhase, InterfaceEvent,
21+
DownloadProgressEvent, 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;
@@ -62,21 +66,57 @@ impl Function for DownloadNavigationData {
6266
type ReturnType = ();
6367

6468
async fn run(&mut self) -> Result<Self::ReturnType> {
65-
// Send an initial progress event TODO: remove these in a breaking version, these are only here for backwards compatibility
66-
InterfaceEvent::send_download_progress_event(DownloadProgressEvent {
67-
phase: DownloadProgressPhase::Downloading,
68-
deleted: None,
69-
total_to_unzip: None,
70-
unzipped: None,
71-
})?;
72-
73-
// Download the data
74-
let data = NetworkRequestBuilder::new(&self.url)
69+
// 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)
70+
let request = NetworkRequestBuilder::new(&self.url)
7571
.context("can't create new NetworkRequestBuilder")?
72+
.with_header(&format!("Range: bytes=0-0"))
73+
.context(".with_header() returned None")?
7674
.get()
77-
.context(".get() returned None")?
78-
.wait_for_data()
79-
.await?;
75+
.context(".get() returned None")?;
76+
77+
request.wait_for_data().await?;
78+
79+
// Try parsing the content-range header
80+
let total_bytes = request
81+
.header_section("content-range")
82+
.context("no content-range header")?
83+
.trim()
84+
.split("/")
85+
.last()
86+
.ok_or(anyhow!("invalid content-range"))?
87+
.parse::<usize>()?;
88+
89+
// Total amount of chunks to download
90+
let total_chunks = total_bytes.div_ceil(DOWNLOAD_CHUNK_SIZE_BYTES);
91+
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![];
94+
95+
for i in 0..total_chunks {
96+
// Calculate the range for the current chunk
97+
let range_start = i * DOWNLOAD_CHUNK_SIZE_BYTES;
98+
let range_end = ((i + 1) * DOWNLOAD_CHUNK_SIZE_BYTES - 1).min(total_bytes - 1);
99+
100+
// Report the current download progress
101+
InterfaceEvent::send_download_progress_event(DownloadProgressEvent {
102+
total_bytes,
103+
downloaded_bytes: range_start,
104+
current_chunk: i,
105+
total_chunks,
106+
})?;
107+
108+
// Dispatch the request
109+
let data = NetworkRequestBuilder::new(&self.url)
110+
.context("can't create new NetworkRequestBuilder")?
111+
.with_header(&format!("Range: bytes={range_start}-{range_end}"))
112+
.context(".with_header() returned None")?
113+
.get()
114+
.context(".get() returned None")?
115+
.wait_for_data()
116+
.await?;
117+
118+
bytes.write_all(&data)?;
119+
}
80120

81121
// 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)
82122
if Lazy::get(&DATABASE_STATE).is_some() {
@@ -87,23 +127,8 @@ impl Function for DownloadNavigationData {
87127
.close_connection()?;
88128
}
89129

90-
// Send the deleting and extraction events
91-
InterfaceEvent::send_download_progress_event(DownloadProgressEvent {
92-
phase: DownloadProgressPhase::Cleaning,
93-
deleted: Some(2),
94-
total_to_unzip: None,
95-
unzipped: None,
96-
})?;
97-
98-
InterfaceEvent::send_download_progress_event(DownloadProgressEvent {
99-
phase: DownloadProgressPhase::Extracting,
100-
deleted: None,
101-
total_to_unzip: Some(2),
102-
unzipped: None,
103-
})?;
104-
105130
// Load the zip archive
106-
let mut zip = ZipArchive::new(Cursor::new(data))?;
131+
let mut zip = ZipArchive::new(Cursor::new(bytes))?;
107132

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

src/wasm/src/lib.rs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,24 @@ mod sentry_gauge;
2121
/// Amount of MS between dispatches of the heartbeat commbus event
2222
const HEARTBEAT_INTERVAL_MS: u128 = 1000;
2323

24-
/// The current phase of downloading
25-
#[derive(Serialize)]
26-
pub enum DownloadProgressPhase {
27-
Downloading,
28-
Cleaning,
29-
Extracting,
30-
}
31-
3224
/// The data associated with the `DownloadProgress` event
3325
#[derive(Serialize)]
3426
pub struct DownloadProgressEvent {
35-
pub phase: DownloadProgressPhase,
36-
pub deleted: Option<usize>,
37-
pub total_to_unzip: Option<usize>,
38-
pub unzipped: Option<usize>,
27+
/// The total amount of bytes to download
28+
pub total_bytes: usize,
29+
/// The amount of bytes downloaded
30+
pub downloaded_bytes: usize,
31+
/// The chunk number (starting at 0) of the current download
32+
pub current_chunk: usize,
33+
/// The total number of chunks needed to download
34+
pub total_chunks: usize,
3935
}
4036

4137
/// The types of events that can be emitted from the interface
4238
#[derive(Serialize)]
4339
enum NavigraphEventType {
4440
Heartbeat,
45-
DownloadProgress, // TODO: remove in a future version. here for backwards compatibility
41+
DownloadProgress,
4642
}
4743

4844
/// The structure of an event message

0 commit comments

Comments
 (0)