Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: embed webui assets into the binary #385

Merged
merged 11 commits into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from 8 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
100 changes: 100 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions aw-client-rust/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ mod test {
use aw_client_rust::Event;
use chrono::{DateTime, Duration, Utc};
use serde_json::Map;
use std::path::PathBuf;
use std::sync::Mutex;
use std::thread;
use tokio_test::block_on;
Expand All @@ -38,10 +37,19 @@ mod test {
}

fn setup_testserver() -> rocket::Shutdown {
use aw_server::endpoints::AssetResolver;
use aw_server::endpoints::ServerState;

struct TestAssetResolver;
impl AssetResolver for TestAssetResolver {
fn resolve(&self, _: &str) -> Option<Vec<u8>> {
panic!("Webui cannot be used")
}
}

let state = ServerState {
datastore: Mutex::new(aw_datastore::Datastore::new_in_memory(false)),
asset_path: PathBuf::from("."), // webui won't be used, so it's invalidly set
asset_resolver: Box::new(TestAssetResolver),
device_id: "test_id".to_string(),
};
let mut aw_config = aw_server::config::AWConfig::default();
Expand Down
1 change: 1 addition & 0 deletions aw-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ gethostname = "0.4"
uuid = { version = "1.3", features = ["serde", "v4"] }
clap = { version = "4.1", features = ["derive", "cargo"] }
log-panics = { version = "2", features = ["with-backtrace"]}
rust-embed = { version = "8.0.0" }

aw-datastore = { path = "../aw-datastore" }
aw-models = { path = "../aw-models" }
Expand Down
7 changes: 7 additions & 0 deletions aw-server/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
std::fs::create_dir_all("../aw-webui/dist").unwrap();

Ok(())
}
103 changes: 0 additions & 103 deletions aw-server/src/dirs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::env;
use std::ffi::OsString;
use std::path::PathBuf;

#[cfg(not(target_os = "android"))]
Expand Down Expand Up @@ -93,104 +91,3 @@ fn test_get_dirs() {
db_path(true).unwrap();
db_path(false).unwrap();
}

// The appdirs implementation of site_data_dir is broken on computers which has flatpak installed
// as flatpak adds its ~/.local/share/flatpak/exports/share directory first and then doesn't care
// about the rest of the paths.
// This is a rewrite of site_data_dir which takes the first folder which exists out of all folders
// in XDG_DATA_DIRS
// TODO: Should we talk to upstream about this? This changes the behavior quite a lot so maybe they
// don't want this change?
fn site_data_dir(app: Option<&str>, _: Option<&str>) -> Result<PathBuf, ()> {
// Iterate over all XDG_DATA_DIRS and return first match that exists
let joined = match env::var_os("XDG_DATA_DIRS") {
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
Some(path) => {
if path.is_empty() {
OsString::from("/usr/local/share:/usr/share")
} else {
path
}
}
None => OsString::from("/usr/local/share:/usr/share"),
};

for mut data_dir in env::split_paths(&joined) {
if let Some(app) = app {
data_dir.push(app);
}
if !data_dir.is_dir() {
continue;
}
return Ok(data_dir);
}
// If no dirs exists in XDG_DATA_DIRS, fallback to /usr/local/share
let default = "/usr/local/share";
let mut data_dir = PathBuf::new();
data_dir.push(default);
if let Some(app) = app {
data_dir.push(app);
}

if data_dir.is_dir() {
Ok(data_dir)
} else {
Err(())
}
}

pub fn get_asset_path() -> PathBuf {
use std::env::current_exe;

// Search order for asset path is:
// 1. ./aw-webui/dist
// 2. $current_exe_dir/aw_server_rust/static
// NOTE: Slightly different for .app bundles on macOS
// 3. $XDG_DATA_DIR/aw_server_rust/static
// 4. (fallback) ./aw-webui/dist

// cargo_dev_path
// (for running with cargo run)
let cargo_dev_path = PathBuf::from("./aw-webui/dist/");
if cargo_dev_path.as_path().exists() {
return cargo_dev_path;
}

info!("Cannot find assets {:?}", cargo_dev_path.as_path());

// current_exe_path
// (for self-contained deployed binaries)
if let Ok(mut current_exe_path) = current_exe() {
current_exe_path.pop(); // remove name of executable
current_exe_path.push("./static/");
if current_exe_path.as_path().exists() {
return current_exe_path;
}
}

// For .app bundles on macOS
//
// On macOS, the executable location is ActivityWatch.app/Contents/MacOS/aw-server-rust,
// and the webui location is ActivityWatch.app/Contents/Resources/aw_server_rust/static.
if let Ok(mut current_exe_path) = current_exe() {
current_exe_path.pop(); // remove name of executable
current_exe_path.pop(); // step up into the Contents directory
current_exe_path.push("Resources/aw_server_rust/static/");
if current_exe_path.as_path().exists() {
return current_exe_path;
}
}

// usr_path
// (for linux usr installs)
if let Ok(mut usr_path) = site_data_dir(Some("aw-server"), None) {
usr_path.push("static");
if usr_path.as_path().exists() {
return usr_path;
}
}

warn!("Unable to find an aw-webui asset path which exists, falling back to ./aw-webui/dist");
cargo_dev_path
}
3 changes: 1 addition & 2 deletions aw-server/src/endpoints/hostcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ impl Fairing for HostCheck {

#[cfg(test)]
mod tests {
use std::path::PathBuf;
use std::sync::Mutex;

use rocket::http::{ContentType, Header, Status};
Expand All @@ -126,7 +125,7 @@ mod tests {
fn setup_testserver(address: String) -> Rocket<rocket::Build> {
let state = endpoints::ServerState {
datastore: Mutex::new(aw_datastore::Datastore::new_in_memory(false)),
asset_path: PathBuf::from("aw-webui/dist"),
asset_resolver: endpoints::embed_asset_resolver!("../aw-webui/dist/", None),
device_id: "test_id".to_string(),
};
let mut aw_config = AWConfig::default();
Expand Down
Loading
Loading