Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/rustgis-mobile/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ one's.
| File dialogs | Android's picker, through `Services::open_async` and `download` as in the browser. `rfd` has no Android backend, so what still asks it (choosing a folder) is cancelled and says so (`crates/ui-egui/src/filedialog.rs`) |
| HTTPS trust | The bundled Mozilla roots, not the device's certificates; the session says so once (`rustgis_data::http`) |
| Folders | Settings, app data and the cache are in the app's internal storage, set once at start-up (`rustgis_data::dirs::set_root`) |
| GeoLibre tools, GDAL formats, DuckDB | Not available: a phone can't run their programs |
| GeoLibre tools, GDAL formats, DuckDB | Not available: a phone can't run their programs. `rustgis_data::RUNS_PROGRAMS` is false on Android and iOS, so no program is looked for, and each place they would appear says so: a file read through GDAL or a DuckDB database is refused with the reason (convert it on a computer), the SQL pane says why it can't query, and the Geoprocessing pane shows a card in place of the GeoLibre tools. Add Data's Database… stays: its PostGIS, MySQL and SQL Server connections need no program. The app is built without the engine's `geolibre` feature |
| Preferences, the keychain, autosave | Not wired up yet |
| Soft keyboard | It opens for a text field. `NativeActivity`'s is poor; a real app moves to `GameActivity` |

Expand Down
18 changes: 18 additions & 0 deletions crates/data/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,24 @@ pub use value::{Field, FieldType, Value};

use serde::{Deserialize, Serialize};

/// Whether this build runs other programs: GDAL's (for the formats read through it), DuckDB (SQL
/// queries and DuckDB databases) and the `geolibre` runner. Not in the browser, which runs none,
/// nor on a phone (Android, iOS), whose apps can't start them (#1902): there, what would run one
/// says so (see [`no_programs_here`]) instead of asking for an install that can't be made.
pub const RUNS_PROGRAMS: bool = !cfg!(any(target_arch = "wasm32", target_os = "android", target_os = "ios"));

/// What runs RustGIS where [`RUNS_PROGRAMS`] is false, for a message that says a program can't run
/// there: "a phone" or "the browser". Empty where programs run.
pub fn no_programs_here() -> &'static str {
if RUNS_PROGRAMS {
""
} else if cfg!(target_arch = "wasm32") {
"the browser"
} else {
"a phone"
}
}

#[derive(Debug, thiserror::Error)]
pub enum DataError {
#[error("i/o: {0}")]
Expand Down
23 changes: 20 additions & 3 deletions crates/data/src/raster/gdal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,20 @@ pub fn convert_jpeg2000(bytes: Vec<u8>, crs: Option<&str>, max: u64) -> Result<C
}

fn missing() -> DataError {
DataError::Unsupported(
DataError::Unsupported(missing_text(crate::no_programs_here()))
}

/// Why a raster format read through GDAL can't be read: GDAL isn't installed, or (`here` names
/// it) this build runs no programs, where installing it wouldn't help.
fn missing_text(here: &str) -> String {
if here.is_empty() {
"this raster format needs GDAL: install it (gdal_translate and gdalinfo on the PATH, or RUSTGIS_GDAL_BIN naming their folder) and add the file again"
.into(),
)
.into()
} else {
format!(
"this raster format is read through GDAL, a program {here} can't run: convert the file to a GeoTIFF on a computer and add that"
)
}
}

