use std::path::PathBuf;
pub mod dialogs {
use std::path::PathBuf;
#[derive(Clone, Debug)]
pub enum PickedFile {
Path(PathBuf),
Bytes { name: String, data: Vec<u8> },
}
#[derive(Clone, Debug)]
pub struct SaveOutcome {
pub ok: bool,
pub path: Option<PathBuf>,
}
pub fn init() {
rlobkit_dialogs::init();
}
#[allow(dead_code)] fn open_options(title: &str, extensions: &[&str]) -> rlobkit_dialogs::picker::OpenFileOptions {
let exts: Vec<String> = extensions.iter().map(|s| s.to_string()).collect();
rlobkit_dialogs::picker::OpenFileOptions {
file_type: rlobkit_dialogs::RlobKitType::Custom {
extensions: exts,
mime_types: vec![],
},
mode: rlobkit_dialogs::RlobKitMode::Single,
title: Some(title.to_string()),
initial_directory: None,
}
}
#[allow(dead_code)] fn save_options(
title: &str,
suggested_name: &str,
extensions: &[&str],
) -> rlobkit_dialogs::picker::SaveFileOptions {
let exts: Vec<String> = extensions.iter().map(|s| s.to_string()).collect();
rlobkit_dialogs::picker::SaveFileOptions {
suggested_name: Some(suggested_name.to_string()),
file_type: Some(rlobkit_dialogs::RlobKitType::Custom {
extensions: exts,
mime_types: vec![],
}),
title: Some(title.to_string()),
..Default::default()
}
}
pub fn pick_open_file(
title: &'static str,
extensions: &'static [&'static str],
on_done: Box<dyn FnOnce(Option<PickedFile>) + Send + 'static>,
) {
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
{
std::thread::spawn(move || {
on_done(
rlobkit_dialogs::blocking_open_file(title, extensions).map(PickedFile::Path),
);
});
}
#[cfg(target_os = "android")]
{
std::thread::spawn(move || {
let opts = open_options(title, extensions);
let picked = futures_lite::future::block_on(
rlobkit_dialogs::RlobKit::open_file_picker(opts),
)
.ok()
.flatten()
.and_then(|mut v| v.pop())
.and_then(|f| {
let name = f.name().to_string();
match f.read_bytes() {
Ok(data) => Some(PickedFile::Bytes {
name,
data: data.to_vec(),
}),
Err(e) => {
log::error!("read picker file failed: {e}");
None
}
}
});
on_done(picked);
});
}
#[cfg(target_arch = "wasm32")]
{
let opts = open_options(title, extensions);
wasm_bindgen_futures::spawn_local(async move {
let picked = rlobkit_dialogs::RlobKit::open_file_picker(opts)
.await
.ok()
.flatten()
.and_then(|mut v| v.pop())
.and_then(|f| {
let name = f.name().to_string();
match f
.data()
.map(|b| b.to_vec())
.or_else(|| f.read_bytes().ok().map(|b| b.to_vec()))
{
Some(data) => Some(PickedFile::Bytes { name, data }),
None => None,
}
});
on_done(picked);
});
}
}
pub fn save_bytes(
title: &'static str,
suggested_name: String,
extensions: &'static [&'static str],
data: Vec<u8>,
on_done: Box<dyn FnOnce(SaveOutcome) + Send + 'static>,
) {
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
{
std::thread::spawn(move || {
let outcome = match rlobkit_dialogs::blocking_save_file(
title,
&suggested_name,
&extensions.join(","),
) {
Some(path) => SaveOutcome {
ok: std::fs::write(&path, &data).is_ok(),
path: Some(path),
},
None => SaveOutcome {
ok: false,
path: None,
},
};
on_done(outcome);
});
}
#[cfg(target_os = "android")]
{
std::thread::spawn(move || {
let opts = save_options(title, &suggested_name, extensions);
let ok = futures_lite::future::block_on(rlobkit_dialogs::RlobKit::save_bytes(
opts, &data,
))
.ok()
.flatten()
.is_some();
on_done(SaveOutcome { ok, path: None });
});
}
#[cfg(target_arch = "wasm32")]
{
let opts = save_options(title, &suggested_name, extensions);
wasm_bindgen_futures::spawn_local(async move {
let ok = rlobkit_dialogs::RlobKit::save_bytes(opts, &data)
.await
.ok()
.flatten()
.is_some();
on_done(SaveOutcome { ok, path: None });
});
}
}
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
pub fn export_path(title: &str, suggested_name: &str, extensions: &[&str]) -> Option<PathBuf> {
rlobkit_dialogs::blocking_save_file(title, suggested_name, &extensions.join(","))
}
}
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
pub fn autosave_store() -> DirStore {
let base = std::env::var_os("RENAMITE_DATA_DIR")
.map(PathBuf::from)
.or_else(|| std::env::var_os("XDG_DATA_HOME").map(|p| PathBuf::from(p).join("renamite")))
.unwrap_or_else(|| PathBuf::from("."));
let dir = base.join("autosave");
let _ = std::fs::create_dir_all(&dir);
DirStore { dir }
}
pub trait KvStore: Send + Sync {
fn get(&self, key: &str) -> Option<Vec<u8>>;
fn set(&self, key: &str, value: &[u8]);
}
pub struct DirStore {
pub dir: PathBuf,
}
impl KvStore for DirStore {
fn get(&self, key: &str) -> Option<Vec<u8>> {
let path = self.dir.join(sanitize_key(key));
std::fs::read(&path).ok()
}
fn set(&self, key: &str, value: &[u8]) {
let path = self.dir.join(sanitize_key(key));
let _ = std::fs::write(&path, value);
}
}
fn sanitize_key(key: &str) -> String {
key.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' {
c
} else {
'_'
}
})
.collect()
}
pub fn now_ms() -> f64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs_f64() * 1000.0)
.unwrap_or(0.0)
}