mirror of
https://github.com/lights0123/n-link.git
synced 2024-12-22 18:25:27 +00:00
Port to tauri beta
This commit is contained in:
parent
35cc07f0ed
commit
eb961b8973
|
@ -15,7 +15,7 @@
|
||||||
"feather-icons": "^4.28.0",
|
"feather-icons": "^4.28.0",
|
||||||
"filesize": "^6.1.0",
|
"filesize": "^6.1.0",
|
||||||
"n-link-core": "0.0.0",
|
"n-link-core": "0.0.0",
|
||||||
"tauri": "^0.12.0",
|
"@tauri-apps/api": "^1.0.0-beta.8",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-async-computed": "^3.9.0",
|
"vue-async-computed": "^3.9.0",
|
||||||
"vue-class-component": "^7.2.3",
|
"vue-class-component": "^7.2.3",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"typescript": "~4.0.3",
|
"typescript": "~4.0.3",
|
||||||
"vue-cli-plugin-tailwind": "~1.5.0",
|
"vue-cli-plugin-tailwind": "~1.5.0",
|
||||||
"vue-cli-plugin-tauri": "~0.12.1",
|
"vue-cli-plugin-tauri": "^1.0.0-beta.6",
|
||||||
"vue-template-compiler": "^2.6.11"
|
"vue-template-compiler": "^2.6.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1968
desktop/src-tauri/Cargo.lock
generated
1968
desktop/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -16,8 +16,7 @@ libnspire = "0.2.2"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
rusb = "0.6.4"
|
rusb = "0.6.4"
|
||||||
serde = { version = "1.0", features = [ "derive" ] }
|
serde = { version = "1.0", features = [ "derive" ] }
|
||||||
tauri = { version = "0.11", features = [ "event", "notification", "open" ] }
|
tauri = { version = "1.0.0-beta.8", features = ["dialog-open", "dialog-save", "notification-all", "shell-open"] }
|
||||||
native-dialog = "0.4"
|
|
||||||
clap = "3.0.0-beta.2"
|
clap = "3.0.0-beta.2"
|
||||||
indicatif = "0.15"
|
indicatif = "0.15"
|
||||||
libusb1-sys = { version = "0.4.2", features = [ "vendored" ] }
|
libusb1-sys = { version = "0.4.2", features = [ "vendored" ] }
|
||||||
|
@ -27,8 +26,8 @@ hashbrown = "0.11"
|
||||||
winres = "0.1"
|
winres = "0.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
embedded-server = [ "tauri/embedded-server" ]
|
custom-protocol = [ "tauri/custom-protocol" ]
|
||||||
no-server = [ "tauri/no-server" ]
|
default = [ "custom-protocol" ]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "n-link"
|
name = "n-link"
|
||||||
|
|
|
@ -5,7 +5,7 @@ extern crate winres;
|
||||||
fn main() {
|
fn main() {
|
||||||
if std::path::Path::new("icons/icon.ico").exists() {
|
if std::path::Path::new("icons/icon.ico").exists() {
|
||||||
let mut res = winres::WindowsResource::new();
|
let mut res = winres::WindowsResource::new();
|
||||||
res.set_icon("icons/icon.ico");
|
res.set_icon_with_id("icons/icon.ico", "32512");
|
||||||
res.compile().expect("Unable to find visual studio tools");
|
res.compile().expect("Unable to find visual studio tools");
|
||||||
} else {
|
} else {
|
||||||
panic!("No Icon.ico found. Please add one or check the path");
|
panic!("No Icon.ico found. Please add one or check the path");
|
||||||
|
|
|
@ -46,14 +46,13 @@ fn get_dev() -> Option<libnspire::Handle<rusb::GlobalContext>> {
|
||||||
rusb::devices()
|
rusb::devices()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|dev| {
|
.find(|dev| {
|
||||||
let descriptor = match dev.device_descriptor() {
|
let descriptor = match dev.device_descriptor() {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
};
|
};
|
||||||
descriptor.vendor_id() == VID && matches!(descriptor.product_id(), PID | PID_CX2)
|
descriptor.vendor_id() == VID && matches!(descriptor.product_id(), PID | PID_CX2)
|
||||||
})
|
})
|
||||||
.next()
|
|
||||||
.map(|dev| libnspire::Handle::new(dev.open().unwrap()).unwrap())
|
.map(|dev| libnspire::Handle::new(dev.open().unwrap()).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,16 +4,10 @@ use std::time::Duration;
|
||||||
use libnspire::{PID, PID_CX2, VID};
|
use libnspire::{PID, PID_CX2, VID};
|
||||||
use rusb::GlobalContext;
|
use rusb::GlobalContext;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tauri::{Runtime, Window};
|
||||||
|
|
||||||
use crate::{Device, DeviceState};
|
use crate::{Device, DeviceState, SerializedError};
|
||||||
use tauri::WebviewMut;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Promise {
|
|
||||||
pub callback: String,
|
|
||||||
pub error: String,
|
|
||||||
}
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
|
||||||
|
@ -21,131 +15,6 @@ pub struct DevId {
|
||||||
pub bus_number: u8,
|
pub bus_number: u8,
|
||||||
pub address: u8,
|
pub address: u8,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(tag = "cmd", rename_all = "camelCase")]
|
|
||||||
pub enum Cmd {
|
|
||||||
// your custom commands
|
|
||||||
// multiple arguments are allowed
|
|
||||||
// note that rename_all = "camelCase": you need to use "myCustomCommand" on JS
|
|
||||||
Enumerate {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
OpenDevice {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
#[serde(flatten)]
|
|
||||||
dev: DevId,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
CloseDevice {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
#[serde(flatten)]
|
|
||||||
dev: DevId,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
UpdateDevice {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
#[serde(flatten)]
|
|
||||||
dev: DevId,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
ListDir {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
#[serde(flatten)]
|
|
||||||
dev: DevId,
|
|
||||||
path: String,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
DownloadFile {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
#[serde(flatten)]
|
|
||||||
dev: DevId,
|
|
||||||
path: (String, u64),
|
|
||||||
dest: String,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
UploadFile {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
#[serde(flatten)]
|
|
||||||
dev: DevId,
|
|
||||||
path: String,
|
|
||||||
src: String,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
UploadOs {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
#[serde(flatten)]
|
|
||||||
dev: DevId,
|
|
||||||
src: String,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
DeleteFile {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
#[serde(flatten)]
|
|
||||||
dev: DevId,
|
|
||||||
path: String,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
DeleteDir {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
#[serde(flatten)]
|
|
||||||
dev: DevId,
|
|
||||||
path: String,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
CreateNspireDir {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
#[serde(flatten)]
|
|
||||||
dev: DevId,
|
|
||||||
path: String,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
Move {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
#[serde(flatten)]
|
|
||||||
dev: DevId,
|
|
||||||
src: String,
|
|
||||||
dest: String,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
Copy {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
#[serde(flatten)]
|
|
||||||
dev: DevId,
|
|
||||||
src: String,
|
|
||||||
dest: String,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
SelectFile {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
filter: Vec<String>,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
SelectFiles {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
filter: Vec<String>,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
SelectFolder {
|
|
||||||
#[serde(flatten)]
|
|
||||||
promise: Promise,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_device(dev: Arc<rusb::Device<GlobalContext>>) -> rusb::Result<((u8, u8), Device)> {
|
pub fn add_device(dev: Arc<rusb::Device<GlobalContext>>) -> rusb::Result<((u8, u8), Device)> {
|
||||||
let descriptor = dev.device_descriptor()?;
|
let descriptor = dev.device_descriptor()?;
|
||||||
|
@ -185,7 +54,8 @@ pub fn add_device(dev: Arc<rusb::Device<GlobalContext>>) -> rusb::Result<((u8, u
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enumerate(handle: &mut WebviewMut) -> Result<Vec<AddDevice>, libnspire::Error> {
|
#[tauri::command]
|
||||||
|
pub fn enumerate<R: Runtime>(handle: Window<R>) -> Result<Vec<AddDevice>, SerializedError> {
|
||||||
let devices: Vec<_> = rusb::devices()?.iter().collect();
|
let devices: Vec<_> = rusb::devices()?.iter().collect();
|
||||||
let mut map = crate::DEVICES.write().unwrap();
|
let mut map = crate::DEVICES.write().unwrap();
|
||||||
map
|
map
|
||||||
|
@ -195,13 +65,12 @@ pub fn enumerate(handle: &mut WebviewMut) -> Result<Vec<AddDevice>, libnspire::E
|
||||||
.all(|d| d.bus_number() != k.0 || d.address() != k.1)
|
.all(|d| d.bus_number() != k.0 || d.address() != k.1)
|
||||||
})
|
})
|
||||||
.for_each(|d| {
|
.for_each(|d| {
|
||||||
if let Err(msg) = tauri::event::emit(
|
if let Err(msg) = handle.emit(
|
||||||
handle,
|
|
||||||
"removeDevice",
|
"removeDevice",
|
||||||
Some(DevId {
|
DevId {
|
||||||
bus_number: (d.0).0,
|
bus_number: (d.0).0,
|
||||||
address: (d.0).1,
|
address: (d.0).1,
|
||||||
}),
|
},
|
||||||
) {
|
) {
|
||||||
eprintln!("{}", msg);
|
eprintln!("{}", msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,25 +3,20 @@
|
||||||
windows_subsystem = "windows"
|
windows_subsystem = "windows"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use hashbrown::HashMap;
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use libnspire::dir::EntryType;
|
use hashbrown::HashMap;
|
||||||
use libnspire::{PID_CX2, VID};
|
use libnspire::{PID_CX2, VID};
|
||||||
use native_dialog::Dialog;
|
|
||||||
use rusb::{GlobalContext, Hotplug, UsbContext};
|
use rusb::{GlobalContext, Hotplug, UsbContext};
|
||||||
use tauri::WebviewMut;
|
use serde::Serialize;
|
||||||
|
use tauri::{Runtime, Window};
|
||||||
|
|
||||||
use crate::cmd::{add_device, AddDevice, DevId, FileInfo, ProgressUpdate};
|
use crate::cmd::{add_device, AddDevice, DevId, ProgressUpdate};
|
||||||
use crate::promise::promise_fn;
|
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod promise;
|
|
||||||
|
|
||||||
pub enum DeviceState {
|
pub enum DeviceState {
|
||||||
Open(
|
Open(
|
||||||
|
@ -40,13 +35,13 @@ pub struct Device {
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref DEVICES: RwLock<HashMap<(u8, u8), Device>> = RwLock::new(HashMap::new());
|
static ref DEVICES: RwLock<HashMap<(u8, u8), Device>> = RwLock::new(HashMap::new());
|
||||||
}
|
}
|
||||||
struct DeviceMon {
|
struct DeviceMon<R: Runtime> {
|
||||||
handle: WebviewMut,
|
window: Window<R>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hotplug<GlobalContext> for DeviceMon {
|
impl<R: Runtime> Hotplug<GlobalContext> for DeviceMon<R> {
|
||||||
fn device_arrived(&mut self, device: rusb::Device<GlobalContext>) {
|
fn device_arrived(&mut self, device: rusb::Device<GlobalContext>) {
|
||||||
let mut handle = self.handle.clone();
|
let handle = self.window.clone();
|
||||||
let is_cx_ii = device
|
let is_cx_ii = device
|
||||||
.device_descriptor()
|
.device_descriptor()
|
||||||
.map(|d| d.product_id() == PID_CX2)
|
.map(|d| d.product_id() == PID_CX2)
|
||||||
|
@ -58,10 +53,9 @@ impl Hotplug<GlobalContext> for DeviceMon {
|
||||||
let name = (dev.1).name.clone();
|
let name = (dev.1).name.clone();
|
||||||
let needs_drivers = (dev.1).needs_drivers;
|
let needs_drivers = (dev.1).needs_drivers;
|
||||||
DEVICES.write().unwrap().insert(dev.0, dev.1);
|
DEVICES.write().unwrap().insert(dev.0, dev.1);
|
||||||
if let Err(msg) = tauri::event::emit(
|
if let Err(msg) = handle.emit(
|
||||||
&mut handle,
|
|
||||||
"addDevice",
|
"addDevice",
|
||||||
Some(AddDevice {
|
AddDevice {
|
||||||
dev: DevId {
|
dev: DevId {
|
||||||
bus_number: (dev.0).0,
|
bus_number: (dev.0).0,
|
||||||
address: (dev.0).1,
|
address: (dev.0).1,
|
||||||
|
@ -69,7 +63,7 @@ impl Hotplug<GlobalContext> for DeviceMon {
|
||||||
name,
|
name,
|
||||||
is_cx_ii,
|
is_cx_ii,
|
||||||
needs_drivers,
|
needs_drivers,
|
||||||
}),
|
},
|
||||||
) {
|
) {
|
||||||
eprintln!("{}", msg);
|
eprintln!("{}", msg);
|
||||||
};
|
};
|
||||||
|
@ -93,13 +87,12 @@ impl Hotplug<GlobalContext> for DeviceMon {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.remove_entry(&(device.bus_number(), device.address()))
|
.remove_entry(&(device.bus_number(), device.address()))
|
||||||
{
|
{
|
||||||
if let Err(msg) = tauri::event::emit(
|
if let Err(msg) = self.window.emit(
|
||||||
&mut self.handle,
|
|
||||||
"removeDevice",
|
"removeDevice",
|
||||||
Some(DevId {
|
DevId {
|
||||||
bus_number: dev.0,
|
bus_number: dev.0,
|
||||||
address: dev.1,
|
address: dev.1,
|
||||||
}),
|
},
|
||||||
) {
|
) {
|
||||||
eprintln!("{}", msg);
|
eprintln!("{}", msg);
|
||||||
};
|
};
|
||||||
|
@ -107,42 +100,41 @@ impl Hotplug<GlobalContext> for DeviceMon {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn err_wrap<T>(
|
fn err_wrap<T, R: Runtime>(
|
||||||
res: Result<T, libnspire::Error>,
|
res: Result<T, libnspire::Error>,
|
||||||
dev: DevId,
|
dev: DevId,
|
||||||
handle: &mut WebviewMut,
|
window: &Window<R>,
|
||||||
) -> Result<T, libnspire::Error> {
|
) -> Result<T, libnspire::Error> {
|
||||||
if let Err(libnspire::Error::NoDevice) = res {
|
if let Err(libnspire::Error::NoDevice) = res {
|
||||||
DEVICES
|
DEVICES
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.remove(&(dev.bus_number, dev.address));
|
.remove(&(dev.bus_number, dev.address));
|
||||||
if let Err(msg) = tauri::event::emit(handle, "removeDevice", Some(dev)) {
|
if let Err(msg) = window.emit("removeDevice", dev) {
|
||||||
eprintln!("{}", msg);
|
eprintln!("{}", msg);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn progress_sender<'a>(
|
fn progress_sender<R: Runtime>(
|
||||||
handle: &'a mut WebviewMut,
|
window: &Window<R>,
|
||||||
dev: DevId,
|
dev: DevId,
|
||||||
total: usize,
|
total: usize,
|
||||||
) -> impl FnMut(usize) + 'a {
|
) -> impl FnMut(usize) + '_ {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
move |remaining| {
|
move |remaining| {
|
||||||
if i > 5 {
|
if i > 5 {
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
if i == 0 || remaining == 0 {
|
if i == 0 || remaining == 0 {
|
||||||
if let Err(msg) = tauri::event::emit(
|
if let Err(msg) = window.emit(
|
||||||
handle,
|
|
||||||
"progress",
|
"progress",
|
||||||
Some(ProgressUpdate {
|
ProgressUpdate {
|
||||||
dev,
|
dev,
|
||||||
remaining,
|
remaining,
|
||||||
total,
|
total,
|
||||||
}),
|
},
|
||||||
) {
|
) {
|
||||||
eprintln!("{}", msg);
|
eprintln!("{}", msg);
|
||||||
};
|
};
|
||||||
|
@ -164,321 +156,323 @@ fn get_open_dev(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct SerializedError(String);
|
||||||
|
|
||||||
|
impl<T: std::fmt::Display> From<T> for SerializedError {
|
||||||
|
fn from(f: T) -> Self {
|
||||||
|
SerializedError(f.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod invoked {
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use libnspire::dir::EntryType;
|
||||||
|
use serde::Serialize;
|
||||||
|
use tauri::{Runtime, Window};
|
||||||
|
|
||||||
|
use crate::cmd::{DevId, FileInfo};
|
||||||
|
use crate::{err_wrap, get_open_dev, progress_sender, DeviceState, SerializedError};
|
||||||
|
|
||||||
|
use super::DEVICES;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn open_device(bus_number: u8, address: u8) -> Result<impl Serialize, SerializedError> {
|
||||||
|
let device = if let Some(dev) = DEVICES.read().unwrap().get(&(bus_number, address)) {
|
||||||
|
if !matches!(dev.state, DeviceState::Closed) {
|
||||||
|
return Err("Already open".into());
|
||||||
|
};
|
||||||
|
dev.device.clone()
|
||||||
|
} else {
|
||||||
|
return Err("Failed to find device".into());
|
||||||
|
};
|
||||||
|
let handle = libnspire::Handle::new(device.open()?)?;
|
||||||
|
let info = handle.info()?;
|
||||||
|
{
|
||||||
|
let mut guard = DEVICES.write().unwrap();
|
||||||
|
let device = guard
|
||||||
|
.get_mut(&(bus_number, address))
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Device lost"))?;
|
||||||
|
device.state = DeviceState::Open(Arc::new(Mutex::new(handle)), info.clone());
|
||||||
|
}
|
||||||
|
Ok(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn close_device(bus_number: u8, address: u8) -> Result<impl Serialize, SerializedError> {
|
||||||
|
let mut guard = DEVICES.write().unwrap();
|
||||||
|
let device = guard
|
||||||
|
.get_mut(&(bus_number, address))
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Device lost"))?;
|
||||||
|
device.state = DeviceState::Closed;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn update_device<R: Runtime>(
|
||||||
|
bus_number: u8,
|
||||||
|
address: u8,
|
||||||
|
window: Window<R>,
|
||||||
|
) -> Result<impl Serialize, SerializedError> {
|
||||||
|
let dev = DevId {
|
||||||
|
bus_number,
|
||||||
|
address,
|
||||||
|
};
|
||||||
|
let handle = get_open_dev(&dev)?;
|
||||||
|
let handle = handle.lock().unwrap();
|
||||||
|
let info = err_wrap(handle.info(), dev, &window)?;
|
||||||
|
Ok(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn list_dir<R: Runtime>(
|
||||||
|
bus_number: u8,
|
||||||
|
address: u8,
|
||||||
|
path: String,
|
||||||
|
window: Window<R>,
|
||||||
|
) -> Result<impl Serialize, SerializedError> {
|
||||||
|
let dev = DevId {
|
||||||
|
bus_number,
|
||||||
|
address,
|
||||||
|
};
|
||||||
|
let handle = get_open_dev(&dev)?;
|
||||||
|
let handle = handle.lock().unwrap();
|
||||||
|
let dir = err_wrap(handle.list_dir(&path), dev, &window)?;
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
dir
|
||||||
|
.iter()
|
||||||
|
.map(|file| FileInfo {
|
||||||
|
path: file.name().to_string_lossy().to_string(),
|
||||||
|
is_dir: file.entry_type() == EntryType::Directory,
|
||||||
|
date: file.date(),
|
||||||
|
size: file.size(),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn download_file<R: Runtime>(
|
||||||
|
bus_number: u8,
|
||||||
|
address: u8,
|
||||||
|
path: (String, u64),
|
||||||
|
dest: String,
|
||||||
|
window: Window<R>,
|
||||||
|
) -> Result<impl Serialize, SerializedError> {
|
||||||
|
let dev = DevId {
|
||||||
|
bus_number,
|
||||||
|
address,
|
||||||
|
};
|
||||||
|
let (file, size) = path;
|
||||||
|
let dest = PathBuf::from(dest);
|
||||||
|
let handle = get_open_dev(&dev)?;
|
||||||
|
let handle = handle.lock().unwrap();
|
||||||
|
let mut buf = vec![0; size as usize];
|
||||||
|
err_wrap(
|
||||||
|
handle.read_file(
|
||||||
|
&file,
|
||||||
|
&mut buf,
|
||||||
|
&mut progress_sender(&window, dev, size as usize),
|
||||||
|
),
|
||||||
|
dev,
|
||||||
|
&window,
|
||||||
|
)?;
|
||||||
|
if let Some(name) = file.split('/').last() {
|
||||||
|
File::create(dest.join(name))?.write_all(&buf)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn upload_file<R: Runtime>(
|
||||||
|
bus_number: u8,
|
||||||
|
address: u8,
|
||||||
|
path: String,
|
||||||
|
src: String,
|
||||||
|
window: Window<R>,
|
||||||
|
) -> Result<impl Serialize, SerializedError> {
|
||||||
|
let dev = DevId {
|
||||||
|
bus_number,
|
||||||
|
address,
|
||||||
|
};
|
||||||
|
let file = PathBuf::from(src);
|
||||||
|
let handle = get_open_dev(&dev)?;
|
||||||
|
let handle = handle.lock().unwrap();
|
||||||
|
let mut buf = vec![];
|
||||||
|
File::open(&file)?.read_to_end(&mut buf)?;
|
||||||
|
let name = file
|
||||||
|
.file_name()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Failed to get file name"))?
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
err_wrap(
|
||||||
|
handle.write_file(
|
||||||
|
&format!("{}/{}", path, name),
|
||||||
|
&buf,
|
||||||
|
&mut progress_sender(&window, dev, buf.len()),
|
||||||
|
),
|
||||||
|
dev,
|
||||||
|
&window,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn upload_os<R: Runtime>(
|
||||||
|
bus_number: u8,
|
||||||
|
address: u8,
|
||||||
|
src: String,
|
||||||
|
window: Window<R>,
|
||||||
|
) -> Result<impl Serialize, SerializedError> {
|
||||||
|
let dev = DevId {
|
||||||
|
bus_number,
|
||||||
|
address,
|
||||||
|
};
|
||||||
|
let handle = get_open_dev(&dev)?;
|
||||||
|
let handle = handle.lock().unwrap();
|
||||||
|
let mut buf = vec![];
|
||||||
|
File::open(&src)?.read_to_end(&mut buf)?;
|
||||||
|
err_wrap(
|
||||||
|
handle.send_os(&buf, &mut progress_sender(&window, dev, buf.len())),
|
||||||
|
dev,
|
||||||
|
&window,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn delete_file<R: Runtime>(
|
||||||
|
bus_number: u8,
|
||||||
|
address: u8,
|
||||||
|
path: String,
|
||||||
|
window: Window<R>,
|
||||||
|
) -> Result<impl Serialize, SerializedError> {
|
||||||
|
let dev = DevId {
|
||||||
|
bus_number,
|
||||||
|
address,
|
||||||
|
};
|
||||||
|
let handle = get_open_dev(&dev)?;
|
||||||
|
let handle = handle.lock().unwrap();
|
||||||
|
err_wrap(handle.delete_file(&path), dev, &window)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn delete_dir<R: Runtime>(
|
||||||
|
bus_number: u8,
|
||||||
|
address: u8,
|
||||||
|
path: String,
|
||||||
|
window: Window<R>,
|
||||||
|
) -> Result<impl Serialize, SerializedError> {
|
||||||
|
let dev = DevId {
|
||||||
|
bus_number,
|
||||||
|
address,
|
||||||
|
};
|
||||||
|
let handle = get_open_dev(&dev)?;
|
||||||
|
let handle = handle.lock().unwrap();
|
||||||
|
err_wrap(handle.delete_dir(&path), dev, &window)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn create_nspire_dir<R: Runtime>(
|
||||||
|
bus_number: u8,
|
||||||
|
address: u8,
|
||||||
|
path: String,
|
||||||
|
window: Window<R>,
|
||||||
|
) -> Result<impl Serialize, SerializedError> {
|
||||||
|
let dev = DevId {
|
||||||
|
bus_number,
|
||||||
|
address,
|
||||||
|
};
|
||||||
|
let handle = get_open_dev(&dev)?;
|
||||||
|
let handle = handle.lock().unwrap();
|
||||||
|
err_wrap(handle.create_dir(&path), dev, &window)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn move_file<R: Runtime>(
|
||||||
|
bus_number: u8,
|
||||||
|
address: u8,
|
||||||
|
src: String,
|
||||||
|
dest: String,
|
||||||
|
window: Window<R>,
|
||||||
|
) -> Result<impl Serialize, SerializedError> {
|
||||||
|
let dev = DevId {
|
||||||
|
bus_number,
|
||||||
|
address,
|
||||||
|
};
|
||||||
|
let handle = get_open_dev(&dev)?;
|
||||||
|
let handle = handle.lock().unwrap();
|
||||||
|
err_wrap(handle.move_file(&src, &dest), dev, &window)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn copy<R: Runtime>(
|
||||||
|
bus_number: u8,
|
||||||
|
address: u8,
|
||||||
|
src: String,
|
||||||
|
dest: String,
|
||||||
|
window: Window<R>,
|
||||||
|
) -> Result<impl Serialize, SerializedError> {
|
||||||
|
let dev = DevId {
|
||||||
|
bus_number,
|
||||||
|
address,
|
||||||
|
};
|
||||||
|
let handle = get_open_dev(&dev)?;
|
||||||
|
let handle = handle.lock().unwrap();
|
||||||
|
err_wrap(handle.copy_file(&src, &dest), dev, &window)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if cli::run() {
|
if cli::run() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut has_registered_callback = false;
|
let has_registered_callback = AtomicBool::new(false);
|
||||||
tauri::AppBuilder::new()
|
tauri::Builder::default()
|
||||||
.invoke_handler(move |webview, arg| {
|
.on_page_load(move |window, _p| {
|
||||||
use cmd::Cmd::*;
|
if !has_registered_callback.swap(true, Ordering::SeqCst) {
|
||||||
match serde_json::from_str(arg) {
|
if rusb::has_hotplug() {
|
||||||
Err(e) => Err(e.to_string()),
|
if let Err(msg) = GlobalContext::default().register_callback(
|
||||||
Ok(command) => {
|
Some(VID),
|
||||||
let mut wv_handle = webview.as_mut();
|
None,
|
||||||
match command {
|
None,
|
||||||
Enumerate { promise } => {
|
Box::new(DeviceMon { window }),
|
||||||
if !has_registered_callback {
|
) {
|
||||||
has_registered_callback = true;
|
eprintln!("{}", msg);
|
||||||
if rusb::has_hotplug() {
|
};
|
||||||
if let Err(msg) = GlobalContext::default().register_callback(
|
std::thread::spawn(|| loop {
|
||||||
Some(VID),
|
GlobalContext::default().handle_events(None).unwrap();
|
||||||
None,
|
});
|
||||||
None,
|
} else {
|
||||||
Box::new(DeviceMon {
|
println!("no hotplug");
|
||||||
handle: webview.as_mut(),
|
|
||||||
}),
|
|
||||||
) {
|
|
||||||
eprintln!("{}", msg);
|
|
||||||
};
|
|
||||||
std::thread::spawn(|| loop {
|
|
||||||
GlobalContext::default().handle_events(None).unwrap();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
println!("no hotplug");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || Ok(cmd::enumerate(&mut wv_handle)?),
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
OpenDevice { promise, dev } => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let device = if let Some(dev) =
|
|
||||||
DEVICES.read().unwrap().get(&(dev.bus_number, dev.address))
|
|
||||||
{
|
|
||||||
anyhow::ensure!(matches!(dev.state, DeviceState::Closed), "Already open");
|
|
||||||
dev.device.clone()
|
|
||||||
} else {
|
|
||||||
anyhow::bail!("Failed to find device");
|
|
||||||
};
|
|
||||||
let handle = libnspire::Handle::new(device.open()?)?;
|
|
||||||
let info = handle.info()?;
|
|
||||||
{
|
|
||||||
let mut guard = DEVICES.write().unwrap();
|
|
||||||
let device = guard
|
|
||||||
.get_mut(&(dev.bus_number, dev.address))
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Device lost"))?;
|
|
||||||
device.state = DeviceState::Open(Arc::new(Mutex::new(handle)), info.clone());
|
|
||||||
}
|
|
||||||
Ok(info)
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
CloseDevice { promise, dev } => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
{
|
|
||||||
let mut guard = DEVICES.write().unwrap();
|
|
||||||
let device = guard
|
|
||||||
.get_mut(&(dev.bus_number, dev.address))
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Device lost"))?;
|
|
||||||
device.state = DeviceState::Closed;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
UpdateDevice { promise, dev } => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let handle = get_open_dev(&dev)?;
|
|
||||||
let handle = handle.lock().unwrap();
|
|
||||||
let info = err_wrap(handle.info(), dev, &mut wv_handle)?;
|
|
||||||
Ok(info)
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ListDir { promise, dev, path } => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let handle = get_open_dev(&dev)?;
|
|
||||||
let handle = handle.lock().unwrap();
|
|
||||||
let dir = err_wrap(handle.list_dir(&path), dev, &mut wv_handle)?;
|
|
||||||
|
|
||||||
Ok(
|
|
||||||
dir
|
|
||||||
.iter()
|
|
||||||
.map(|file| FileInfo {
|
|
||||||
path: file.name().to_string_lossy().to_string(),
|
|
||||||
is_dir: file.entry_type() == EntryType::Directory,
|
|
||||||
date: file.date(),
|
|
||||||
size: file.size(),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
DownloadFile {
|
|
||||||
promise,
|
|
||||||
dev,
|
|
||||||
path: (file, size),
|
|
||||||
dest,
|
|
||||||
} => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let dest = PathBuf::from(dest);
|
|
||||||
let handle = get_open_dev(&dev)?;
|
|
||||||
let handle = handle.lock().unwrap();
|
|
||||||
let mut buf = vec![0; size as usize];
|
|
||||||
err_wrap(
|
|
||||||
handle.read_file(
|
|
||||||
&file,
|
|
||||||
&mut buf,
|
|
||||||
&mut progress_sender(&mut wv_handle.clone(), dev, size as usize),
|
|
||||||
),
|
|
||||||
dev,
|
|
||||||
&mut wv_handle,
|
|
||||||
)?;
|
|
||||||
if let Some(name) = file.split('/').last() {
|
|
||||||
File::create(dest.join(name))?.write_all(&buf)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
UploadFile {
|
|
||||||
promise,
|
|
||||||
dev,
|
|
||||||
path,
|
|
||||||
src,
|
|
||||||
} => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let file = PathBuf::from(src);
|
|
||||||
let handle = get_open_dev(&dev)?;
|
|
||||||
let handle = handle.lock().unwrap();
|
|
||||||
let mut buf = vec![];
|
|
||||||
File::open(&file)?.read_to_end(&mut buf)?;
|
|
||||||
let name = file
|
|
||||||
.file_name()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Failed to get file name"))?
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
err_wrap(
|
|
||||||
handle.write_file(
|
|
||||||
&format!("{}/{}", path, name),
|
|
||||||
&buf,
|
|
||||||
&mut progress_sender(&mut wv_handle.clone(), dev, buf.len()),
|
|
||||||
),
|
|
||||||
dev,
|
|
||||||
&mut wv_handle,
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
UploadOs { promise, dev, src } => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let handle = get_open_dev(&dev)?;
|
|
||||||
let handle = handle.lock().unwrap();
|
|
||||||
let mut buf = vec![];
|
|
||||||
File::open(&src)?.read_to_end(&mut buf)?;
|
|
||||||
err_wrap(
|
|
||||||
handle.send_os(
|
|
||||||
&buf,
|
|
||||||
&mut progress_sender(&mut wv_handle.clone(), dev, buf.len()),
|
|
||||||
),
|
|
||||||
dev,
|
|
||||||
&mut wv_handle,
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
DeleteFile { promise, dev, path } => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let handle = get_open_dev(&dev)?;
|
|
||||||
let handle = handle.lock().unwrap();
|
|
||||||
err_wrap(handle.delete_file(&path), dev, &mut wv_handle)?;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
DeleteDir { promise, dev, path } => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let handle = get_open_dev(&dev)?;
|
|
||||||
let handle = handle.lock().unwrap();
|
|
||||||
err_wrap(handle.delete_dir(&path), dev, &mut wv_handle)?;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
CreateNspireDir { promise, dev, path } => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let handle = get_open_dev(&dev)?;
|
|
||||||
let handle = handle.lock().unwrap();
|
|
||||||
err_wrap(handle.create_dir(&path), dev, &mut wv_handle)?;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Move {
|
|
||||||
promise,
|
|
||||||
dev,
|
|
||||||
src,
|
|
||||||
dest,
|
|
||||||
} => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let handle = get_open_dev(&dev)?;
|
|
||||||
let handle = handle.lock().unwrap();
|
|
||||||
err_wrap(handle.move_file(&src, &dest), dev, &mut wv_handle)?;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Copy {
|
|
||||||
promise,
|
|
||||||
dev,
|
|
||||||
src,
|
|
||||||
dest,
|
|
||||||
} => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let handle = get_open_dev(&dev)?;
|
|
||||||
let handle = handle.lock().unwrap();
|
|
||||||
err_wrap(handle.copy_file(&src, &dest), dev, &mut wv_handle)?;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
SelectFile { promise, filter } => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let filter = filter.iter().map(|t| t.as_str()).collect::<Vec<_>>();
|
|
||||||
Ok(
|
|
||||||
(native_dialog::OpenSingleFile {
|
|
||||||
filter: Some(&filter),
|
|
||||||
dir: None,
|
|
||||||
})
|
|
||||||
.show()?,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
SelectFiles { promise, filter } => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || {
|
|
||||||
let filter = filter.iter().map(|t| t.as_str()).collect::<Vec<_>>();
|
|
||||||
Ok(
|
|
||||||
(native_dialog::OpenMultipleFile {
|
|
||||||
filter: Some(&filter),
|
|
||||||
dir: None,
|
|
||||||
})
|
|
||||||
.show()?,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
SelectFolder { promise } => {
|
|
||||||
promise_fn(
|
|
||||||
webview,
|
|
||||||
move || Ok((native_dialog::OpenSingleDir { dir: None }).show()?),
|
|
||||||
promise,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.build()
|
.invoke_handler(tauri::generate_handler![
|
||||||
.run();
|
cmd::enumerate,
|
||||||
|
invoked::open_device,
|
||||||
|
invoked::close_device,
|
||||||
|
invoked::update_device,
|
||||||
|
invoked::list_dir,
|
||||||
|
invoked::download_file,
|
||||||
|
invoked::upload_file,
|
||||||
|
invoked::upload_os,
|
||||||
|
invoked::delete_file,
|
||||||
|
invoked::delete_dir,
|
||||||
|
invoked::create_nspire_dir,
|
||||||
|
invoked::move_file,
|
||||||
|
invoked::copy,
|
||||||
|
])
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
use crate::cmd::Promise;
|
|
||||||
use serde::Serialize;
|
|
||||||
use tauri::api::rpc::{format_callback, format_callback_result};
|
|
||||||
use tauri::Webview;
|
|
||||||
|
|
||||||
pub fn promise_fn<R: Serialize, F: FnOnce() -> tauri::Result<R> + Send + 'static>(
|
|
||||||
webview: &mut Webview<'_>,
|
|
||||||
task: F,
|
|
||||||
Promise {
|
|
||||||
callback: success_callback,
|
|
||||||
error: error_callback,
|
|
||||||
}: Promise,
|
|
||||||
) {
|
|
||||||
let mut webview = webview.as_mut();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
let callback_string = match format_callback_result(
|
|
||||||
task().map_err(|err| err.to_string()),
|
|
||||||
success_callback,
|
|
||||||
error_callback.clone(),
|
|
||||||
) {
|
|
||||||
Ok(callback_string) => callback_string,
|
|
||||||
Err(e) => format_callback(error_callback, e.to_string()),
|
|
||||||
};
|
|
||||||
webview
|
|
||||||
.dispatch(move |webview_ref| webview_ref.eval(callback_string.as_str()))
|
|
||||||
.expect("Failed to dispatch promise callback");
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,9 +1,5 @@
|
||||||
{
|
{
|
||||||
"ctx": {},
|
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"embeddedServer": {
|
|
||||||
"active": true
|
|
||||||
},
|
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": ["deb", "msi", "appimage", "dmg"],
|
"targets": ["deb", "msi", "appimage", "dmg"],
|
||||||
|
@ -21,30 +17,42 @@
|
||||||
"category": "Utility",
|
"category": "Utility",
|
||||||
"shortDescription": "Free, cross-platform, CX-II compatible computer linking program for the TI-Nspire",
|
"shortDescription": "Free, cross-platform, CX-II compatible computer linking program for the TI-Nspire",
|
||||||
"longDescription": "Free, cross-platform, CX-II compatible computer linking program for the TI-Nspire",
|
"longDescription": "Free, cross-platform, CX-II compatible computer linking program for the TI-Nspire",
|
||||||
"osx": {
|
"macOS": {
|
||||||
"frameworks": [],
|
"frameworks": [],
|
||||||
"minimumSystemVersion": "",
|
"minimumSystemVersion": "",
|
||||||
"useBootstrapper": false
|
"useBootstrapper": false,
|
||||||
|
"exceptionDomain": "",
|
||||||
|
"signingIdentity": null,
|
||||||
|
"entitlements": null
|
||||||
},
|
},
|
||||||
"exceptionDomain": ""
|
"windows": {
|
||||||
|
"certificateThumbprint": null,
|
||||||
|
"digestAlgorithm": "sha256",
|
||||||
|
"timestampUrl": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updater": {
|
||||||
|
"active": false
|
||||||
},
|
},
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
"event": true,
|
"shell": {
|
||||||
"notification": true,
|
"open": true
|
||||||
"open": true
|
},
|
||||||
},
|
"dialog": {
|
||||||
"window": {
|
"open": true
|
||||||
"title": "N-Link",
|
}
|
||||||
"width": 800,
|
|
||||||
"height": 600,
|
|
||||||
"resizable": true,
|
|
||||||
"fullscreen": false
|
|
||||||
},
|
},
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "N-Link",
|
||||||
|
"width": 800,
|
||||||
|
"height": 600,
|
||||||
|
"resizable": true,
|
||||||
|
"fullscreen": false
|
||||||
|
}
|
||||||
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
|
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
|
||||||
},
|
|
||||||
"inliner": {
|
|
||||||
"active": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {promisified} from 'tauri/api/tauri';
|
import {invoke} from '@tauri-apps/api/tauri';
|
||||||
import {listen} from 'tauri/api/event';
|
import {listen} from '@tauri-apps/api/event';
|
||||||
|
import {open as openDialog} from '@tauri-apps/api/dialog';
|
||||||
import {Component, Vue} from 'vue-property-decorator';
|
import {Component, Vue} from 'vue-property-decorator';
|
||||||
import type {GenericDevices} from 'n-link-core/components/devices';
|
import type {GenericDevices} from 'n-link-core/components/devices';
|
||||||
|
|
||||||
|
@ -55,47 +56,47 @@ export type Device = { name: string; isCxIi: boolean; needsDrivers: boolean; inf
|
||||||
|
|
||||||
async function downloadFile(dev: DevId | string, path: [string, number], dest: string) {
|
async function downloadFile(dev: DevId | string, path: [string, number], dest: string) {
|
||||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||||
await promisified({...dev, cmd: 'downloadFile', path, dest});
|
await invoke('download_file', {...dev, path, dest});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadFile(dev: DevId | string, path: string, src: string) {
|
async function uploadFile(dev: DevId | string, path: string, src: string) {
|
||||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||||
await promisified({...dev, cmd: 'uploadFile', path, src});
|
await invoke('upload_file', {...dev, path, src});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadOs(dev: DevId | string, src: string) {
|
async function uploadOs(dev: DevId | string, src: string) {
|
||||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||||
await promisified({...dev, cmd: 'uploadOs', src});
|
await invoke('upload_os', {...dev, src});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteFile(dev: DevId | string, path: string) {
|
async function deleteFile(dev: DevId | string, path: string) {
|
||||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||||
await promisified({...dev, cmd: 'deleteFile', path});
|
await invoke('delete_file', {...dev, path});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteDir(dev: DevId | string, path: string) {
|
async function deleteDir(dev: DevId | string, path: string) {
|
||||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||||
await promisified({...dev, cmd: 'deleteDir', path});
|
await invoke('delete_dir', {...dev, path});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createDir(dev: DevId | string, path: string) {
|
async function createDir(dev: DevId | string, path: string) {
|
||||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||||
await promisified({...dev, cmd: 'createNspireDir', path});
|
await invoke('create_nspire_dir', {...dev, path});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function move(dev: DevId | string, src: string, dest: string) {
|
async function move(dev: DevId | string, src: string, dest: string) {
|
||||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||||
await promisified({...dev, cmd: 'move', src, dest});
|
await invoke('move_file', {...dev, src, dest});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copy(dev: DevId | string, src: string, dest: string) {
|
async function copy(dev: DevId | string, src: string, dest: string) {
|
||||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||||
await promisified({...dev, cmd: 'copy', src, dest});
|
await invoke('copy', {...dev, src, dest});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listDir(dev: DevId | string, path: string) {
|
async function listDir(dev: DevId | string, path: string) {
|
||||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||||
return await promisified({...dev, cmd: 'listDir', path}) as FileInfo[];
|
return await invoke('list_dir', {...dev, path}) as FileInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listAll(dev: DevId | string, path: FileInfo): Promise<FileInfo[]> {
|
async function listAll(dev: DevId | string, path: FileInfo): Promise<FileInfo[]> {
|
||||||
|
@ -195,7 +196,7 @@ class Devices extends Vue implements GenericDevices {
|
||||||
async enumerate() {
|
async enumerate() {
|
||||||
this.enumerating = true;
|
this.enumerating = true;
|
||||||
try {
|
try {
|
||||||
for (const dev of await promisified({cmd: 'enumerate'}) as (Device & DevId)[]) {
|
for (const dev of await invoke('enumerate') as (Device & DevId)[]) {
|
||||||
this.$set(this.devices, devToString(dev as DevId), dev);
|
this.$set(this.devices, devToString(dev as DevId), dev);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -205,19 +206,19 @@ class Devices extends Vue implements GenericDevices {
|
||||||
|
|
||||||
async open(dev: DevId | string) {
|
async open(dev: DevId | string) {
|
||||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||||
const info = await promisified({...dev, cmd: 'openDevice'});
|
const info = await invoke('open_device', {...dev});
|
||||||
this.$set(this.devices[devToString(dev)], 'info', info);
|
this.$set(this.devices[devToString(dev)], 'info', info);
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(dev: DevId | string) {
|
async close(dev: DevId | string) {
|
||||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||||
await promisified({...dev, cmd: 'closeDevice'});
|
await invoke('close_device', {...dev});
|
||||||
this.$delete(this.devices[devToString(dev)], 'info');
|
this.$delete(this.devices[devToString(dev)], 'info');
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(dev: DevId | string) {
|
async update(dev: DevId | string) {
|
||||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||||
const info = await promisified({...dev, cmd: 'updateDevice'});
|
const info = await invoke('update_device', {...dev});
|
||||||
this.$set(this.devices[devToString(dev)], 'info', info);
|
this.$set(this.devices[devToString(dev)], 'info', info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +228,7 @@ class Devices extends Vue implements GenericDevices {
|
||||||
|
|
||||||
async promptUploadFiles(dev: DevId | string, path: string) {
|
async promptUploadFiles(dev: DevId | string, path: string) {
|
||||||
if (typeof dev !== 'string') dev = devToString(dev);
|
if (typeof dev !== 'string') dev = devToString(dev);
|
||||||
const files = await promisified({cmd: 'selectFiles', filter: ['tns']}) as string[];
|
const files = await openDialog({filters:[{extensions:['tns'], name:'TNS files'}], multiple: true});
|
||||||
for (const src of files) {
|
for (const src of files) {
|
||||||
this.addToQueue(dev, {action: 'upload', path, src});
|
this.addToQueue(dev, {action: 'upload', path, src});
|
||||||
}
|
}
|
||||||
|
@ -235,14 +236,14 @@ class Devices extends Vue implements GenericDevices {
|
||||||
|
|
||||||
async uploadOs(dev: DevId | string, filter: string) {
|
async uploadOs(dev: DevId | string, filter: string) {
|
||||||
if (typeof dev !== 'string') dev = devToString(dev);
|
if (typeof dev !== 'string') dev = devToString(dev);
|
||||||
const src = await promisified({cmd: 'selectFile', filter: [filter]}) as string | null;
|
const src = await openDialog({filters:[{extensions:[filter], name:'TI Nspire OS upgrade files'}]}) as string | null;
|
||||||
if (!src) return;
|
if (!src) return;
|
||||||
this.addToQueue(dev, {action: 'uploadOs', src});
|
this.addToQueue(dev, {action: 'uploadOs', src});
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadFiles(dev: DevId | string, files: [string, number][]) {
|
async downloadFiles(dev: DevId | string, files: [string, number][]) {
|
||||||
if (typeof dev !== 'string') dev = devToString(dev);
|
if (typeof dev !== 'string') dev = devToString(dev);
|
||||||
const dest = await promisified({cmd: 'selectFolder'}) as string | null;
|
const dest = await openDialog({directory: true}) as string | null;
|
||||||
if (!dest) return;
|
if (!dest) return;
|
||||||
for (const path of files) {
|
for (const path of files) {
|
||||||
this.addToQueue(dev, {action: 'download', path, dest});
|
this.addToQueue(dev, {action: 'download', path, dest});
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Component, Vue, Watch} from 'vue-property-decorator';
|
import {Component, Vue, Watch} from 'vue-property-decorator';
|
||||||
import {open} from 'tauri/api/window';
|
import {open} from '@tauri-apps/api/shell';
|
||||||
import CalcInfo from 'n-link-core/components/CalcInfo.vue';
|
import CalcInfo from 'n-link-core/components/CalcInfo.vue';
|
||||||
import FileBrowser from 'n-link-core/components/FileBrowser.vue';
|
import FileBrowser from 'n-link-core/components/FileBrowser.vue';
|
||||||
import DeviceSelect from "n-link-core/components/DeviceSelect.vue";
|
import DeviceSelect from "n-link-core/components/DeviceSelect.vue";
|
||||||
|
|
Loading…
Reference in a new issue