/// Refuses an ER Mapper header that would open more than its own data: any but an `ERStorage` one
Expand Down Expand Up @@ -496,6 +506,13 @@ mod tests {
use super::*;
use std::process::Command;

#[test]
fn where_programs_cant_run_the_message_says_so_instead_of_asking_for_an_install() {
assert!(missing_text("").contains("install it"));
let phone = missing_text("a phone");
assert!(phone.contains("a phone can't run") && !phone.contains("install"), "{phone}");
}

#[test]
fn names() {
assert!(is_gdal_raster_name("/d/N45E006.HGT"));
Expand Down
23 changes: 20 additions & 3 deletions crates/data/src/source/duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,20 @@ fn live_not_here() -> DataError {
}

fn missing() -> DataError {
DataError::Unsupported(
DataError::Unsupported(missing_text(crate::no_programs_here()))
}

/// Why a query or a DuckDB database can't be read: DuckDB isn't installed, or (`here` names it)
/// this build runs no programs, where installing it wouldn't help.
fn missing_text(here: &str) -> String {
if here.is_empty() {
"SQL queries and DuckDB databases need DuckDB: install it (the duckdb program on the PATH, or RUSTGIS_DUCKDB_BIN naming its folder) and try again"
.into(),
)
.into()
} else {
format!(
"SQL queries and DuckDB databases run through DuckDB, a program {here} can't run: open this project on a computer to use them"
)
}
}

/// Where results are kept: the platform's cache folder, or `RUSTGIS_DUCKDB_CACHE`.
Expand Down Expand Up @@ -966,6 +976,13 @@ fn ours(name: &str) -> bool {
mod tests {
use super::*;

#[test]
fn where_programs_cant_run_the_message_says_so_instead_of_asking_for_an_install() {
assert!(missing_text("").contains("install it"));
let phone = missing_text("a phone");
assert!(phone.contains("a phone can't run") && !phone.contains("install"), "{phone}");
}

/// Each kind reads only its own client's variable, and an empty one is none (a fake
/// environment: the process's is never changed).
#[test]
Expand Down
6 changes: 5 additions & 1 deletion crates/data/src/source/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ use crate::Result;
use std::path::{Path, PathBuf};
use std::process::Command;

/// A program: in the folder `env` names, else on the `PATH`.
/// A program: in the folder `env` names, else on the `PATH`. Never one in a build that can't run
/// programs ([`crate::RUNS_PROGRAMS`]): a phone may have a file of that name, but can't start it.
pub(crate) fn program(env: &str, name: &str) -> Option<PathBuf> {
if !crate::RUNS_PROGRAMS {
return None;
}
let exe = if cfg!(windows) { format!("{name}.exe") } else { name.to_string() };
let dirs: Vec<PathBuf> = std::env::var_os(env)
.map(PathBuf::from)
Expand Down
23 changes: 20 additions & 3 deletions crates/data/src/source/gdal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,18 @@ pub fn usable() -> bool {
}

fn missing() -> DataError {
DataError::Unsupported(
DataError::Unsupported(missing_text(crate::no_programs_here()))
Comment thread
giswqs marked this conversation as resolved.
Resolved
}

/// Why a format read through GDAL can't be read: GDAL isn't installed, or (`here` names it) this
/// build runs no programs, where installing it wouldn't help.
fn missing_text(here: &str) -> String {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, grouped with the other quality points: missing_text is copied in source/gdal.rs, raster/gdal.rs and source/duckdb.rs, and each module has a test with the same name (where_programs_cant_run_the_message_says_so_instead_of_asking_for_an_install). The three messages differ only in their install and convert wording. A single helper taking those two strings would remove the copies. The phone branches in chrome.rs (search_platform_disabled), ribbon.rs, sqlpane.rs and the GeoLibre card have no test at all. Passing runs_programs in as a parameter to search_platform_disabled would make its phone branch testable.

Left for the release quality pass.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left for the release quality pass.

if here.is_empty() {
"this format needs GDAL: install it (ogr2ogr and ogrinfo on the PATH, or RUSTGIS_GDAL_BIN naming their folder) and add the file again"
.into(),
)
.into()
} else {
format!("this format is read through GDAL, a program {here} can't run: convert the file to a GeoPackage on a computer and add that")
}
}

/// Where converted layers are kept: the platform's cache folder, or `RUSTGIS_GDAL_CACHE`.
Expand Down Expand Up @@ -606,6 +614,15 @@ mod tests {
use super::*;
use crate::GeometryType;

#[test]
fn where_programs_cant_run_the_message_says_so_instead_of_asking_for_an_install() {
assert!(missing_text("").contains("install it"));
let phone = missing_text("a phone");
assert!(phone.contains("a phone can't run") && !phone.contains("install"), "{phone}");
// Where programs run, the build reports none missing for that reason.
assert_eq!(crate::RUNS_PROGRAMS, crate::no_programs_here().is_empty());
}

#[test]
fn names() {
assert!(
Expand Down
9 changes: 8 additions & 1 deletion crates/engine/src/cmd/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1587,7 +1587,14 @@ fn add_geodatabase(s: &mut Session, p: &Value, path: &str) -> CmdResult {
match gdal.then(|| add_gdal(s, &q, path)) {
Some(Ok(r)) => results.push(r),
Some(Err(g)) => s.notify(format!("{path}: {table} not read: {why}; through GDAL: {g}"), true),
None => s.notify(format!("{path}: {table} not read: {why} (GDAL, when installed, reads it)"), true),
None if rustgis_data::RUNS_PROGRAMS => {
s.notify(format!("{path}: {table} not read: {why} (GDAL, when installed, reads it)"), true)
}
// Installing GDAL is no help where no program runs (a phone, #1902).
None => s.notify(
format!("{path}: {table} not read: {why} (GDAL reads it, a program {} can't run)", rustgis_data::no_programs_here()),
true,
),
}
}
if results.is_empty() {
Expand Down
24 changes: 24 additions & 0 deletions crates/ui-egui/src/panes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3155,12 +3155,36 @@ impl GpForm {
}
}

/// The card that stands for the GeoLibre tools on a phone, which can't run their program.
fn no_geolibre_here(ui: &mut Ui, t: &Tokens) {
ui.add_space(6.0);
egui::Frame::NONE.stroke(Stroke::new(1.0, t.border)).corner_radius(CornerRadius::same(6)).inner_margin(egui::Margin::same(10)).show(
ui,
|ui| {
ui.set_width(ui.available_width());
ui.label(egui::RichText::new(tl!("Whitebox and GeoLibre tools")).font(semibold(13.0)));
ui.label(
egui::RichText::new(tl!(
"Not on a phone: these tools run as a program of their own, which a phone can't run. Use them in RustGIS on a computer."
))
.size(11.5)
.color(t.text_dim),
);
},
);
}

/// Where to get the GeoLibre tools when the runner isn't installed (desktop and CLI builds only).
fn geolibre_card(app: &mut RustGisApp, ui: &mut Ui, t: &Tokens) {
// Read directly, not through Session::run: a command each frame would bump the session's
// revision and make the views redraw. The status itself is cached in the runner module.
let status = rustgis_engine::cmd::gp::geolibre_status();
if status["supported"] != true {
// A phone's build leaves the suite out (its runner is a program, which a phone's apps
// can't start): said where the card would be, so the tools aren't simply missing (#1902).
if !rustgis_engine::data::RUNS_PROGRAMS && !cfg!(target_arch = "wasm32") {
no_geolibre_here(ui, t);
}
return;
}
// An install that just finished: use the runner, and remember it.
Expand Down
4 changes: 3 additions & 1 deletion crates/ui-egui/src/ribbon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,9 @@ fn home_tab(app: &mut RustGisApp, ui: &mut Ui) {
menu_item(ui, app, "", "From URL…", "ui.addUrl", Value::Null);
menu_item(ui, app, "", "From Service…", "ui.addService", Value::Null);
menu_item(ui, app, "", "Zarr…", "ui.dialog", json!({"name": "zarr"}));
// Databases are read through DuckDB, a program the browser can't run.
// Databases are read through DuckDB, a program the browser can't run. A phone keeps
// the row: its PostGIS, MySQL and SQL Server connections need no program, and a
// DuckDB database refused there says why (#1902).
#[cfg(not(target_arch = "wasm32"))]
menu_item(ui, app, "", "Database…", "ui.dialog", json!({"name": "addDatabase"}));
Comment thread
giswqs marked this conversation as resolved.
Resolved
#[cfg(not(target_arch = "wasm32"))]
Expand Down
8 changes: 8 additions & 0 deletions crates/ui-egui/src/sqlpane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,14 @@ fn body(app: &mut RustGisApp, ui: &mut Ui, t: &Tokens) {
);
return;
}
// A phone's apps can't start another program (#1902).
if !rustgis_engine::data::RUNS_PROGRAMS {
ui.label(
RichText::new(tl!("SQL queries run through DuckDB, a program a phone can't run: open this project on a computer to query it."))
.weak(),
);
return;
}
if !app.ui.sql.history_loaded {
app.ui.sql.history_loaded = true;
app.ui.sql.history = app
Expand Down
Loading