Skip to content

Commit 857a58f

Browse files
committed
feat: support use of bundled navigation data
1 parent b963ccb commit 857a58f

File tree

16 files changed

+194
-103
lines changed

16 files changed

+194
-103
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ Here's an overview on the structure of this repository, which is designed to be
3030
```
3131
- Note that if you already have a `VCockpit` with `NO_TEXTURE` you can just add another `htmlgauge` to it, while making sure to increase the index
3232

33+
## Dealing with Bundled Navigation Data
34+
35+
If you bundle outdated navigation data in your aircraft and you want this module to handle updating it for users with subscriptions, place the navigation data into the `NavigationData` directory in `PackageSources`. You can see an example [here](examples/aircraft/PackageSources/NavigationData/)
36+
37+
## Where is the Navigation Data Stored?
38+
39+
The default location for navigation data is `work/NavigationData`. If you have bundled navigation data, its located in the `NavigationData` folder in the root of your project. You can use the `GetActiveDatabasePath` function from the interface to find the most up to date version installed.
40+
3341
## Building the Sample Aircraft
3442

3543
Before building, make sure you have properly created and set an `.env` file in `src/gauge`! An example can be found in the `.env.example` file in that directory. Replace with your credentials

examples/aircraft/PackageDefinitions/navigraph-aircraft-navigation-data-interface-sample.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@
4343
<AssetDir>PackageSources\html_ui\</AssetDir>
4444
<OutputDir>html_ui\</OutputDir>
4545
</AssetGroup>
46+
<AssetGroup Name="NavigationData">
47+
<Type>Copy</Type>
48+
<Flags>
49+
<FSXCompatibility>false</FSXCompatibility>
50+
</Flags>
51+
<AssetDir>PackageSources\NavigationData\</AssetDir>
52+
<OutputDir>NavigationData\</OutputDir>
53+
</AssetGroup>
4654
</AssetGroups>
4755
</AssetPackage>
4856

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

examples/gauge/Components/InterfaceSample.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ export class InterfaceSample extends DisplayComponent<InterfaceSampleProps> {
2222
private readonly dropdownRef = FSComponent.createRef<Dropdown>()
2323
private readonly downloadButtonRef = FSComponent.createRef<HTMLButtonElement>()
2424
private readonly executeButtonRef = FSComponent.createRef<HTMLButtonElement>()
25-
private readonly setActiveButtonRef = FSComponent.createRef<HTMLButtonElement>()
2625
private readonly inputRef = FSComponent.createRef<HTMLInputElement>()
2726

2827
private cancelSource = CancelToken.source()
@@ -69,9 +68,6 @@ export class InterfaceSample extends DisplayComponent<InterfaceSampleProps> {
6968
<div ref={this.downloadButtonRef} class="button">
7069
Download
7170
</div>
72-
<div ref={this.setActiveButtonRef} class="button">
73-
Set as Active
74-
</div>
7571
<input ref={this.inputRef} type="text" id="sql" name="sql" value="ESSA" class="text-field" />
7672
<div ref={this.executeButtonRef} class="button">
7773
Execute SQL
@@ -103,16 +99,6 @@ export class InterfaceSample extends DisplayComponent<InterfaceSampleProps> {
10399
.catch(e => console.error(e))
104100
})
105101

106-
this.setActiveButtonRef.instance.addEventListener("click", () => {
107-
const format = this.dropdownRef.instance.getNavigationDataFormat()
108-
if (!format) return
109-
// This will only work if the database specified is a SQLite database
110-
this.navigationDataInterface
111-
.set_active_database(format)
112-
.then(() => console.info("WASM set active database"))
113-
.catch(err => this.displayError(String(err)))
114-
})
115-
116102
AuthService.user.sub(user => {
117103
if (user) {
118104
this.qrCodeRef.instance.src = ""
@@ -173,7 +159,7 @@ export class InterfaceSample extends DisplayComponent<InterfaceSampleProps> {
173159
const pkg = await packages.getPackage(format)
174160

175161
// Download navigation data to work dir
176-
await this.navigationDataInterface.download_navigation_data(pkg.file.url, pkg.format)
162+
await this.navigationDataInterface.download_navigation_data(pkg.file.url)
177163
this.displayMessage("Navigation data downloaded")
178164
} catch (err) {
179165
if (err instanceof Error) this.displayError(err.message)

src/database/src/database.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,35 +27,49 @@ use crate::{
2727
vhf_navaid::VhfNavaid,
2828
waypoint::Waypoint,
2929
},
30-
sql_structs::{self},
31-
util,
30+
sql_structs, util,
3231
};
3332

3433
pub struct Database {
3534
database: Option<Connection>,
35+
pub path: Option<String>,
3636
}
3737

3838
#[derive(Debug)]
3939
struct NoDatabaseOpen;
4040

4141
impl Display for NoDatabaseOpen {
42-
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "No database open") }
42+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
43+
write!(f, "No database open")
44+
}
4345
}
4446

4547
impl Error for NoDatabaseOpen {}
4648

4749
impl Database {
48-
pub fn new() -> Self { Database { database: None } }
50+
pub fn new() -> Self {
51+
Database {
52+
database: None,
53+
path: None,
54+
}
55+
}
4956

50-
fn get_database(&self) -> Result<&Connection, NoDatabaseOpen> { self.database.as_ref().ok_or(NoDatabaseOpen) }
57+
fn get_database(&self) -> Result<&Connection, NoDatabaseOpen> {
58+
self.database.as_ref().ok_or(NoDatabaseOpen)
59+
}
5160

52-
pub fn set_active_database(&mut self, mut path: String) -> Result<(), Box<dyn Error>> {
53-
// Check if the path is a directory and if it is, search for a sqlite file
54-
let formatted_path = format!("\\work/{}", path);
55-
if util::get_path_type(std::path::Path::new(&formatted_path)) == util::PathType::Directory {
56-
path = util::find_sqlite_file(&formatted_path)?;
61+
pub fn set_active_database(&mut self, path: String) -> Result<(), Box<dyn Error>> {
62+
println!("[NAVIGRAPH] Setting active database to {}", path);
63+
self.close_connection();
64+
if util::is_sqlite_file(&path)? {
65+
self.open_connection(path.clone())?;
5766
}
67+
self.path = Some(path);
5868

69+
Ok(())
70+
}
71+
72+
pub fn open_connection(&mut self, path: String) -> Result<(), Box<dyn Error>> {
5973
// We have to open with flags because the SQLITE_OPEN_CREATE flag with the default open causes the file to
6074
// be overwritten
6175
let flags = OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_URI | OpenFlags::SQLITE_OPEN_NO_MUTEX;
@@ -541,5 +555,7 @@ impl Database {
541555
Ok(data)
542556
}
543557

544-
pub fn close_connection(&mut self) { self.database = None; }
558+
pub fn close_connection(&mut self) {
559+
self.database = None;
560+
}
545561
}

src/database/src/util.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use std::{fs, io::Read, path::Path};
1+
use std::{error::Error, fs, io::Read, path::Path};
2+
3+
// From 1.3.1 of https://www.sqlite.org/fileformat.html
4+
const SQLITE_HEADER: [u8; 16] = [
5+
0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00,
6+
];
27

38
#[derive(PartialEq, Eq)]
49
pub enum PathType {
@@ -31,27 +36,33 @@ pub fn get_path_type(path: &Path) -> PathType {
3136
PathType::DoesNotExist
3237
}
3338

34-
pub fn find_sqlite_file(path: &str) -> Result<String, Box<dyn std::error::Error>> {
35-
// From 1.3.1 of https://www.sqlite.org/fileformat.html
36-
let sqlite_header = [
37-
0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00,
38-
];
39+
pub fn find_sqlite_file(path: &str) -> Result<String, Box<dyn Error>> {
40+
if get_path_type(&Path::new(path)) != PathType::Directory {
41+
return Err("Path is not a directory".into());
42+
}
43+
3944
// We are going to search this directory for a database
4045
for entry in std::fs::read_dir(path)? {
4146
let entry = entry?;
4247
let path = entry.path();
4348
if get_path_type(&path) == PathType::File {
4449
let path = path.to_str().ok_or("Invalid path")?;
45-
// Get first 16 bytes of file
46-
let mut file = std::fs::File::open(path)?;
47-
let mut buf = [0; 16];
48-
file.read_exact(buf.as_mut())?;
49-
// Compare bytes to sqlite header
50-
if buf == sqlite_header {
51-
// We found a database
50+
51+
if is_sqlite_file(path)? {
5252
return Ok(path.to_string());
5353
}
5454
}
5555
}
5656
Err("No SQL database found. Make sure the database specified is a SQL database".into())
5757
}
58+
59+
pub fn is_sqlite_file(path: &str) -> Result<bool, Box<dyn Error>> {
60+
if get_path_type(&Path::new(path)) != PathType::File {
61+
return Ok(false);
62+
}
63+
64+
let mut file = fs::File::open(path)?;
65+
let mut buf = [0; 16];
66+
file.read_exact(&mut buf)?;
67+
Ok(buf == SQLITE_HEADER)
68+
}

src/js/interface/NavigationDataInterfaceTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ export interface DownloadProgressData {
2424

2525
export enum NavigraphFunction {
2626
DownloadNavigationData = "DownloadNavigationData",
27+
GetActiveDatabasePath = "GetActiveDatabasePath",
2728
SetDownloadOptions = "SetDownloadOptions",
28-
SetActiveDatabase = "SetActiveDatabase",
2929
ExecuteSQLQuery = "ExecuteSQLQuery",
3030
GetDatabaseInfo = "GetDatabaseInfo",
3131
GetAirport = "GetAirport",

src/js/interface/NavigraphNavigationDataInterface.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -69,34 +69,30 @@ export class NavigraphNavigationDataInterface {
6969
* Downloads the navigation data from the given URL to the given path
7070
*
7171
* @param url - A valid signed URL to download the navigation data from
72-
* @param path - The path to download the navigation data to
7372
* @returns A promise that resolves when the download is complete
7473
*/
75-
public async download_navigation_data(url: string, path: string): Promise<void> {
76-
return await this.callWasmFunction("DownloadNavigationData", { url, path })
74+
public async download_navigation_data(url: string): Promise<void> {
75+
return await this.callWasmFunction("DownloadNavigationData", { url })
7776
}
7877

7978
/**
80-
* Sets the download options for all future downloads
79+
* Gets the currently active database path
8180
*
82-
* @param batchSize - The number of files to delete or unzip each update (default: 10). This is a performance optimization to avoid blocking the main thread for too long.
83-
* @returns A promise that resolves when the function is complete
81+
* @returns A promise that resolves with the path of the currently active database
8482
*/
85-
public async set_download_options(batch_size: number): Promise<void> {
86-
return await this.callWasmFunction("SetDownloadOptions", batch_size)
83+
84+
public async get_active_database_path(): Promise<string> {
85+
return await this.callWasmFunction("GetActiveDatabasePath", {})
8786
}
8887

8988
/**
90-
* Sets the active DFD database to the one at the given path
91-
*
92-
* @remarks
93-
* The path must be a valid path to a folder that contains a DFD file.
89+
* Sets the download options for all future downloads
9490
*
95-
* @param path - The path to the folder that contains the DFD file
91+
* @param batchSize - The number of files to delete or unzip each update (default: 10). This is a performance optimization to avoid blocking the main thread for too long.
9692
* @returns A promise that resolves when the function is complete
9793
*/
98-
public async set_active_database(path: string): Promise<void> {
99-
return await this.callWasmFunction("SetActiveDatabase", { path })
94+
public async set_download_options(batch_size: number): Promise<void> {
95+
return await this.callWasmFunction("SetDownloadOptions", batch_size)
10096
}
10197

10298
/**

0 commit comments

Comments
 (0)