diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7f45e44e..ff5d8588 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,7 +2,7 @@ name: 'publish' on: push: - branches: [ "main" ] + branches: "main" jobs: publish-tauri: @@ -14,8 +14,9 @@ jobs: include: - platform: 'macos-latest' # for ARM based macs args: '--target aarch64-apple-darwin' - - platform: 'macos-latest' # for Intel based macs - args: '--target x86_64-apple-darwin' + # Blender no longer supports Intel based macs. May phase out in the future + # - platform: 'macos-latest' # for Intel based macs + # args: '--target x86_64-apple-darwin' - platform: 'ubuntu-22.04' # for linux distro args: '' - platform: 'windows-latest' @@ -34,8 +35,9 @@ jobs: if: matrix.platform == 'ubuntu-22.04' run: | sudo apt-get update - sudo apt-get install -y libewbkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf +# TODO: Find a way to fix SQLX error: "set `DATABASE_URL` to use query macros online, or run `cargo sqlx prepare` to update the query cache" - uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 7cc8b3ab..2c636367 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,8 @@ target/ *ServerSettings.json Cargo.lock *.env + +# schemas always update and appear diff on every git changes +src-tauri/gen/* +blender_rs/examples/assets/*.png +src-tauri/.sqlx/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index e516d16e..dad199d9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,13 +8,9 @@ "name": "dbg Dev client", "type": "lldb", "request": "launch", - "program": "${workspaceRoot}/target/debug/blendfarm", + "program": "${workspaceRoot}/src-tauri/target/debug/blendfarm", "args": [ - // "build", - // "--manifest-path=./src-tauri/Cargo.toml", - // "--no-default-features" - "--client", - "true" + "client" ], "cwd": "${workspaceRoot}", // "preLaunchTask": "ui:dev" diff --git a/.vscode/settings.json b/.vscode/settings.json index 86407a2a..88a2c18b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "rust-analyzer.showUnlinkedFileNotification": false, - "todo-tree.tree.scanMode": "workspace" + "todo-tree.tree.scanMode": "workspace", + "makefile.configureOnOpen": false } \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..dcb20805 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +default: + cd ./src-tauri/ + cargo tauri dev + +build: + cd ./src-tauri/ + cargo tauri build + # maybe a command to bundle a release and upload gpg keys / etc? + +rebuild_database: .sqlx + cd ./src-tauri/ # navigate to Tauri's codebase + cargo sqlx db reset -y # create the database file + cargo sqlx prepare # create cache sql result that satisfy cargo compiler + +test: + cd ./src-tauri/ && cargo test + +clean: + rm -rf ./src-tauri/target ./src-tauri/ \ No newline at end of file diff --git a/README.md b/README.md index efba7321..6e6db8fc 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,13 @@ This project is inspired by the original project - [LogicReinc](https://github.com/LogicReinc/LogicReinc.BlendFarm) -# A Word from Developer: -This is still a experimental program I'm working on. If you find bugs or problem with this tool, please do not heistate to create an issue, I will review them when I get to the next milestone step. +## A Word from Developer: + +This is still a experimental program I'm working on. If you find bugs or problem with this tool, please create an issue and I will review them when I can. Much of the codebase is experimental of what I've learned over my rust journey. + +## TLDR + +Run `make` from makefile directory in terminal - This will install dependencies, compile, build, and run Blendfarm from the releases folder. For more info, please read below. ### Why I created this application: @@ -17,7 +22,7 @@ I humbly present you BlendFarm 2.0, a open-source software completely re-written [libp2p](https://docs.libp2p.io/) - Peer 2 Peer decenteralize network service that enables network discovery service (mDNS), communication (gossipsub), and file share (kad/DHT). -[Blender](https://github.com/tiberiumboy/BlendFarm/tree/main/blender) - Custom library I wrote that acts as a blender CLI wrapper to install, invoke, and launch Blender application. +[Blender](https://github.com/tiberiumboy/BlendFarm/tree/main/blender) - Custom library I authored that acts as a blender CLI wrapper to install, invoke, and launch Blender application. [Blend](https://docs.rs/blend/latest/blend/) - Used to read blender file without blender application to enable extracting information to the user with pre-configured setup (Eevee/Cycle, frame range, Cameras, resolution, last blender version used, etc). @@ -50,24 +55,28 @@ Blender's limitation applies to this project's scope limitation. If a feature is ## Getting Started -There are several ways to start; the first and easiest would be to download the files and simply run the executable, the second way is to download the source code and compile on your computer to run and start. +Download the latest build from the [release](https://github.com/tiberiumboy/BlendFarm/releases) page. Verify the content using checksum. Then unpack, `chmod +x ./blendfarm` for UNIX users, and run `./blendfarm`. This launches BlendFarm's GUI Manager window. Passing the `client` argument will run the program as worker node. This mode consume your computer to install and run blender! There can only be one client instances per machine. -### TLDR: +### To compile -First and foremost - this commands may be subject to change in the future. (Need to find a better way to handle Clap subcommand with tauri's cli plugin - for now, I'm treating it as an argument) +Run `make` from `Blendfarm` directory. Instruction inside [Makefile](./Makefile) guides step by step instructions to navigate, run, and compile. -First - Install tauri-cli as this component is needed to run `cargo tauri` command. Run the following command: -`cargo install tauri-cli --version ^2.0.0-rc --locked` + -*Note- For windows, you must encapsulate the version in double quotes! + +To launch the application in developer mode, navigate to `./src-tauri/` directory and run `cargo tauri dev`. To run Tauri app - run the following command under `/BlendFarm/` directory - `cargo tauri dev` -To run the client app - run the following command under `/BlendFarm/src-tauri/` directory - `cargo run -- client` +To run the client app - run the following command under `/BlendFarm/src-tauri/` directory - `cargo tauri dev -- -- client` ### Network: -Under the hood, this program uses libp2p with [QUIC transport](https://docs.libp2p.io/concepts/transports/quic/). This treat this computer as both a server and a client. Wrapped in a containerized struct, I am using [mdns](https://docs.libp2p.io/concepts/discovery-routing/mdns/) for network discovery service (to find other network farm node on the network so that you don't have to connect manually), [gossipsub]() for private message procedure call ( how node interacts with other nodes), and kad for file transfer protocol (how node distribute blend, image, and blender binary files across the network). With the power of trio combined, it is the perfect solution for making network farm accessible, easy to start up, and robost. Have a read into [libp2p](https://libp2p.io/) if this interest your project needs! +Under the hood, this program uses libp2p with [QUIC transport](https://docs.libp2p.io/concepts/transports/quic/). This treat this computer as both a server and a client. Wrapped in a containerized struct, I am using [mdns](https://docs.libp2p.io/concepts/discovery-routing/mdns/) for network discovery service (to find other network farm node on the network so that you don't have to connect manually), [gossipsub]() for private message procedure call (how computer talks to another computer), and kad for file transfer protocol (how node distribute blend, image, and blender binary files across the network). With the power of trio combined, it is the perfect solution for making network farm accessible, easy to start up, and robost. Have a read into [libp2p](https://libp2p.io/) if this interest your project needs! + +## Developer blogs +I am using Obsidian to keep track of changes and blogs, which helps provide project clarity and goals. Please check out the [obsidian folder](./obsidian/blendfarm/Context.md) for all of the change logs. B; + B-->A; +``` diff --git a/blender/examples/download/README.md b/blender/examples/download/README.md deleted file mode 100644 index 17a557a4..00000000 --- a/blender/examples/download/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Download blender example -This example will download blender with the version passed into arguments and returns the path to blender executables, unpacked. - -## Test it! -To run this example, simply run: -```bash -cargo run --example download - -// For example, if I want to download Blender 4.1.0 -cargo run --example download 4.1.0 -Cache file found! Fetching metadata creation date property! -Blender downloaded at: "/Users/User/Downloads/blender/Blender4.1/blender-4.1.0-macos-arm64/Blender.app/Contents/MacOS/Blender" -``` -The output result will show you where Blender struct is referencing the executable path that is used to pass to argument commands. \ No newline at end of file diff --git a/blender/examples/download/main.rs b/blender/examples/download/main.rs deleted file mode 100644 index bfe24ef9..00000000 --- a/blender/examples/download/main.rs +++ /dev/null @@ -1,17 +0,0 @@ -use ::blender::manager::Manager as BlenderManager; -use semver::Version; - -fn main() { - let args = std::env::args().collect::>(); - let version = match args.get(1) { - Some(v) => Version::parse(v).expect("Invalid version!"), - None => return println!("Please, set a version number. E.g. 4.1.0"), - }; - - let mut manager = BlenderManager::load(); - let blender = manager - .fetch_blender(&version) - .expect("Unable to download Blender!"); - println!("Blender: {:?}", blender); - assert_eq!(&version, blender.get_version()); -} diff --git a/blender/examples/render/main.rs b/blender/examples/render/main.rs deleted file mode 100644 index fe9fe8ce..00000000 --- a/blender/examples/render/main.rs +++ /dev/null @@ -1,96 +0,0 @@ -use blender::blender::Manager; -use blender::models::{args::Args, status::Status}; -use std::ops::Range; -use std::path::PathBuf; -use std::sync::{Arc, RwLock}; - -// This struct will hold information necessary to render what next frame blender requested. -// You can create your own custom class to hold how blender should render per frame. -#[derive(Debug)] -struct Test { - start: i32, - end: i32, -} - -impl Test { - pub fn new(frame_range: Range) -> Self { - Self { - start: frame_range.start, - end: frame_range.end, - } - } - - // denotes the function on how to render the frame - pub fn get_next_frame(&mut self) -> Option { - if self.start <= self.end { - let val = self.start; - self.start = self.start + 1; - return Some(val); - } - None - } -} - -async fn render_with_manager() { - let args = std::env::args().collect::>(); - let blend_path = match args.get(1) { - None => PathBuf::from("./examples/assets/test.blend"), - Some(p) => PathBuf::from(p), - }; - - // Get latest blender installed, or install latest blender from web. - let mut manager = Manager::load(); - let blender = match manager.latest_local_avail() { - Some(blender) => blender, - None => manager - .download_latest_version() - .expect("Should be able to download blender! Are you not connected to the internet?"), - }; - - // Here we ask for the output path, for now we set our path in the same directory as our executable path. - // This information will be display after render has been completed successfully. - // TODO: BUG! This will save to root of C:/ on windows platform! Need to change this to current working dir - let output = PathBuf::from("./examples/assets/"); - - // Create blender argument - let args = Args::new(blend_path, output); - let frames = Test::new(Range { start: 2, end: 10 }); - let frames = Arc::new(RwLock::new(frames)); - - // render the frame. Completed render will return the path of the rendered frame, error indicates failure to render due to blender incompatible hardware settings or configurations. (CPU vs GPU / Metal vs OpenGL) - let listener = blender - .render(args, move || { - let mut frame = frames.write().unwrap(); - frame.get_next_frame() - }) - .await; - - // Handle blender status - while let Ok(status) = listener.recv() { - match status { - Status::Completed { frame, result } => { - println!("[Completed] {frame} {result:?}"); - } - Status::Log { status } => { - println!("[Info] {}", status); - } - Status::Running { status } => { - println!("[Running] {}", status); - } - Status::Error(e) => { - println!("[ERROR] {:?}", e); - } - Status::Exit => { - println!("[Exit]"); - } - _ => { - println!("Unhandled blender status! {:?}", status); - } - } - } -} - -#[tokio::main] -async fn main() { - render_with_manager().await; -} diff --git a/blender/examples/test/main.rs b/blender/examples/test/main.rs deleted file mode 100644 index 62be5d82..00000000 --- a/blender/examples/test/main.rs +++ /dev/null @@ -1,17 +0,0 @@ -use blender::models::home::BlenderHome; - -fn test_download_blender_home_link() { - let home = BlenderHome::new().expect("Unable to get data"); - let newest = home.as_ref().first().unwrap(); - let link = newest.fetch_latest(); - match link { - Ok(link) => { - dbg!(link); - } - Err(e) => println!("Something wrong - {e}"), - } -} - -fn main() { - test_download_blender_home_link(); -} diff --git a/blender/src/blender.rs b/blender/src/blender.rs deleted file mode 100644 index dcef7da2..00000000 --- a/blender/src/blender.rs +++ /dev/null @@ -1,565 +0,0 @@ -/* -Developer blog: - -Currently, there is no error handling situation from blender side of things. If blender crash, we will resume the rest of the code in attempt to parse the data. - This will eventually lead to a program crash because we couldn't parse the information we expect from stdout. - Todo peek into stderr and see if - -- As of Blender 4.2 - they introduced BLENDER_EEVEE_NEXT as a replacement to BLENDER_EEVEE. Will need to make sure I pass in the correct enum for version 4.2 and above. - -- Spoke to Sheepit - another "Intranet" distribution render service (Closed source) - - In order to get Render preview window, there needs to be a GPU context to attach to. Otherwise, we'll have to wait for the render to complete the process before sending the image back to the user. - - They mention to enforce compute methods, do not mix cpu and gpu. (Why?) - -Trial: -- Try docker? -- try loading .dll from blender? See if it's possible? -- Learning Unsafe Rust and using FFI - going to try and find blender's library code that rust can bind to. - - todo: see about cbindgen/cxx? - -Advantage: -- can support M-series ARM processor. -- Original tool Doesn't composite video for you - We can make ffmpeg wrapper? - This will be a feature but not in this level of implementation. -- LogicReinc uses JSON to load batch file - no way to adjust frame after job sent. This version we establish IPC for python to ask next frame. We have better control what to render next. - -Disadvantage: -- Currently rely on python script to do custom render within blender. - No interops/additional cli commands other than interops through bpy (blender python) package - Instead of using JSON to send configuration to python/blender, we're using IPC to control next frame to render. - Currently using Command::Process to invoke commands to blender. Would like to see if there's public API or .dll to interface into. - -Challenges: - Blender support tileX/Y, but gluing the image together is a new challenge - a 64K 24bits image would consume about 3Gb, and size exponentially grow from there. - Have a look into NIP2 to stitch large images together - https://github.com/libvips/nip2 - TODO: Find a way to glue image async by image to image, buffer to buffer, flush out each image before loading new image and hold nothing in memory, store it all on disk instead. - -WARN: - From LogicReinc FAQ's: - Q: Render fails due to Gdip - A: You're running Linux or Mac but did not install libgdiplus and libc6-dev, - install these and you should be good. - - Q:Render fails on Linux - A:You may not have the required blender system dependencies. Easiest way to cover them all is to just run `apt-get install blender` to fetch them all. - (It does not have to be an up2date blender package, its just for dependencies) - -TODO: - Q: My Blendfile requires special addons to be active while rendering, can I add these? - A: Blendfarm has its own versions of Blender in the BlenderData directory, and it runs - these versions always in factory startup, thus without any added addons. This is done - on purpose to make sure the environment is not altered. Most addons don't have to be - active during rendering as they generate geometry etc. If you really need this, make - an issue and I see what I can do. However do realise that this may make the workflow - less smooth. (As you may need to set up these plugins for every Blender version instead - of just letting BlendFarm do all the work. - */ -extern crate xml_rpc; -pub use crate::manager::{Manager, ManagerError}; -pub use crate::models::args::Args; -use crate::models::{ - blender_peek_response::BlenderPeekResponse, blender_render_setting::BlenderRenderSetting, - status::Status, -}; - -use blend::Blend; -#[cfg(test)] -use blend::Instance; -use regex::Regex; -use semver::Version; -use serde::{Deserialize, Serialize}; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use std::process::{Command, Stdio}; -use std::sync::Arc; -use std::{ - fs, - io::{BufRead, BufReader}, - path::{Path, PathBuf}, - sync::mpsc::{self, Receiver}, -}; -use thiserror::Error; -use tokio::spawn; -use xml_rpc::{Fault, Server}; - -// TODO: this is ugly, and I want to get rid of this. How can I improve this? -// Backstory: Win and linux can be invoked via their direct app link. However, MacOS .app is just a bundle, which contains the executable inside. -// To run process::Command, I must properly reference the executable path inside the blender.app on MacOS, using the hardcoded path below. -const MACOS_PATH: &str = "Contents/MacOS/Blender"; - -pub type Frame = i32; - -#[derive(Debug, Error)] -pub enum BlenderError { - #[error("Unable to call blender!")] - ExecutableInvalid, - #[error("Path to executable not found! {0}")] - ExecutableNotFound(PathBuf), - #[error("Invalid file path! {0}")] - InvalidFile(String), - #[error("Unable to render! Error: {0}")] - RenderError(String), - #[error("Unable to launch blender! Received Python errors: {0}")] - PythonError(String), -} - -/// Blender structure to hold path to executable and version of blender installed. -/// Pretend this is the wrapper to interface with the actual blender program. -#[derive(Debug, Clone, Serialize, Deserialize, Eq)] -pub struct Blender { - /// Path to blender executable on the system. - executable: PathBuf, - /// Version of blender installed on the system. - version: Version, -} - -impl PartialEq for Blender { - fn eq(&self, other: &Self) -> bool { - self.version.eq(&other.version) - } -} - -impl PartialOrd for Blender { - fn ge(&self, other: &Self) -> bool { - self.version.ge(&other.version) - } - - fn partial_cmp(&self, other: &Self) -> Option { - self.version.partial_cmp(&other.version) - } -} - -impl Ord for Blender { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.version.cmp(&other.version) - } -} - -impl Blender { - /* Private method impl */ - - /// Create a new blender struct with provided path and version. This does not checked and enforced! - /// - /// # Examples - /// ``` - /// use blender::Blender; - /// let blender = Blender::new(PathBuf::from("path/to/blender"), Version::new(4,1,0)); - /// ``` - fn new(executable: PathBuf, version: Version) -> Self { - Self { - executable, - version, - } - } - - /// This function will invoke the -v command ot retrieve blender version information. - /// - /// # Errors - /// * InvalidData - executable path do not exist or is invalid. Please verify that the path provided exist and not compressed. - /// This error also serves where the executable is unable to provide the blender version. - // TODO: Find a better way to fetch version from stdout (Research for best practice to parse data from stdout) - fn check_version(executable_path: impl AsRef) -> Result { - if let Ok(output) = Command::new(executable_path.as_ref()).arg("-v").output() { - // wonder if there's a better way to test this? - let regex = - Regex::new(r"(Blender (?[0-9]).(?[0-9]).(?[0-9]))").unwrap(); - - let stdout = String::from_utf8(output.stdout).unwrap(); - return match regex.captures(&stdout) { - Some(info) => Ok(Version::new( - info["major"].parse().unwrap(), - info["minor"].parse().unwrap(), - info["patch"].parse().unwrap(), - )), - None => Err(BlenderError::ExecutableInvalid), - }; - } - Err(BlenderError::ExecutableInvalid) - } - - /// Fetch the configuration path for blender. This is used to store temporary files and configuration files for blender. - pub fn get_config_path() -> PathBuf { - dirs::config_dir().unwrap().join("BlendFarm") - } - - /// Return the executable path to blender (Entry point for CLI) - pub fn get_executable(&self) -> &Path { - &self.executable - } - - /// Return validated Blender Version - pub fn get_version(&self) -> &Version { - &self.version - } - - /// Create a new blender struct from executable path. This function will fetch the version of blender by invoking -v command. - /// Otherwise, if Blender is not install, or a version is not found, an error will be thrown - /// - /// # Error - /// - /// * InvalidData - executable path do not exist, or is invalid. Please verify that the executable path is correct and leads to the actual executable. - /// * - /// # Examples - /// - /// ``` - /// use blender::Blender; - /// let blender = Blender::from_executable(Pathbuf::from("path/to/blender")).unwrap(); - /// ``` - pub fn from_executable(executable: impl AsRef) -> Result { - // check and verify that the executable exist. - // first line for validating blender executable. - let path = executable.as_ref(); - - // macOS is special. To invoke the blender application, I need to navigate inside Blender.app, which is an app bundle that contains stuff to run blender. - // Command::Process needs to access the content inside app bundle to perform the operation correctly. - // To do this - I need to append additional path args to correctly invoke the right application for this to work. - // TODO: Verify this works for Linux/window OS? - let path = if std::env::consts::OS == "macos" && !&path.ends_with(MACOS_PATH) { - &path.join(MACOS_PATH) - } else { - path - }; - - // this should be clear and explicit that I must have a valid path? How can I do this? - // does it need a wrapper? - if !path.exists() { - return Err(BlenderError::ExecutableNotFound(path.to_path_buf())); - } - - // Obtain the version by invoking version command to blender directly. - // This validate two things, - // 1: Blender's internal version is reliable - // 2: Executable is functional and operational - // Otherwise, return an error that we were unable to verify this custom blender integrity. - let version = Self::check_version(path)?; - Ok(Self::new(path.to_path_buf(), version)) - } - - // this is used to read and see blend file friendly view mode - #[cfg(test)] - #[allow(dead_code)] - fn explore_value<'a>(obj: &Instance<'a>) { - for i in &obj.fields { - match i.1.is_primitive { - true => { - match i.1.info { - blend::parsers::field::FieldInfo::Value => { - match i.1.type_name.as_str() { - "int" => { - println!("{}: {} = {} ", i.0, i.1.type_name, &obj.get_i32(i.0)); - } - "short" => { - println!("{}: {} = {} ", i.0, i.1.type_name, &obj.get_u16(i.0)); - } - "char" => { - println!( - "{}: {} = {} ", - i.0, - i.1.type_name, - &obj.get_string(i.0) - ); - } - "float" => { - println!("{}: {} = {}", i.0, i.1.type_name, &obj.get_f32(i.0)); - } - "uint64_t" => { - println!("{}: {} = {}", i.0, i.1.type_name, &obj.get_u64(i.0)); - } - _ => println!("Unhandle value for {} | {}", i.1.type_name, i.0), - }; - } - blend::parsers::field::FieldInfo::ValueArray { .. } => { - match i.1.type_name.as_str() { - "char" => { - println!("{}: String = {}", i.0, &obj.get_string(i.0)); - } - "float" => { - println!("{}: vec = {:?}", i.0, &obj.get_f32_vec(i.0)); - } - _ => { - println!("Unhandle Value Array for {} | {}", i.1.type_name, i.0) - } - } - } - // blend::parsers::field::FieldInfo::PointerArray { .. } => todo!(), - // blend::parsers::field::FieldInfo::Pointer { indirection_count } => todo!(), - // blend::parsers::field::FieldInfo::FnPointer => todo!(), - _ => { - println!("Unhandle: {} | {} ", i.0, i.1.type_name) - } - } - } - false => { - println!("{}: TYPE = {}", i.0, i.1.type_name); - } - } - } - } - - /// Peek is a function design to read and fetch information about the blender file. - pub async fn peek(blend_file: &PathBuf) -> Result { - let blend = Blend::from_path(&blend_file) - .map_err(|_| BlenderError::InvalidFile("Received BlenderParseError".to_owned()))?; - - // blender version are display as three digits number, e.g. 404 is major: 4, minor: 4. - // treat this as a u16 major = u16 / 100, minor = u16 % 100; - let value: u64 = std::str::from_utf8(&blend.blend.header.version) - .expect("Fail to parse version into utf8") - .parse() - .expect("Fail to parse string to value"); - let major = value / 100; - let minor = value % 100; - - // using scope to drop manager usage. - let blend_version = { - let manager = Manager::load(); - - // Get the latest patch from blender home - match manager - .home - .as_ref() - .iter() - .find(|v| v.major.eq(&major) && v.minor.eq(&minor)) - { - // TODO: Find a better way to handle this without using unwrap - Some(v) => v.fetch_latest().unwrap().as_ref().clone(), - // potentially could be a problem, if there's no internet connection, then we can't rely on zero patch? - // For now this will do. - None => Version::new(major.into(), minor.into(), 0), - } - }; - - let mut scenes: Vec = Vec::new(); - let mut cameras: Vec = Vec::new(); - - let mut frame_start: i32 = 0; - let mut frame_end: i32 = 0; - let mut render_width: i32 = 0; - let mut render_height: i32 = 0; - let mut fps: u16 = 0; - let mut samples: i32 = 0; - let mut output: PathBuf = PathBuf::new(); - let mut engine = String::from(""); - - // this denotes how many scene objects there are. - for obj in blend.instances_with_code(*b"SC") { - let scene = obj.get("id").get_string("name").replace("SC", ""); // not the correct name usage? - // get render data - let render = &obj.get("r"); - - engine = render.get_string("engine"); // will show BLENDER_EEVEE_NEXT properly - samples = obj.get("eevee").get_i32("taa_render_samples"); - - // Issue, Cannot find cycles info! Blender show that it should be here under SCscene, just like eevee, but I'm looking it over and over and it's not there? Where is cycle? - // Use this for development only! - // Self::explore_value(&obj.get("eevee")); - - render_width = render.get_i32("xsch"); - render_height = render.get_i32("ysch"); - frame_start = render.get_i32("sfra"); - frame_end = render.get_i32("efra"); - fps = render.get_u16("frs_sec"); - output = render - .get_string("pic") - .parse::() - .map_err(|e| BlenderError::PythonError(e.to_string()))?; - - scenes.push(scene); - } - - // interesting - I'm picking up the wrong camera here? - for obj in blend.instances_with_code(*b"CA") { - let camera = obj.get("id").get_string("name").replace("CA", ""); - cameras.push(camera); - } - - let selected_camera = cameras.get(0).unwrap_or(&"".to_owned()).to_owned(); - let selected_scene = scenes.get(0).unwrap_or(&"".to_owned()).to_owned(); - - // parse i32 into u16 - let result = BlenderPeekResponse { - last_version: blend_version, - render_width, - render_height, - frame_start, - frame_end, - fps, - samples, - cameras, - selected_camera, - scenes, - selected_scene, - engine, - output, - }; - - Ok(result) - } - - /// Render one frame - can we make the assumption that ProjectFile may have configuration predefined Or is that just a system global setting to apply on? - /// # Examples - /// ``` - /// use blender::Blender; - /// use blender::args::Args; - /// let blender = Blender::from_executable("path/to/blender").unwrap(); - /// let args = Args::new(PathBuf::from("path/to/project.blend"), PathBuf::from("path/to/output.png")); - /// let final_output = blender.render(&args).unwrap(); - /// ``` - // so instead of just returning the string of render result or blender error, we'll simply use the single producer to produce result from this class. - pub async fn render(&self, args: Args, get_next_frame: F) -> Receiver - where - F: Fn() -> Option + Send + Sync + 'static, - { - let (rx, tx) = mpsc::channel::(); - let (signal, listener) = mpsc::channel::(); - let executable = self.executable.clone(); - - let blend_info = Self::peek(&args.file) - .await - .expect("Fail to parse blend file!"); // TODO: Need to clean this error up a bit. - - // this is the only place used for BlenderRenderSetting... thoughts? - let settings = BlenderRenderSetting::parse_from(&args, &blend_info); - let global_settings = Arc::new(settings); - - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8081); - let mut server = Server::new(); - - server.register_simple("next_render_queue", move |_i: i32| match get_next_frame() { - Some(frame) => Ok(frame), - None => Err(Fault::new(1, "No more frames to render!")), - }); - - server.register_simple("fetch_info", move |_i: i32| { - let setting = (*global_settings).clone(); - Ok(setting) - }); - - let bind_server = server - .bind(&socket) - .expect("Unable to open socket for xml_rpc!"); - - // spin up XML-RPC server - spawn(async move { - loop { - // if the program shut down or if we've completed the render, then we should stop the server - match listener.try_recv() { - Ok(Status::Exit) => break, - _ => bind_server.poll(), - } - } - }); - - spawn(async move { - let script_path = Blender::get_config_path().join("render.py"); - if !script_path.exists() { - let data = include_bytes!("./render.py"); - fs::write(&script_path, data).unwrap(); - } - - let col = vec![ - "--factory-startup".to_string(), - "-noaudio".to_owned(), - "-b".to_owned(), - args.file.to_str().unwrap().to_string(), - "-P".to_owned(), - script_path.to_str().unwrap().to_string(), - ]; - - let stdout = Command::new(executable) - .args(col) - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .stdout - .unwrap(); - - let reader = BufReader::new(stdout); - let mut frame: i32 = 0; - - // parse stdout for human to read - reader.lines().for_each(|line| { - if let Ok(line) = line { - match line { - line if line.contains("Fra:") => { - let col = line.split('|').collect::>(); - - // this seems a bit expensive? - let init = col[0].split(" ").next(); - if let Some(value) = init { - frame = value.replace("Fra:", "").parse().unwrap_or(1); - } - let last = col.last().unwrap().trim(); - let slice = last.split(' ').collect::>(); - let msg = match slice[0] { - "Rendering" => { - let current = slice[1].parse::().unwrap(); - let total = slice[3].parse::().unwrap(); - let percentage = current / total * 100.0; - let render_perc = format!("{} {:.2}%", last, percentage); - Status::Running { - status: render_perc, - } - } - "Sample" => Status::Running { - status: last.to_owned(), - }, - _ => Status::Log { - status: last.to_owned(), - }, - }; - rx.send(msg).unwrap(); - } - line if line.contains("Saved:") => { - let location = line.split('\'').collect::>(); - let result = PathBuf::from(location[1]); - rx.send(Status::Completed { frame, result }).unwrap(); - } - // Strange how this was thrown, but doesn't report back to this program? - line if line.contains("EXCEPTION:") => { - signal.send(Status::Exit).unwrap(); - rx.send(Status::Error(BlenderError::PythonError(line.to_owned()))) - .unwrap(); - } - line if line.contains("Warning:") => { - rx.send(Status::Warning { - message: line.to_owned(), - }) - .unwrap(); - } - line if line.contains("Error:") => { - let msg = Status::Error(BlenderError::RenderError(line.to_owned())); - rx.send(msg).unwrap(); - } - line if line.contains("Blender quit") => { - signal.send(Status::Exit).unwrap(); - rx.send(Status::Exit).unwrap(); - } - line if !line.is_empty() => { - let msg = Status::Running { - status: line.to_owned(), - }; - rx.send(msg).unwrap(); - } - _ => { - // Only empty log entry would show up here... - } - }; - }; - }); - }); - tx - } -} - -// TODO: impl unit test for blender specifically. -/* -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn should_run() {} - - #[test] - fn should_render() {} -} -*/ diff --git a/blender/src/lib.rs b/blender/src/lib.rs deleted file mode 100644 index 518b8cea..00000000 --- a/blender/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod blender; -pub mod manager; -pub mod models; -pub mod page_cache; diff --git a/blender/src/main.rs b/blender/src/main.rs deleted file mode 100644 index e7a11a96..00000000 --- a/blender/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/blender/src/manager.rs b/blender/src/manager.rs deleted file mode 100644 index 374a0ee9..00000000 --- a/blender/src/manager.rs +++ /dev/null @@ -1,325 +0,0 @@ -/* - Developer blog: - This manager class will serve the following purpose: - - Keep track of blender installation on this active machine. - - Prevent downloading of the same blender version if we have one already installed. - - If user fetch for list of installation, verify all path exist before returning the list. - - Implements download and install code -*/ -use crate::blender::Blender; -use crate::models::{category::BlenderCategory, download_link::DownloadLink, home::BlenderHome}; - -use semver::Version; -use serde::{Deserialize, Serialize}; -use std::path::Path; -use std::{fs, path::PathBuf}; -use thiserror::Error; - -// I would like this to be a feature only crate. blender by itself should be lightweight and interface with the program directly. -// could also implement serde as optionals? -#[derive(Debug, Error)] -pub enum ManagerError { - #[error("Unsupported OS: {0}")] - UnsupportedOS(String), - #[error("Unsupported Archtecture: {0}")] - UnsupportedArch(String), - #[error("Unable to extract content: {0}")] - UnableToExtract(String), - #[error("Unable to fetch download from the source! {0}")] - FetchError(String), - #[error("Cannot find target download link for blender! os: {os} | arch: {arch} | url: {url}")] - DownloadNotFound { - arch: String, - os: String, - url: String, - }, - #[error("Unable to fetch blender! {0}")] - RequestError(String), - // TODO: Find meaningful error message to represent from this struct class? - #[error("IO Error: {0}")] - IoError(String), - #[error("Url ParseError: {0}")] - UrlParseError(String), - #[error("Page cache error: {0}")] - PageCacheError(String), - #[error("Blender error: {source}")] - BlenderError { - #[from] - source: crate::blender::BlenderError, - }, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct BlenderConfig { - blenders: Vec, - install_path: PathBuf, - auto_save: bool, -} - -// I wanted to keep this struct private only to this library crate? -#[derive(Debug)] -pub struct Manager { - /// Store all known installation of blender directory information - config: BlenderConfig, - pub home: BlenderHome, // for now let's make this public - has_modified: bool, -} - -impl Default for Manager { - // the default method implement should be private because I do not want people to use this function. - // instead they should rely on "load" function instead. - fn default() -> Self { - let install_path = dirs::download_dir().unwrap().join("Blender"); - let config = BlenderConfig { - blenders: Vec::new(), - install_path, - auto_save: true, - }; - Self { - config, - home: BlenderHome::new().expect("Unable to load blender home!"), - has_modified: false, - } - } -} - -impl Manager { - fn set_config(&mut self, config: BlenderConfig) -> &mut Self { - self.config = config; - self - } - - pub fn get_config_dir() -> PathBuf { - let path = dirs::config_dir().unwrap().join("BlendFarm"); - fs::create_dir_all(&path).expect("Unable to create directory!"); - path - } - - // this path should always be fixed and stored under machine specific. - // this path should not be shared across machines. - fn get_config_path() -> PathBuf { - Self::get_config_dir().join("BlenderManager.json") - } - - // Download the specific version from download.blender.org - pub fn download(&mut self, version: &Version) -> Result { - // TODO: As a extra security measure, I would like to verify the hash of the content before extracting the files. - let arch = std::env::consts::ARCH.to_owned(); - let os = std::env::consts::OS.to_owned(); - - let category = self - .home - .as_ref() - .iter() - .find(|&b| b.major.eq(&version.major) && b.minor.eq(&version.minor)) - .ok_or(ManagerError::DownloadNotFound { - arch, - os, - url: "".to_owned(), - })?; - - let download_link = category - .retrieve(version) - .map_err(|e| ManagerError::FetchError(e.to_string()))?; - - let destination = self.config.install_path.join(&category.name); - - // got a permission denied here? Interesting? - // I need to figure out why and how I can stop this from happening? - fs::create_dir_all(&destination).unwrap(); - - // TODO: verify this is working for windows (.zip)? - let destination = download_link - .download_and_extract(&destination) - .map_err(|e| ManagerError::IoError(e.to_string()))?; - - let blender = Blender::from_executable(destination) - .map_err(|e| ManagerError::BlenderError { source: e })?; - self.add_blender(blender.clone()); - self.save().unwrap(); - Ok(blender) - } - - // Save the configuration to local - // do I need to save? What's the reason behind this? - fn save(&self) -> Result<(), ManagerError> { - // strictly speaking, this function shouldn't crash... - let data = serde_json::to_string(&self.config).unwrap(); - let path = Self::get_config_path(); - fs::write(path, data).map_err(|e| ManagerError::IoError(e.to_string())) - } - - /// Return a reference to the vector list of all known blender installations - pub fn get_blenders(&self) -> &Vec { - &self.config.blenders - } - - /// Load the manager data from the config file. - pub fn load() -> Self { - // load from a known file path (Maybe a persistence storage solution somewhere?) - // if the config file does not exist on the system, create a new one and return a new struct instead. - let path = Self::get_config_path(); - let mut data = Self::default(); - if let Ok(content) = fs::read_to_string(&path) { - if let Ok(config) = serde_json::from_str(&content) { - data.set_config(config); - return data; - } else { - println!("Fail to deserialize manager config file!"); - } - } else { - println!("File not found! Creating a new default one!"); - }; - // default case, create a new manager data and save it. - let data = Self::default(); - match data.save() { - Ok(()) => println!("New manager data created and saved!"), - // TODO: Find a better way to handle this error. - Err(e) => println!("Unable to save new manager data! {:?}", e), - } - data - } - - pub fn get_install_path(&self) -> &Path { - &self.config.install_path - } - - /// Set path for blender download and installation - pub fn set_install_path(&mut self, new_path: &Path) { - // Consider the design behind this. Should we move blender installations to new path? - self.config.install_path = new_path.to_path_buf().clone(); - self.has_modified = true; - } - - /// Add a new blender installation to the manager list. - pub fn add_blender(&mut self, blender: Blender) { - self.config.blenders.push(blender); - self.has_modified = true; - } - - /// Check and add a local installation of blender to manager's registry of blender version to use from. - pub fn add_blender_path(&mut self, path: &impl AsRef) -> Result { - let path = path.as_ref(); - let extension = BlenderCategory::get_extension().map_err(ManagerError::UnsupportedOS)?; - - let path = if path - .extension() - .is_some_and(|e| extension.contains(e.to_str().unwrap())) - { - // Create a folder name from given path - let folder_name = &path - .file_name() - .unwrap() - .to_os_string() - .to_str() - .unwrap() - .replace(&extension, ""); - - DownloadLink::extract_content(path, folder_name) - .map_err(|e| ManagerError::UnableToExtract(e.to_string())) - } else { - // for MacOS - User will select the app bundle instead of actual executable, We must include the additional path - match std::env::consts::OS { - "macos" => Ok(path.join("Contents/MacOS/Blender")), - _ => Ok(path.to_path_buf()), - } - }?; - let blender = - Blender::from_executable(path).map_err(|e| ManagerError::BlenderError { source: e })?; - - // I would have at least expect to see this populated? - self.add_blender(blender.clone()); - // TODO: This is a hack - Would prefer to understand why program does not auto save file after closing. - // Or look into better saving mechanism than this. - let _ = self.save(); - Ok(blender) - } - - /// Remove blender installation from the manager list. - pub fn remove_blender(&mut self, blender: &Blender) { - self.config.blenders.retain(|x| x.eq(blender)); - self.has_modified = true; - } - - /// Deletes the parent directory that blender reside in. This might be a dangerous function as this involves removing the directory blender executable is in. - /// TODO: verify that this doesn't break macos path executable... Why mac gotta be special with appbundle? - pub fn delete_blender(&mut self, _blender: &Blender) { - // this deletes blender from the system. You have been warn! - // todo!("Exercise with caution!"); - // fs::remove_dir_all(_blender.get_executable().parent().unwrap()).unwrap(); - self.remove_blender(_blender); - } - - // TODO: Name ambiguous - clarify method name to clear and explicit - pub fn fetch_blender(&mut self, version: &Version) -> Result { - match self.have_blender(version) { - Some(blender) => Ok(blender.clone()), - None => self.download(version), - } - } - - pub fn have_blender(&self, version: &Version) -> Option<&Blender> { - self.config - .blenders - .iter() - .find(|x| x.get_version().eq(version)) - } - - /// Fetch the latest version of blender available from Blender.org - /// this function might be ambiguous. Should I use latest_local or latest_online? - pub fn latest_local_avail(&mut self) -> Option { - // in this case I need to contact Manager class or BlenderDownloadLink somewhere and fetch the latest blender information - let mut data = self.config.blenders.clone(); - data.sort(); - data.first().map(|v: &Blender| v.to_owned()) - } - - // find a way to hold reference to blender home here? - pub fn download_latest_version(&mut self) -> Result { - // in this case - we need to fetch the latest version from somewhere, download.blender.org will let us fetch the parent before we need to dive into - let list = self.home.as_ref(); - // TODO: Find a way to replace these unwrap() - let category = list.first().unwrap(); - let destination = self.config.install_path.join(&category.name); - - // got a permission denied here? Interesting? - // I need to figure out why and how I can stop this from happening? - fs::create_dir_all(&destination).unwrap(); - - let link = category.fetch_latest().unwrap(); - let path = link - .download_and_extract(&destination) - .map_err(|e| ManagerError::IoError(e.to_string()))?; - dbg!(&path); - let blender = - Blender::from_executable(path).map_err(|e| ManagerError::BlenderError { source: e })?; - self.config.blenders.push(blender.clone()); - Ok(blender) - } -} - -impl AsRef for Manager { - fn as_ref(&self) -> &PathBuf { - &self.config.install_path - } -} - -impl Drop for Manager { - fn drop(&mut self) { - if self.has_modified || self.config.auto_save { - if let Err(e) = self.save() { - println!("Error saving manager file: {}", e); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn should_pass() { - let _manager = Manager::load(); - } -} diff --git a/blender/src/models.rs b/blender/src/models.rs deleted file mode 100644 index af15e149..00000000 --- a/blender/src/models.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod args; -pub mod blender_peek_response; -pub mod blender_render_setting; -pub mod category; -pub mod device; -pub mod download_link; -pub mod engine; -pub mod format; -pub mod home; -pub mod mode; -pub mod status; diff --git a/blender/src/models/args.rs b/blender/src/models/args.rs deleted file mode 100644 index 9f434c9a..00000000 --- a/blender/src/models/args.rs +++ /dev/null @@ -1,48 +0,0 @@ -/* - Developer blog - - - Having done extensive research, Blender have two ways to interface to the program - 1. Through CLI - 2. Through Python API via "bpy" library - - Review online for possible solution to interface blender via CAPI, but was strongly suggested to use a python script instead - this limits what I can do in term of functionality, but it'll be a good start. - FEATURE - See if python allows pointers/buffer access to obtain job render progress - Allows node to send host progress result. Possibly viewport network rendering? - - Do note that blender is open source - it's not impossible to create FFI that interfaces blender directly, but rather, there's no support to perform this kind of action. - - BlendFarm code shows that they heavily rely on using python code to perform exact operation. - Question is, do I want to use their code, or do I want to stick with CLI instead? - I'll try implement both solution, CLI for version and other basic commands, python for advance features and upgrade? -*/ - -// May Subject to change. - -use crate::models::{device::Device, engine::Engine, format::Format}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -// ref: https://docs.blender.org/manual/en/latest/advanced/command_line/render.html -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Args { - pub file: PathBuf, // required - pub output: PathBuf, // optional - pub engine: Engine, // optional - pub device: Device, // optional - pub format: Format, // optional - default to Png - pub use_continuation: bool, // optional - default to false -} - -impl Args { - pub fn new(file: PathBuf, output: PathBuf) -> Self { - Args { - file: file, - output: output, - // TODO: Change this so that we can properly reflect the engine used by A) Blendfile B) User request, and C) allowlist from machine config - engine: Default::default(), - device: Default::default(), - format: Default::default(), - use_continuation: false, - } - } -} diff --git a/blender/src/models/blender_peek_response.rs b/blender/src/models/blender_peek_response.rs deleted file mode 100644 index 3aa81433..00000000 --- a/blender/src/models/blender_peek_response.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::path::PathBuf; - -use semver::Version; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct BlenderPeekResponse { - pub last_version: Version, - pub render_width: i32, - pub render_height: i32, - pub frame_start: i32, - pub frame_end: i32, - #[serde(rename = "FPS")] - pub fps: u16, - pub samples: i32, - pub cameras: Vec, - pub selected_camera: String, - pub scenes: Vec, - pub selected_scene: String, - pub engine: String, - pub output: PathBuf, -} diff --git a/blender/src/models/blender_render_setting.rs b/blender/src/models/blender_render_setting.rs deleted file mode 100644 index 3f90b660..00000000 --- a/blender/src/models/blender_render_setting.rs +++ /dev/null @@ -1,165 +0,0 @@ -use super::{ - args::Args, blender_peek_response::BlenderPeekResponse, device::Device, engine::Engine, - format::Format, -}; -use serde::{de::Visitor, ser::SerializeStruct, Deserialize, Serialize}; -use std::{ops::Range, path::PathBuf}; -use uuid::Uuid; - -// In the python script, this Window values gets assigned to border of scn.render.border_* -// Here - I'm calling it as window instead. -#[derive(Debug, Clone, PartialEq)] -pub struct Window { - pub x: Range, - pub y: Range, -} - -impl Default for Window { - fn default() -> Self { - Self { - x: Range { - start: 0.0, - end: 1.0, - }, - y: Range { - start: 0.0, - end: 1.0, - }, - } - } -} - -impl Serialize for Window { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_struct("Border", 4)?; - state.serialize_field("X", &self.x.start)?; - state.serialize_field("X2", &self.x.end)?; - state.serialize_field("Y", &self.y.start)?; - state.serialize_field("Y2", &self.y.end)?; - state.end() - } -} - -impl<'de> Deserialize<'de> for Window { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct WindowVisitor; - - impl<'de> Visitor<'de> for WindowVisitor { - type Value = Window; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct Border") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: serde::de::SeqAccess<'de>, - { - let x = seq.next_element()?.unwrap_or(0.0); - let x2 = seq.next_element()?.unwrap_or(1.0); - let y = seq.next_element()?.unwrap_or(0.0); - let y2 = seq.next_element()?.unwrap_or(1.0); - Ok(Window { - x: Range { start: x, end: x2 }, - y: Range { start: y, end: y2 }, - }) - } - } - - const FIELDS: &[&str] = &["X", "X2", "Y", "Y2"]; - deserializer.deserialize_struct("Window", FIELDS, WindowVisitor) - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct BlenderRenderSetting { - #[serde(rename = "TaskID")] - pub id: Uuid, - pub output: PathBuf, - pub scene: String, - pub camera: String, - pub cores: usize, - pub compute_unit: i32, - #[serde(rename = "FPS")] - pub fps: u16, // u32 convert into string for xml-rpc. BEWARE! - pub border: Window, - pub tile_width: i32, - pub tile_height: i32, - pub samples: i32, - pub width: i32, - pub height: i32, - pub engine: i32, - #[serde(rename = "RenderFormat")] - pub format: Format, - // discourage? - pub crop: bool, -} - -impl BlenderRenderSetting { - fn new( - output: PathBuf, - scene: String, - camera: String, - compute_unit: Device, - fps: u16, - border: Window, - tile_width: i32, - tile_height: i32, - samples: i32, - width: i32, - height: i32, - engine: Engine, - format: Format, - ) -> Self { - let id = Uuid::new_v4(); - Self { - id, - output, - scene, - camera, - cores: std::thread::available_parallelism().unwrap().get(), - compute_unit: compute_unit as i32, - fps, - border, - tile_width, - tile_height, - samples, - width, - height, - engine: engine as i32, - format, - crop: false, - } - } - - pub fn parse_from(args: &Args, info: &BlenderPeekResponse) -> Self { - let output = args.output.clone(); - let compute_unit = args.device.clone(); - let border = Default::default(); - let engine = args.engine.clone(); - let format = args.format.clone(); - - BlenderRenderSetting::new( - output.to_owned(), - info.selected_scene.to_owned(), - info.selected_camera.to_owned(), - compute_unit.to_owned(), - info.fps, - border, - -1, - -1, - info.samples, - info.render_width, - info.render_height, - engine, - format, - ) - } -} diff --git a/blender/src/models/category.rs b/blender/src/models/category.rs deleted file mode 100644 index 5400f3cc..00000000 --- a/blender/src/models/category.rs +++ /dev/null @@ -1,137 +0,0 @@ -use super::download_link::DownloadLink; -use crate::page_cache::PageCache; -use regex::Regex; -use semver::Version; -use std::env::consts; -use thiserror::Error; -use url::Url; - -// I'd like to relocate this to a different file. Possibly home? -#[derive(Debug)] -pub struct BlenderCategory { - pub name: String, - pub url: Url, - pub major: u64, - pub minor: u64, -} - -#[derive(Debug, Error)] -pub enum BlenderCategoryError { - #[error("Architecture type \"{0}\" is not supported!")] - InvalidArch(String), - #[error("Unsupported operating system: {0}")] - UnsupportedOS(String), - #[error("Not found")] - NotFound, - #[error("Io Error")] - Io(#[from] std::io::Error), -} - -impl BlenderCategory { - /// fetch current architecture (Currently support x86_64 or aarch64 (apple silicon)) - fn get_valid_arch() -> Result { - match consts::ARCH { - "x86_64" => Ok("x64".to_owned()), - "aarch64" => Ok("arm64".to_owned()), - arch => Err(BlenderCategoryError::InvalidArch(arch.to_string())), - } - } - - /// Return extension matching to the current operating system (Only display Windows(.zip), Linux(.tar.xz), or macos(.dmg)). - pub fn get_extension() -> Result { - match consts::OS { - "windows" => Ok(".zip".to_owned()), - "macos" => Ok(".dmg".to_owned()), - "linux" => Ok(".tar.xz".to_owned()), - os => Err(os.to_string()), - } - } - - pub fn new(name: String, url: Url, major: u64, minor: u64) -> Self { - Self { - name, - url, - major, - minor, - } - } - - // TODO - implement thiserror? - // for some reason I was fetching this multiple of times already. This seems expensive to call for some reason? - // also, strange enough, the pattern didn't pick up anything? - pub fn fetch(&self) -> Result, BlenderCategoryError> { - // TODO: Find a way to recycle PageCache from BlenderHome - let mut cache = PageCache::load()?; // I really hate the fact that I have to create a new instance for this. - let content = cache.fetch(&self.url).map_err(BlenderCategoryError::Io)?; - let arch = Self::get_valid_arch()?; - let ext = Self::get_extension().map_err(BlenderCategoryError::UnsupportedOS)?; - - // Regex rules - Find the url that matches version, computer os and arch, and the extension. - // - There should only be one entry matching for this. Otherwise return error stating unable to find download path - let pattern = format!( - r#".*)\">(?.*-{}\.{}\.(?\d*.)-{}.*{}*.{})<\/a>"#, - self.major, - self.minor, - consts::OS, - arch, - ext, - ); - - let regex = Regex::new(&pattern).unwrap(); - // for (_, [url, name, patch]) in - let vec = regex - .captures_iter(&content) - .filter_map(|c| { - let (_, [url, name, patch]) = c.extract(); - let url = self.url.join(url).ok()?; - let patch = patch.parse().ok()?; - let version = Version::new(self.major, self.minor, patch); - Some(DownloadLink::new(name.to_owned(), url, version)) - }) - .collect(); - - Ok(vec) - } - - pub fn fetch_latest(&self) -> Result { - let mut list = self.fetch()?; - list.sort_by(|a, b| b.cmp(a)); - let entry = list.first().ok_or(BlenderCategoryError::NotFound)?; - Ok(entry.clone()) - } - - pub fn retrieve(&self, version: &Version) -> Result { - let list = self.fetch()?; - let entry = list - .iter() - .find(|dl| dl.as_ref().eq(version)) - .ok_or(BlenderCategoryError::NotFound)?; - Ok(entry.to_owned()) - } -} - -impl PartialEq for BlenderCategory { - fn eq(&self, other: &Self) -> bool { - self.url == other.url && self.major.eq(&other.major) && self.minor.eq(&other.minor) - } -} - -impl Eq for BlenderCategory {} - -impl PartialOrd for BlenderCategory { - fn partial_cmp(&self, other: &Self) -> Option { - match self.major.partial_cmp(&other.major) { - Some(core::cmp::Ordering::Equal) => return self.minor.partial_cmp(&other.minor), - ord => return ord, - } - } -} - -impl Ord for BlenderCategory { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - match self.major.cmp(&other.major) { - std::cmp::Ordering::Equal => self.minor.cmp(&other.minor), - all => return all, - } - } -} diff --git a/blender/src/models/device.rs b/blender/src/models/device.rs deleted file mode 100644 index 6dbb7a16..00000000 --- a/blender/src/models/device.rs +++ /dev/null @@ -1,43 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/* -Developer blog- -The only reason why we need to add number that may or may not match blender's enum number list -is because we're passing in the arguments to the python file instead of Blender CLI. -Once I get this part of the code working, then I'll go back and refactor python to make this less ugly and hackable. -*/ - -// TODO: Once python code is working with this rust code - refactor python to reduce this garbage mess below: -#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)] -#[allow(dead_code, non_camel_case_types)] -pub enum Device { - #[default] - CPU = 0, - CUDA = 1, - OPENCL = 2, - CUDA_GPUONLY = 3, - OPENCL_GPUONLY = 4, - HIP = 5, - HIP_GPUONLY = 6, - METAL = 7, - METAL_GPUONLY = 8, - ONEAPI = 9, - ONEAPI_GPUONLY = 10, - OPTIX = 11, - OPTIX_GPUONLY = 12, -} - -// Append +CPU to a GPU device to render on both CPU and GPU. -impl ToString for Device { - fn to_string(&self) -> String { - match self { - Device::CPU => "CPU".to_owned(), - Device::CUDA => "CUDA".to_owned(), - Device::OPTIX => "OPTIX".to_owned(), - Device::HIP => "HIP".to_owned(), - Device::ONEAPI => "ONEAPI".to_owned(), - Device::METAL => "METAL".to_owned(), - _ => todo!("to be implemented after getting this to work with python!"), - } - } -} diff --git a/blender/src/models/download_link.rs b/blender/src/models/download_link.rs deleted file mode 100644 index f89f8e3a..00000000 --- a/blender/src/models/download_link.rs +++ /dev/null @@ -1,170 +0,0 @@ -use super::category::BlenderCategory; -use semver::Version; -use serde::{Deserialize, Serialize}; -use std::{ - fs, - io::{Error, Read}, - path::{Path, PathBuf}, -}; -use url::Url; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] -pub struct DownloadLink { - pub name: String, - url: Url, - version: Version, -} - -impl DownloadLink { - /* private function impl */ - - pub fn new(name: String, url: Url, version: Version) -> Self { - Self { name, url, version } - } - - pub fn get_version(&self) -> &Version { - &self.version - } - - // Currently being used for MacOS (I wonder if I need to do the same for windows?) - #[cfg(target_os = "macos")] - fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> Result<(), Error> { - fs::create_dir_all(&dst)?; - for entry in fs::read_dir(src)? { - let entry = entry.unwrap(); - if entry.file_type().unwrap().is_dir() { - Self::copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name())).unwrap(); - } else { - fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; - } - } - Ok(()) - } - - /// Extract tar.xz file from destination path, and return blender executable path - // TODO: Tested on Linux - something didn't work right here. Need to investigate/debug through - #[cfg(target_os = "linux")] - pub fn extract_content( - download_path: impl AsRef, - folder_name: &str, - ) -> Result { - use std::fs::File; - use tar::Archive; - use xz::read::XzDecoder; - - // Get file handler to download location - let file = File::open(&download_path)?; - - // decode compressed xz file - let tar = XzDecoder::new(file); - - // unarchive content from decompressed file - let mut archive = Archive::new(tar); - - // generate destination path - let destination = download_path.as_ref().parent().unwrap(); - - // extract content to destination - archive.unpack(destination)?; - - // return extracted executable path - Ok(destination.join(folder_name).join("blender")) - } - - // TODO: Test this on macos - /// Mounts dmg target to volume, then extract the contents to a new folder using the folder_name, - /// lastly, provide a path to the blender executable inside the content. - #[cfg(target_os = "macos")] - pub fn extract_content( - download_path: impl AsRef, - folder_name: &str, - ) -> Result { - use dmg::Attach; - - let source = download_path.as_ref(); - let dst = source // generate destination path - .parent() - .unwrap() - .join(folder_name) - .join("Blender.app"); - - if !dst.exists() { - let _ = fs::create_dir_all(&dst)?; - } - - let dmg = Attach::new(&source).attach()?; // attach dmg to volume - let src = PathBuf::from(&dmg.mount_point.join("Blender.app")); // create source path from mount point - Self::copy_dir_all(&src, &dst)?; // Extract content inside Blender.app to destination - dmg.detach()?; // detach dmg volume - Ok(dst.join("Contents/MacOS/Blender")) // return path with additional path to invoke blender directly - } - - #[cfg(target_os = "windows")] - pub fn extract_content( - download_path: impl AsRef, - folder_name: &str, - ) -> Result { - use std::fs::File; - use zip::ZipArchive; - - let source = download_path.as_ref(); - // On windows, unzipped content includes a new folder underneath. Instead of doing this, we will just unzip from the parent instead... weird - let zip_loc = source.parent().unwrap(); - let output = zip_loc.join(folder_name); - - // check if the directory exist - match &output.exists() { - // if it does, check and see if blender exist. - true => { - // if it does exist, then we can skip extracting the file entirely. - if output.join("Blender.exe").exists() { - return Ok(output.join("Blender.exe")); - } - } - _ => {} - } - - let file = File::open(source).unwrap(); - let mut archive = ZipArchive::new(file).unwrap(); - if let Err(e) = archive.extract(zip_loc) { - println!("Unable to extract content to target: {e:?}"); - } - - Ok(output.join("Blender.exe")) - } - - // contains intensive IO operation - // TODO: wonder why I'm not using BlenderError for this? - pub fn download_and_extract(&self, destination: impl AsRef) -> Result { - // precheck qualification - let ext = BlenderCategory::get_extension() - .map_err(|e| Error::other(format!("Cannot run blender under this OS: {}!", e)))?; - - let target = &destination.as_ref().join(&self.name); - - // Check and see if we haven't already download the file - if !target.exists() { - // Download the file from the internet and save it to blender data folder - let mut response = ureq::get(self.url.as_str()) - .call() - .map_err(|e: ureq::Error| Error::other(e))?; - - let mut body: Vec = Vec::new(); - if let Err(e) = response.body_mut().as_reader().read_to_end(&mut body) { - eprintln!("Fail to read data from response! {e:?}"); - } - fs::write(target, &body)?; - } - - // create a target folder name to extract content to. - let folder_name = &self.name.replace(&ext, ""); - let executable_path = Self::extract_content(target, folder_name)?; - Ok(executable_path) - } -} - -impl AsRef for DownloadLink { - fn as_ref(&self) -> &Version { - &self.version - } -} diff --git a/blender/src/models/engine.rs b/blender/src/models/engine.rs deleted file mode 100644 index 65cbe6bd..00000000 --- a/blender/src/models/engine.rs +++ /dev/null @@ -1,20 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Copy, Default, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub enum Engine { - Cycles = 0, - #[default] - Eevee = 1, - OptiX = 2, -} - -impl ToString for Engine { - fn to_string(&self) -> String { - match self { - Engine::Cycles => "CYCLES".to_owned(), - // Blender 4.2 introduce a new enum called BLENDER_EEVEE_NEXT, which is currently handle in python file atm. - Engine::Eevee => "EEVEE".to_owned(), - Engine::OptiX => "WORKBENCH".to_owned(), - } - } -} diff --git a/blender/src/models/format.rs b/blender/src/models/format.rs deleted file mode 100644 index 39739c79..00000000 --- a/blender/src/models/format.rs +++ /dev/null @@ -1,68 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::str::FromStr; - -pub enum FormatError { - InvalidInput, -} - -// More context: https://docs.blender.org/manual/en/latest/advanced/command_line/arguments.html#format-options -#[derive(Debug, Clone, Default, PartialEq, Deserialize)] -pub enum Format { - TGA, - RAWTGA, - JPEG, - IRIS, - AVIRAW, - AVIJPEG, - #[default] - PNG, - BMP, - HDR, - TIFF, -} - -impl Serialize for Format { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl FromStr for Format { - type Err = FormatError; - - fn from_str(s: &str) -> Result { - match s.to_uppercase().as_str() { - "TGA" => Ok(Format::TGA), - "RAWTGA" => Ok(Format::RAWTGA), - "JPEG" => Ok(Format::JPEG), - "IRIS" => Ok(Format::IRIS), - "AVIRAW" => Ok(Format::AVIRAW), - "AVIJPEG" => Ok(Format::AVIJPEG), - "PNG" => Ok(Format::PNG), - "BMP" => Ok(Format::BMP), - "HDR" => Ok(Format::HDR), - "TIFF" => Ok(Format::TIFF), - _ => Err(FormatError::InvalidInput), - } - } -} - -impl ToString for Format { - fn to_string(&self) -> String { - match self { - Format::TGA => "TARGA".to_owned(), - Format::RAWTGA => "RAWTARGA".to_owned(), - Format::JPEG => "JPEG".to_owned(), - Format::IRIS => "IRIS".to_owned(), - Format::AVIRAW => "AVIRAW".to_owned(), - Format::AVIJPEG => "AVIJPEG".to_owned(), - Format::PNG => "PNG".to_owned(), - Format::BMP => "BMP".to_owned(), - Format::HDR => "HDR".to_owned(), - Format::TIFF => "TIFF".to_owned(), - } - } -} diff --git a/blender/src/models/home.rs b/blender/src/models/home.rs deleted file mode 100644 index 83ec555e..00000000 --- a/blender/src/models/home.rs +++ /dev/null @@ -1,72 +0,0 @@ -use super::category::BlenderCategory; -use crate::page_cache::PageCache; -use regex::Regex; -use std::io::{Error, ErrorKind, Result}; -use url::Url; - -#[derive(Debug)] -pub struct BlenderHome { - // might use this as a ref? - list: Vec, - // I'd like to reuse this component throughout blender program. If I need to access a web page, this should be used. - cache: PageCache, -} - -impl BlenderHome { - fn get_content(cache: &mut PageCache) -> Result> { - let parent = Url::parse("https://download.blender.org/release/").unwrap(); - let content = cache.fetch(&parent)?; - - // Omit any blender version 2.8 and below - let pattern = r#".*)\">(?Blender(?[3-9]|\d{2,}).(?\d*).*)\/<\/a>"#; - let regex = Regex::new(pattern).map_err(|e| { - Error::new( - ErrorKind::InvalidData, - format!("Unable to create new Regex pattern! {e:?}"), - ) - })?; - - let mut list: Vec = regex - .captures_iter(&content) - .map(|c| { - let (_, [url, name, major, minor]) = c.extract(); - let url = parent.join(url).ok()?; - let major = major.parse().ok()?; - let minor = minor.parse().ok()?; - Some(BlenderCategory::new(name.to_owned(), url, major, minor)) - }) - .flatten() - .collect(); - - list.sort_by(|a, b| b.cmp(a)); - Ok(list) - } - - // I need to have this reference regardless. Offline or online mode. - pub fn new() -> Result { - // TODO: Verify this-: In original source code - there's a comment implying we should use cache as much as possible to avoid possible IP lacklisted. - let mut cache = PageCache::load()?; - let list = match Self::get_content(&mut cache) { - Ok(col) => col, - // maybe the user is offline, we don't know, and that's ok! This shouldn't stop the program from running - // TODO: It would be nice to indicate that we're running in offline mode. Disable some feature such as download blender from web. - Err(e) => { - eprintln!("Unable to get content! {e:?}"); - Vec::new() - } - }; - Ok(Self { list, cache }) - } - - pub fn refresh(&mut self) -> Result<()> { - let content = Self::get_content(&mut self.cache)?; - self.list = content; - Ok(()) - } -} - -impl AsRef> for BlenderHome { - fn as_ref(&self) -> &Vec { - &self.list - } -} diff --git a/blender/src/models/mode.rs b/blender/src/models/mode.rs deleted file mode 100644 index b716f0cb..00000000 --- a/blender/src/models/mode.rs +++ /dev/null @@ -1,23 +0,0 @@ -// use std::default; -use std::ops::Range; -use serde::{Deserialize, Serialize}; - -// context for serde: https://serde.rs/enum-representations.html -#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] -pub enum Mode { - // JSON: "Frame": "i32", - // render a single frame. - Frame(i32), - - // JSON: "Animation": {"start":"i32", "end":"i32"} - // contains the target start frame to the end target frame. - Animation(Range), - - // future project - allow network node to only render section of the frame instead of whole to visualize realtime rendering view solution. - // JSON: "Section": {"frame":"i32", "coord":{"i32", "i32"}, "size": {"i32", "i32"} } - // Section { - // frame: i32, - // coord: (i32, i32), - // size: (i32, i32), - // }, -} diff --git a/blender/src/models/status.rs b/blender/src/models/status.rs deleted file mode 100644 index 57ffafeb..00000000 --- a/blender/src/models/status.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::blender::BlenderError; -use std::path::PathBuf; - -// TODO Find good use of this? -#[derive(Debug)] -pub enum Status { - Idle, - Running { status: String }, - Log { status: String }, - Warning { message: String }, - Error(BlenderError), - Completed { frame: i32, result: PathBuf }, - Exit, -} diff --git a/blender/src/page_cache.rs b/blender/src/page_cache.rs deleted file mode 100644 index 618f185d..00000000 --- a/blender/src/page_cache.rs +++ /dev/null @@ -1,159 +0,0 @@ -use regex::Regex; -use serde::{Deserialize, Serialize}; -use std::io::{Error, Read, Result}; -use std::{collections::HashMap, fs, path::PathBuf, time::SystemTime}; -use url::Url; - -// Hide this for now, -#[doc(hidden)] -// rely the cache creation date on file metadata. -#[derive(Debug, Deserialize, Serialize, Default)] -pub struct PageCache { - // Url is not serialized? - cache: HashMap, - was_modified: bool, -} - -impl PageCache { - // fetch cache directory - fn get_dir() -> Result { - // TODO: What should happen if I can't fetch cache_dir()? - let mut tmp = dirs::cache_dir().ok_or(Error::new( - std::io::ErrorKind::NotFound, - "Unable to fetch cache directory!", - ))?; - tmp.push("cache"); - fs::create_dir_all(&tmp)?; - Ok(tmp) - } - - // fetch path to cache file - fn get_cache_path() -> Result { - let path = Self::get_dir()?; - Ok(path.join("cache.json")) - } - - // private method, only used to save when cache has changed. - fn save(&mut self) -> Result<()> { - self.was_modified = false; - let data = serde_json::to_string(&self).expect("Unable to deserialize data!"); - let path = Self::get_cache_path()?; - fs::write(path, data)?; - Ok(()) - } - - // TODO: Impl a way to verify cache is not old or out of date. What's a good refresh cache time? 2 weeks? server_settings config? - pub fn load() -> Result { - let expiration = SystemTime::now(); - // use define path to cache file - let path = Self::get_cache_path()?; - let created_date = match fs::metadata(&path) { - Ok(metadata) => { - if metadata.is_file() { - metadata.created().unwrap_or(SystemTime::now()) - } else { - SystemTime::now() - } - } - Err(_) => SystemTime::now(), - }; - - let data = match expiration.duration_since(created_date) { - Ok(_duration) => { - // let sec = duration.as_secs() / (60 * 60 * 24); - // println!("Cache file is {sec} day old!"); - match fs::read_to_string(path) { - Ok(data) => serde_json::from_str(&data).unwrap_or(Self::default()), - Err(_) => Self::default(), - } - } - Err(_) => Self::default(), - }; - - Ok(data) - } - - // This function can be relocated somewhere else? - fn generate_file_name(url: &Url) -> String { - let mut file_name = url.to_string(); - - // Rule: find any invalid file name characters - let re = Regex::new(r#"[/\\?%*:|."<>]"#).unwrap(); - - // remove trailing slash - if file_name.ends_with('/') { - file_name.pop(); - } - - // Replace any invalid characters with hyphens - re.replace_all(&file_name, "-").to_string() - } - - /// Fetch url response from argument and save response body to cache directory using url as file name - /// This will append a new entry to the cache hashmap. - fn save_content_to_cache(url: &Url) -> Result { - // create an absolute file path - let mut tmp = Self::get_dir()?; - tmp.push(Self::generate_file_name(url)); - - // fetch the content from the url - // expensive implict type cast? - let mut response = ureq::get(url.as_ref()).call().map_err(Error::other)?; - let mut body = Vec::new(); - if let Err(e) = response.body_mut().as_reader().read_to_end(&mut body) { - eprintln!("Fail to read data for cache: {e:?}"); - } - - // write the content to the file - fs::write(&tmp, body)?; - Ok(tmp) - } - - /// check and see if the url matches the cache, - /// otherwise, fetch the page from the internet, and save it to storage cache, - /// then return the page result. - pub fn fetch(&mut self, url: &Url) -> Result { - let path = self.cache.entry(url.to_owned()).or_insert({ - self.was_modified = true; - Self::save_content_to_cache(url)?.to_owned() - }); - - fs::read_to_string(path) - } - - // TODO: Maybe this isn't needed, but would like to know if there's a better way to do this? Look into IntoUrl? - pub fn fetch_str(&mut self, url: &str) -> Result { - let url = Url::parse(url).unwrap(); - self.fetch(&url) - } -} - -impl Drop for PageCache { - fn drop(&mut self) { - if self.was_modified { - if let Err(e) = self.save() { - println!("Error saving cache file: {}", e); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn should_pass() { - let cache = PageCache::load(); - assert_eq!(cache.is_ok(), true); - let mut cache = cache.unwrap(); - let url = Url::parse("http://www.google.com").unwrap(); - let content = cache.fetch(&url); - assert_eq!(content.is_ok(), true); - } - - #[test] - fn should_fail() { - todo!(); - } -} diff --git a/blender/src/render.py b/blender/src/render.py deleted file mode 100644 index a42b3cfe..00000000 --- a/blender/src/render.py +++ /dev/null @@ -1,254 +0,0 @@ -# TODO: Refactor this so it's less code to read through. -# Sybren mention that Cycle will perform better if the render was sent out as a batch instead of individual renders. -# TODO: See if there's a way to adjust blender render batch if possible? - -#Start -import bpy # type: ignore -import xmlrpc.client -from multiprocessing import cpu_count - -isPre3 = bpy.app.version < (3,0,0) -# Eventually this might get removed due to getting actual value from blend file instead -isPreEeveeNext = bpy.app.version < (4, 2, 0) - -if(isPre3): - print('Detected Blender >= 3.0.0\n') - -scn = bpy.context.scene - -def useDevices(type, allowGPU, allowCPU): - cyclesPref = bpy.context.preferences.addons["cycles"].preferences - - #For older Blender Builds - if (isPre3): - cyclesPref.compute_device_type = type - devs = cyclesPref.get_devices() - cuda_devices, opencl_devices = cyclesPref.get_devices() - print(cyclesPref.compute_device_type) - - devices = None - if(type == "CUDA"): - devices = cuda_devices - elif(type == "OPTIX"): - devices = cuda_devices - else: - devices = opencl_devices - for d in devices: - d.use = (allowCPU and d.type == "CPU") or (allowGPU and d.type != "CPU") - print(type + " Device:", d["name"], d["use"]) - #For Blender Builds >= 3.0 - else: - cyclesPref.compute_device_type = type - - print(cyclesPref.compute_device_type) - - devices = None - if(type == "CUDA"): - devices = cyclesPref.get_devices_for_type("CUDA") - elif(type == "OPTIX"): - devices = cyclesPref.get_devices_for_type("OPTIX") - elif(type == "HIP"): - devices = cyclesPref.get_devices_for_type("HIP") - elif(type == "METAL"): - devices = cyclesPref.get_devices_for_type("METAL") - elif(type == "ONEAPI"): - devices = cyclesPref.get_devices_for_type("ONEAPI") - else: - devices = cyclesPref.get_devices_for_type("OPENCL") - print("Devices Found:", devices) - if(len(devices) == 0): - raise Exception("No devices found for type " + type + ", Unsupported hardware or platform?") - for d in devices: - d.use = (allowCPU and d.type == "CPU") or (allowGPU and d.type != "CPU") - print(type + " Device:", d["name"], d["use"]) - -#Renders provided settings with id to path -def renderWithSettings(renderSettings, frame): - global scn - - # Scene parse - scen = renderSettings["Scene"] - if(scen is None): - scen = "" - if(scen != "" + scn.name != scen): - print("Rendering specified scene " + scen + "\n") - scn = bpy.data.scenes[scen] - if(scn is None): - raise Exception("Unknown Scene :" + scen) - - # set render format - renderFormat = renderSettings["RenderFormat"] - if (not renderFormat): - scn.render.image_settings.file_format = "PNG" - else: - scn.render.image_settings.file_format = renderFormat - - # Set threading - scn.render.threads_mode = 'FIXED' - scn.render.threads = max(cpu_count(), int(renderSettings["Cores"])) - - if (isPre3): - scn.render.tile_x = int(renderSettings["TileWidth"]) - scn.render.tile_y = int(renderSettings["TileHeight"]) - else: - print("Blender > 3.0 doesn't support tile size, thus ignored") - - # Set constraints - scn.render.use_border = True - scn.render.use_crop_to_border = renderSettings["Crop"] - if not renderSettings["Crop"]: - scn.render.film_transparent = True - - scn.render.border_min_x = float(renderSettings["Border"]["X"]) - scn.render.border_max_x = float(renderSettings["Border"]["X2"]) - scn.render.border_min_y = float(renderSettings["Border"]["Y"]) - scn.render.border_max_y = float(renderSettings["Border"]["Y2"]) - - #Set Camera - camera = renderSettings["Camera"] - if(camera != None and camera != "" and bpy.data.objects[camera]): - scn.camera = bpy.data.objects[camera] - - #Set Resolution - scn.render.resolution_x = int(renderSettings["Width"]) - scn.render.resolution_y = int(renderSettings["Height"]) - scn.render.resolution_percentage = 100 - - #Set Samples - scn.cycles.samples = int(renderSettings["Samples"]) - scn.render.use_persistent_data = True - - #Render Device - renderType = int(renderSettings["ComputeUnit"]) - engine = int(renderSettings["Engine"]) - - if(engine == 2): #Optix - optixGPU = renderType == 1 or renderType == 3 or renderType == 11 or renderType == 12; #CUDA or CUDA_GPU_ONLY - optixCPU = renderType != 3 and renderType != 12; #!CUDA_GPU_ONLY && !OPTIX_GPU_ONLY - if(optixCPU and not optixGPU): - scn.cycles.device = "CPU" - else: - scn.cycles.device = "GPU" - useDevices("OPTIX", optixGPU, optixCPU) - else: #Cycles/Eevee - if renderType == 0: #CPU - scn.cycles.device = "CPU" - print("Use CPU") - elif renderType == 1: #Cuda - useDevices("CUDA", True, True) - scn.cycles.device = "GPU" - print("Use Cuda") - elif renderType == 2: #OpenCL - useDevices("OPENCL", True, True) - scn.cycles.device = "GPU" - print("Use OpenCL") - elif renderType == 3: #Cuda (GPU Only) - useDevices("CUDA", True, False) - scn.cycles.device = 'GPU' - print("Use Cuda (GPU)") - elif renderType == 4: #OpenCL (GPU Only) - useDevices("OPENCL", True, False) - scn.cycles.device = 'GPU' - print("Use OpenCL (GPU)") - elif renderType == 5: #HIP - useDevices("HIP", True, False) - scn.cycles.device = 'GPU' - print("Use HIP") - elif renderType == 6: #HIP (GPU Only) - useDevices("HIP", True, True) - scn.cycles.device = 'GPU' - print("Use HIP (GPU)") - elif renderType == 7: #METAL - useDevices("METAL", True, True) - scn.cycles.device = 'GPU' - print("Use METAL") - elif renderType == 8: #METAL (GPU Only) - useDevices("METAL", True, False) - scn.cycles.device = 'GPU' - print("Use METAL (GPU)") - elif renderType == 9: #ONEAPI - useDevices("ONEAPI", True, True) - scn.cycles.device = 'GPU' - print("Use ONEAPI") - elif renderType == 10: #ONEAPI (GPU Only) - useDevices("ONEAPI", True, False) - scn.cycles.device = 'GPU' - print("Use ONEAPI (GPU)") - elif renderType == 11: #OptiX - useDevices("OPTIX", True, True) - scn.cycles.device = "GPU" - print("Use OptiX") - elif renderType == 12: #OptiX (GPU Only) - useDevices("OPTIX", True, False) - scn.cycles.device = "GPU" - print("Use OptiX (GPU)") - - # At the moment, we should derive to use the file settings instead of asking user to manually adjust. Remove denoiser if possible - #Denoiser - Disable this until I can figure out how to fetch this info from Blend lib - # denoise = renderSettings["Denoiser"] - # if denoise is not None: - # if denoise == "None": - # scn.cycles.use_denoising = False - # elif len(denoise) > 0: - # scn.cycles.use_denoising = True - # scn.cycles.denoiser = denoise - - # Set Frames Per Second - fps = renderSettings["FPS"] - if fps is not None and fps > 0: - scn.render.fps = fps - - # blender uses the new BLENDER_EEVEE_NEXT enum for blender4.2 and above. - if(engine == 1): #Eevee - if(isPreEeveeNext): - print("Using EEVEE") - scn.render.engine = "BLENDER_EEVEE" - else: - print("Using EEVEE_NEXT") - scn.render.engine = "BLENDER_EEVEE_NEXT" - else: - scn.render.engine = "CYCLES" - - # Set frame - scn.frame_set(frame) - - # Set Output - scn.render.filepath = renderSettings["Output"] + '/' + str(frame).zfill(5) - id = str(renderSettings["TaskID"]) - - # Render - print("RENDER_START: " + id + "\n", flush=True) - # TODO: Research what use_viewport does? - bpy.ops.render.render(animation=False, write_still=True, use_viewport=False, layer="", scene = scen) - print("SUCCESS: " + id + "\n", flush=True) - -def runBatch(): - # Fatal exception was thrown [Errno 61] Connection refused - see if it's the firewall? - proxy = xmlrpc.client.ServerProxy("http://localhost:8081") - - # Do i need to send in RPC like this or can it just be a value instead? - renderSettings = None - try: - renderSettings = proxy.fetch_info(1) - except Exception as e: - print("Fail to call fetch_info over xml_rpc: " + str(e)) - return - - # Loop over batches - while True: - try: - frame = proxy.next_render_queue(1) - renderWithSettings(renderSettings, frame) - except Exception as e: - print(e) - break - - print("BATCH_COMPLETE\n") - -#Main - -try: - runBatch() - -except Exception as e: - print("EXCEPTION:" + str(e)) \ No newline at end of file diff --git a/blender_rs/.vscode/launch.json b/blender_rs/.vscode/launch.json new file mode 100644 index 00000000..9f826181 --- /dev/null +++ b/blender_rs/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(lldb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/target/debug/examples/manager", + "args": [ + "exact-download", + "4.2.4" + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "lldb" + } + ] +} \ No newline at end of file diff --git a/blender/Cargo.toml b/blender_rs/Cargo.toml similarity index 51% rename from blender/Cargo.toml rename to blender_rs/Cargo.toml index 8c32e74f..2533e346 100644 --- a/blender/Cargo.toml +++ b/blender_rs/Cargo.toml @@ -10,28 +10,25 @@ edition = "2021" [dependencies] dirs = "6.0.0" regex = "^1.11.1" -semver = { version = "^1.0.25", features = ["serde"] } -serde = { version = "^1.0.216", features = ["derive"] } -serde_json = "^1.0.138" +lazy-regex = "^3.6" +semver = { version = "^1.0", features = ["serde"] } +serde = { version = "^1.0", features = ["derive"] } +serde_json = "^1.0" url = { version = "^2.5.4", features = ["serde"] } -thiserror = "^2.0.11" -uuid = { version = "^1.13.1", features = ["serde", "v4"] } -ureq = { version = "^3.0.5" } +thiserror = "^2.0" +uuid = { version = "^1.21", features = ["serde", "v4"] } +ureq = { version = "^3.0" } blend = "0.8.0" -tokio = { version = "1.42.0", features = ["full"] } -# hack to get updated patches - og inactive for 6 years -xml-rpc = { git = "https://github.com/tiberiumboy/xml-rpc-rs.git" } +tokio = { version = "^1.49", features = ["full"] } +# TODO: Clap is only ever used in examples. Do not include clap for release mode build. Add a feature switch to include examples. +clap = { version = "4.6.0", features = ["derive"] } [target.'cfg(target_os = "windows")'.dependencies] -zip = "^2.2.2" +zip = "^8.1" [target.'cfg(target_os = "macos")'.dependencies] dmg = { version = "^0.1" } [target.'cfg(target_os = "linux")'.dependencies] xz = { version = "^0.1" } -tar = { version = "^0.4.43" } - - -# [features] -# manager = ["ureq", "xz", "tar", "dmg"] +tar = { version = "^0.4" } diff --git a/blender/README.md b/blender_rs/README.md similarity index 80% rename from blender/README.md rename to blender_rs/README.md index 10d5cec2..5961ccae 100644 --- a/blender/README.md +++ b/blender_rs/README.md @@ -9,12 +9,12 @@ This example demonstrate downloading a copy of blender from the blender foundati Run ```bash -cargo run --example download [install_path] +cargo run --example manager exact-download # e.g. -cargo run --example download 4.1.0 +cargo run --example manager exact-download 4.1.0 ``` -For more info, please read [here](./examples/download/README.md). +For more info, please read [here](./examples/manager/README.md). ### Render This example will first check if you have blender installed, if not, it will ask you to run above examples. \ No newline at end of file diff --git a/blender/examples/assets/test.blend b/blender_rs/examples/assets/test.blend similarity index 100% rename from blender/examples/assets/test.blend rename to blender_rs/examples/assets/test.blend diff --git a/blender_rs/examples/manager/README.md b/blender_rs/examples/manager/README.md new file mode 100644 index 00000000..9e48b6b1 --- /dev/null +++ b/blender_rs/examples/manager/README.md @@ -0,0 +1,27 @@ +# Manager example +This example will demonstrate basic cli interface to the manager struct. The manager class requires a path to store configuration file, and load persistent storage. By default it will create one in your application config directory, under the subfolder "BlendFarm". This location will contain a config file, page cache, and render cache. +blender with the version passed into arguments and returns the path to blender executables, unpacked. + +## Test it! +To run this example, simply run: +```bash +# to list installed blenders +cargo run --example manager + +# or update manager with provided installation. +cargo run --example manager add ~/Downloads/Blender-5.0/blender +``` + +# Download blender example +This example will download blender with the version passed into arguments and returns the path to blender executables, unpacked, and ready to be use! + +## Test it! +To run this example, simply run: +```bash +cargo run --example manager exact-version + +// For example, if I want to download Blender 4.1.0 +cargo run --example manager exact-download 4.1.0 +[Success] Blender 4.1.0 installed at "~/Downloads/Blender/Blender4.1/blender-4.1.0-macos-arm64/Blender.app/Contents/MacOS/Blender" +``` +The output result will show you where Blender struct is referencing the executable path that is used to pass to argument commands. \ No newline at end of file diff --git a/blender_rs/examples/manager/main.rs b/blender_rs/examples/manager/main.rs new file mode 100644 index 00000000..2454f66a --- /dev/null +++ b/blender_rs/examples/manager/main.rs @@ -0,0 +1,110 @@ +// here we'll provide basic cli interface controls to list, edit, add, or remove blender installations history. +// Below the surface should follow simple implementations similar to REST api. + +// todo, load the config file here. + +use std::path::PathBuf; +// TODO: I only want to use clap for examples, but not include with the whole library itself. +use clap::{Parser, Subcommand}; + +use blender::{blender::Blender, manager::Manager, models::blender_config::BlenderConfig}; +use semver::Version; +// use semver::Version; + +#[derive(Subcommand, Debug)] +enum Command { + Add { path: PathBuf }, + ExactDownload { version: Version }, + Download { major: u64, minor: u64 }, // minor can accept 0 as default (Wildcard to use latest) + // Disconnect { target: Version }, + // Delete { target: Version}, +} + +/// The manager cli is a great way to interface the persistent manager state for BlendFarm services. +/// This manager responsibility is to fetch and download (Portal), unpack and install (Config) Blenders installation. +/// It stores a collection of executable path to blender, and holds the version as unique key identifier. +/// The manager only cares about single instance version of blender that is uniquely bound to the version the software was compiled in. +/// +/// Caller can invoke built-in commands to update the persistent storage to include locally installed blender +/// to the list of available blender installations for BlendFarm to use from. +/// You can also run commands to download and install specific or latest blender versions available online. +/// ``` +/// cargo run # Returns list of known configurable blender installation path. +/// +/// cargo run -- add "./path/to/blender" # Verify executable and append to Manager's collection of installations. +/// +/// cargo run -- exact-download "4.2.4" +/// ``` +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + /// Path to load custom config location, otherwise load from default location (~/.config/BlendFarm/BlenderManager.json) + config: Option, + /// Subcommand to invoke cli utility mode + #[command(subcommand)] + command: Option, +} + +#[inline] +fn handle_download_blender(manager: &mut Manager, version: &Version) { + match manager.fetch_blender(&version) { + Ok(blender) => println!( + "[Success] Blender {} installed at {:?}", + blender.get_version(), + blender.get_executable() + ), + Err(e) => eprintln!("[Fail] Unable to fetch blender {}: {:?}", &version, e), + } + // you should at least save the record if it has been modified. Otherwise all record changes will not be saved away. + if let Err(e) = manager.save() { + eprintln!("Unable to update persistent data! Changes made will be lost! {e:?}"); + } +} + +fn main() { + // retrieve the sub command the user wants to invoke + // let args: Vec = std::env::args().collect::>(); + let args = Args::parse(); + + let config_path = match args.config { + Some(path) => path, + None => BlenderConfig::get_default_config_path(), + }; + + let mut manager = Manager::load_from_path(config_path).expect(&format!( + "Unable to launch manager, must have valid config!" + )); + + // find a way to accept "add" "edit" "delete" blender collection. Modify and save the list verbosely. + match args.command { + Some(action) => match action { + Command::Add { path } => { + let blender = Blender::from_executable(path) + .expect("Path must point to valid blender executable location!"); + if let Err(e) = manager.add_blender(&blender) { + eprintln!("Fail to add blender! {e:?}"); + } + if let Err(e) = manager.save() { + eprintln!("Unable to update existing config file! {e:?}"); + } + } + Command::ExactDownload { version } => { + handle_download_blender(&mut manager, &version); + } + // Download exact version from the internet. + Command::Download { major, minor } => { + // the secret trick is to use patch 0 to use the latest version available. + let version = Version::new(major, minor, 0); + handle_download_blender(&mut manager, &version); + // Here we will try and download blender from the internet. + } // Command::Disconnect { target } => { + // todo!("We'll come back to this one... This one a bit weird and odd..."); + // }, + // Command::Delete { target } => todo!(), + }, + None => manager + .get_blenders() + .iter() + .for_each(|v| println!("{v:?}")), + } +} diff --git a/blender/examples/peek/main.rs b/blender_rs/examples/peek/main.rs similarity index 60% rename from blender/examples/peek/main.rs rename to blender_rs/examples/peek/main.rs index b64cebcf..52e8e277 100644 --- a/blender/examples/peek/main.rs +++ b/blender_rs/examples/peek/main.rs @@ -1,4 +1,4 @@ -use blender::blender::Blender; +use blender::blend_file::BlendFile; use std::path::PathBuf; /// Peek into the blend file to see what's inside. @@ -6,13 +6,13 @@ use std::path::PathBuf; async fn main() { let args = std::env::args().collect::>(); let blend_path = match args.get(1) { + // Note this would only work if you ran the example from /blender_rs directory None => PathBuf::from("./examples/assets/test.blend"), Some(p) => PathBuf::from(p), }; - // we reference blender by executable path. Version will be detected upon running command process. (Self validation) - match Blender::peek(&blend_path).await { - Ok(result) => println!("{:?}", &result), + match BlendFile::new(&blend_path) { + Ok(result) => println!("{:?}", &result.peek_response(None)), Err(e) => println!("Error: {:?}", e), } } diff --git a/blender/examples/render/README.md b/blender_rs/examples/render/README.md similarity index 100% rename from blender/examples/render/README.md rename to blender_rs/examples/render/README.md diff --git a/blender_rs/examples/render/main.rs b/blender_rs/examples/render/main.rs new file mode 100644 index 00000000..3b926297 --- /dev/null +++ b/blender_rs/examples/render/main.rs @@ -0,0 +1,84 @@ +use blender::blend_file::BlendFile; +use blender::blender::Manager; +use blender::models::blender_config::BlenderConfig; +use blender::models::{args::Args, event::BlenderEvent}; +use semver::Version; +use std::fs; +use std::path::PathBuf; + +async fn render_with_manager() { + let args = std::env::args().collect::>(); + let blend_path = match args.get(1) { + // FIXME: Path is relative to where command is invoked. Must be from blender_rs directory, otherwise path will fail. + None => PathBuf::from("./examples/assets/test.blend"), + Some(p) => PathBuf::from(p), + }; + + // loads blender file and retrieve some information to display for job queue. + let blend_file = BlendFile::new(&blend_path).expect("Expects a valid blend file to continue!"); + + // Get latest blender installed, or install latest blender from web. + let mut manager = + Manager::load_from_path(BlenderConfig::get_default_config_path()).expect("Must be able to launch manager to get blender"); + + // Retrieve last blender version opened/used. Only contains major and minor, no patch. Rely on latest patch if possible. + let (max, min) = blend_file.get_partial_version(); + + // Minimum version required to run this blender file + let version = Version::new(max as u64, min as u64, 0); + + // Fetch latest local version that meets the requirement version. We will not try to install, + // so we will stop here and ask the user to load blender into configuration initially. + let blender = manager + .latest_local_avail(&version) + .expect("No local blender installation found! Must have at least one blender installed!"); + println!("Prepare blender configuration..."); + + // Here we ask for the output path, for now we set our path in the same directory as our executable path. + // This information will be display after render has been completed successfully. + // TODO: BUG! This will save to root of C:/ on windows platform! Need to change this to current working dir + let output = fs::canonicalize( PathBuf::from("./examples/assets/")).expect("Must be able to collapse to absolute path!"); + + // Create blender argument + let args = Args::new(blend_file, output, 2, 10); + + // render the frame. Completed render will return the path of the rendered frame, error indicates failure to render due to blender incompatible hardware settings or configurations. (CPU vs GPU / Metal vs OpenGL) + let listener = blender + .render(args) + .await + .expect("Should not have any issue?"); + + // Handle blender status + while let Ok(status) = listener.recv() { + match status { + BlenderEvent::Completed { frame, result } => { + println!("[Completed] {frame} {result:?}"); + } + BlenderEvent::Rendering { current, total } => { + let percent = (current / total) * 100.0; + println!("[Rendering] {current} out of {total} (%{percent})"); + } + BlenderEvent::Error(e) => { + println!("[ERR] {e}"); + } + BlenderEvent::Warning(msg) => { + println!("[WARN] {msg}"); + } + BlenderEvent::Log(msg) => { + println!("[LOG] {msg}") + } + BlenderEvent::Exit => { + println!("[Exit]"); + break; + } + _ => { + println!("Unhandled blender status! {:?}", status); + } + } + } +} + +#[tokio::main] +async fn main() { + render_with_manager().await; +} diff --git a/blender_rs/src/blend_file.rs b/blender_rs/src/blend_file.rs new file mode 100644 index 00000000..06453ea9 --- /dev/null +++ b/blender_rs/src/blend_file.rs @@ -0,0 +1,224 @@ +use std::{ + fs, + num::ParseIntError, + path::{Path, PathBuf}, +}; + +use blend::Blend; +use semver::Version; +use serde::{Deserialize, Serialize}; + +use crate::{ + blender::{BlenderError, Frame}, + models::{ + blender_scene::{BlenderScene, Camera, Sample, SceneName}, config::BlenderConfiguration, /*engine::Engine, */ format::Format, peek_response::PeekResponse, render_setting::{FrameRate, RenderSetting}, window::Window + }, + utils::get_config_path, +}; + +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] +pub struct SceneInfo { + pub scenes: Vec, + pub cameras: Vec, + pub frame_start: Frame, + pub frame_end: Frame, + render_width: i32, + render_height: i32, + fps: FrameRate, + sample: Sample, + output: PathBuf, + // engine: Engine, +} + +impl SceneInfo { + pub fn selected_camera(&self) -> String { + self.cameras.get(0).unwrap_or(&"".to_owned()).to_owned() + } + + pub fn selected_scene(&self) -> String { + self.scenes.get(0).unwrap_or(&"".to_owned()).to_owned() + } + + pub(crate) fn process(mut self, blend: &Blend) -> Result { + // this denotes how many scene objects there are. + for obj in blend.instances_with_code(*b"SC") { + let scene = obj.get("id").get_string("name").replace("SC", ""); // not the correct name usage? + let render = &obj.get("r"); // get render data + + // do need to make sure that the engine is correctly set? + // self.engine = match render.get_string("engine") { + // x if x.contains("NEXT") => Engine::BLENDER_EEVEE_NEXT, + // x if x.contains("EEVEE") => Engine::BLENDER_EEVEE, + // x if x.contains("OPTIX") => Engine::OPTIX, + // _ => Engine::CYCLES, + // }; + + self.sample = obj.get("eevee").get_i32("taa_render_samples"); + + // Issue, Cannot find cycles info! Blender show that it should be here under SCscene, just like eevee, but I'm looking it over and over and it's not there? Where is cycle? + // Use this for development only! + // Self::explore_value(&obj.get("eevee")); + + self.render_width = render.get_i32("xsch"); + self.render_height = render.get_i32("ysch"); + self.frame_start = render.get_i32("sfra"); + self.frame_end = render.get_i32("efra"); + self.fps = render.get_u16("frs_sec"); + self.output = render + .get_string("pic") + .parse::() + .map_err(|e| BlenderError::PythonError(e.to_string()))?; + + self.scenes.push(scene); + } + + // interesting - I'm picking up the wrong camera here? + for obj in blend.instances_with_code(*b"CA") { + let camera = obj.get("id").get_string("name").replace("CA", ""); + self.cameras.push(camera); + } + + Ok(self) + } + + pub fn render_setting(self) -> RenderSetting { + RenderSetting::new( + self.output, + self.render_width, + self.render_height, + self.sample, + self.fps, + // self.engine, + Format::default(), + Window::default(), + ) + } + + pub(crate) fn peek_response(&self, version: &Version) -> PeekResponse { + let selected_scene = self.selected_scene(); + let selected_camera = self.selected_camera(); + + let render_setting: RenderSetting = self.clone().render_setting(); + let current = BlenderScene::new(selected_scene, selected_camera, render_setting); + + PeekResponse::new( + version.clone(), + self.frame_start, + self.frame_end, + self.cameras.clone(), + self.scenes.clone(), + current, + ) + } +} + +// A struct to hold valid blend file with compatible partial version. +// we can extract additional data if we need to? +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BlendFile { + inner: PathBuf, + major: u16, + minor: u16, + scene_info: SceneInfo, + render_setting: RenderSetting, +} + +impl BlendFile { + pub fn new(path_to_blend_file: impl AsRef) -> Result { + let blend = Blend::from_path(&path_to_blend_file) + // BUG: *BlendParseError contains different traits that's preventing me using anyhow error traits implementation. + .map_err(|e| { + BlenderError::InvalidFile(format!("Received BlenderParseError! {e:?}").to_owned()) + })?; + + // blender version are display as three digits number, e.g. 404 is major: 4, minor: 4. + // treat this as a u16 major = u16 / 100, minor = u16 % 100; + let str_version = std::str::from_utf8(&blend.blend.header.version) + .map_err(|e| BlenderError::InvalidFile(e.to_string()))?; + + let value: u16 = str_version + .parse() + .map_err(|e: ParseIntError| BlenderError::InvalidFile(e.to_string()))?; + let major = value / 100; + let minor = value % 100; + + let scene_info = SceneInfo::default().process(&blend)?; + let render_setting = scene_info.clone().render_setting(); + + Ok(BlendFile { + inner: path_to_blend_file.as_ref().to_path_buf(), + major, + minor, + render_setting, + scene_info, + }) + } + + pub fn setup_args(&self, settings: &BlenderConfiguration) -> Result, BlenderError> { + let script_path = get_config_path().join("render.py"); + if !script_path.exists() { + let data = include_bytes!("./render.py"); + fs::write(&script_path, data).map_err(|e| BlenderError::PythonError(e.to_string()))?; + } + + let path = self.to_path().as_os_str().to_os_string(); + // provide the configuration in json format + let content = serde_json::to_string(settings).map_err(|e|BlenderError::InvalidFile(e.to_string()))?; + + Ok(vec![ + "--factory-startup".to_owned(), + "-noaudio".into(), + "-b".into(), + fs::canonicalize(path).unwrap().to_str().unwrap_or_default().to_owned(), + "-P".into(), + script_path.to_str().unwrap().into(), + "--".into(), + "-c".into(), + // does this handle escaped characters? + content, + ]) + } + + pub fn get_partial_version(&self) -> (u16, u16) { + (self.major, self.minor) + } + + pub fn peek_response(&self, version: Option<&Version>) -> PeekResponse { + let last_version = match version { + Some(v) => v, + None => &Version::new(self.major.into(), self.minor.into(), 0), + }; + self.scene_info.peek_response(last_version) + } + + pub fn to_path(&self) -> &Path { + self.inner.as_path() + } +} + +impl Into for BlendFile { + fn into(self) -> PathBuf { + self.inner + } +} + +impl Into for BlendFile { + fn into(self) -> RenderSetting { + self.render_setting + } +} + +impl Into for BlendFile { + fn into(self) -> SceneInfo { + self.scene_info + } +} + +#[cfg(test)] +mod tests { + // use crate::blend_file::BlendFile; + + // fn mock_blendfile() -> BlendFile { + // let blend_file = BlendFile::new(path_to_blend_file) + // } +} diff --git a/blender_rs/src/blender.rs b/blender_rs/src/blender.rs new file mode 100644 index 00000000..c418c2de --- /dev/null +++ b/blender_rs/src/blender.rs @@ -0,0 +1,554 @@ +#![cfg(not(doctest))] +/* +Developer blog: + +Spending time on replacing xml-rpc-rs due to maintainers not willing to replace rouille plugin that supports this implementations. +I would instead incorporate the functionality of XML-RPC protocol myself instead of relying third party packages. +Reading the wikipedia - https://en.wikipedia.org/wiki/XML-RPC#Usage - xml-rpc is done via simple http server. + +Currently, there is no error handling situation from blender side of things. If blender crash, we will resume the rest of the code in attempt to parse the data. + This will eventually lead to a program crash because we couldn't parse the information we expect from stdout. + TODO: How can I stream this data better? + +- As of Blender 4.2 - they introduced BLENDER_EEVEE_NEXT as a replacement to BLENDER_EEVEE. + Will need to make sure I pass in the correct enum for version 4.2 and above. + +- Spoke to Sheepit - another "Intranet" distribution render service (Closed source) + - In order to get Render preview window, there needs to be a GPU context to attach to. + Otherwise, we'll have to wait for the render to complete the process before sending the image back to the user. + - They mention to enforce compute methods, do not mix cpu and gpu. (Why?) + +Advantage: +- can support M-series ARM processor. +- Original tool Doesn't composite video for you - We can make ffmpeg wrapper? - This will be a feature but not in this level of implementation. +- LogicReinc uses JSON to load batch file - difficult to adjust frame(s) after job sent. + I'm creating an IPC between this program and python to ask next frame. To improve actions over blender. + +Disadvantage: +- Currently rely on python script to do custom render within blender. + No interops/additional cli commands other than interops through bpy (blender python) package + Instead of using JSON to send configuration to python/blender, we're using IPC to control next frame/batch to render(s). + Currently using Command::Process to invoke commands to blender. Would like to see if there's public API or .dll to interface into. + +Challenges: + Blender support tileX/Y, but gluing the image together is a new challenge - a 64K 24bits image would consume about 3Gb, and size exponentially grow from there. + Have a look into NIP2 to stitch large images together - https://github.com/libvips/nip2 + TODO: Find a way to glue image async by image to image, buffer to buffer, flush out each image before loading new image and hold nothing in memory, store it all on disk instead. + +WARN: + From LogicReinc FAQ's: + Q: Render fails due to Gdip + A: You're running Linux or Mac but did not install libgdiplus and libc6-dev, + install these and you should be good. + + Q:Render fails on Linux + A:You may not have the required blender system dependencies. Easiest way to cover them all is to just run `apt-get install blender` to fetch them all. + (It does not have to be an up2date blender package, its just for dependencies) + +TODO: + Q: My Blendfile requires special addons to be active while rendering, can I add these? + A: Blendfarm has its own versions of Blender in the BlenderData directory, and it runs + these versions always in factory startup, thus without any added addons. This is done + on purpose to make sure the environment is not altered. Most addons don't have to be + active during rendering as they generate geometry etc. If you really need this, make + an issue and I see what I can do. However do realise that this may make the workflow + less smooth. (As you may need to set up these plugins for every Blender version instead + of just letting BlendFarm do all the work. + */ + +pub use crate::manager::{Manager, ManagerError}; +pub use crate::models::args::Args; +use crate::models::event::BlenderEvent; + +#[cfg(test)] +use blend::Instance; +use lazy_regex::regex_captures; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::num::ParseIntError; +use std::process::{Command, Stdio}; +use std::{ + io::{BufRead, BufReader}, + path::{Path, PathBuf}, + sync::mpsc::{self, Receiver, Sender}, +}; +use thiserror::Error; +use tokio::spawn; +use tokio::task::JoinHandle; + +pub type Frame = i32; + +#[derive(Debug, Error)] +pub enum BlenderError { + #[error("Unable to call blender!")] + ExecutableInvalid, + #[error("Path to executable not found! {0}")] + ExecutableNotFound(PathBuf), + #[error("Invalid file path! {0}")] + InvalidFile(String), + #[error("Unable to render! Error: {0}")] + RenderError(String), + #[error("Unable to launch blender! Received Python errors: {0}")] + PythonError(String), + #[error("Unable to fetch info from blender home service! Are you connected to the internet and is blender foundation still around?")] + ServiceOffline, + #[error("Unable to parse ints from stream! {0}")] + ParseInt(#[from] ParseIntError), +} + +// [Note] In the sense of PartialOrd, Ord - Blender's executable would not matter if the version is identical. +/// Blender structure to hold path to executable and version of blender installed. +/// Pretend this is the wrapper to interface with the actual blender program. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Blender { + /// Path to blender executable on the system. + executable: PathBuf, + /// Version of blender installed on the system. + version: Version, +} + +// Overload to omit path ordering. Order by Version instead. +impl PartialOrd for Blender { + fn ge(&self, other: &Self) -> bool { + self.version.ge(&other.version) + } + + fn partial_cmp(&self, other: &Self) -> Option { + self.version.partial_cmp(&other.version) + } +} + +// Overload to omit path ordering. Order by Version instead. +impl Ord for Blender { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.version.cmp(&other.version) + } +} + +impl Blender { + /* Private method impl */ + + /// Create a new blender struct with provided path and version. This does not checked and enforced! + /// + /// # Examples + /// ``` + /// use blender::Blender; + /// let blender = Blender::new(PathBuf::from("path/to/blender"), Version::new(4,1,0)); + /// ``` + pub(crate) fn new(executable: PathBuf, version: Version) -> Self { + Self { + executable, + version, + } + } + + #[inline] + fn handle_parse(names: &str) -> Result { + names.parse().map_err(BlenderError::ParseInt) + } + + /// Obtain the version by invoking version command to blender directly. + /// This function will invoke the -v command to retrieve blender version information. + /// This validate two things, + /// 1: Blender's internal version is reliable + /// 2: Executable is functional and operational + /// Otherwise, return an error that we were unable to verify this custom blender integrity. + /// + /// # Errors + /// * InvalidData - executable path do not exist or is invalid. Please verify that the path provided exist and not compressed. + /// This error also serves where the executable is unable to provide the blender version. + fn check_version(executable_path: impl AsRef) -> Result { + let exec_path = executable_path.as_ref(); + let output = Command::new(exec_path).arg("-v").output().map_err(|e| { + eprintln!("Received output error(s)? {e:?}"); + BlenderError::ExecutableInvalid + })?; + let stdout = String::from_utf8(output.stdout).unwrap(); + match regex_captures!( + r"Blender (?[0-9]).(?[0-9]).(?[0-9])", + &stdout + ) { + Some((_, major, minor, patch)) => { + let maj = Self::handle_parse(major)?; + let min = Self::handle_parse(minor)?; + let pat = Self::handle_parse(patch)?; + let version = Version::new(maj, min, pat); + let blender = Self::new(exec_path.to_path_buf(), version); + Ok(blender) + } + None => { + eprintln!("Found no regex matches! {stdout:?}"); + Err(BlenderError::ExecutableInvalid) + } + } + } + + // the difference between this function and getting executable are + // a) MacOs is special. Executable reference a path inside app bundle. + // b) This returns valid dir location to open to for user to look at from file POV + // TODO: Remove all of this unwrap nightmare. + pub fn get_relative_path(&self) -> &Path { + if cfg!(target_os = "macos") { + &self + .executable + .parent() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap() + } else { + &self.executable.parent().unwrap() + } + } + + /// Return the executable path to blender (Entry point for CLI) + pub fn get_executable(&self) -> &Path { + &self.executable + } + + /// Return validated Blender Version + pub fn get_version(&self) -> &Version { + &self.version + } + + /// Create a new blender struct from executable path. This function will fetch the version of blender by invoking -v command. + /// Otherwise, if Blender is not install, or a version is not found, an error will throw + /// + /// # Error + /// + /// * InvalidData - executable path do not exist, or is invalid. Please verify that the executable path is correct and leads to the actual executable. + /// * + /// # Examples + /// ``` + /// use blender::Blender; + /// let blender = Blender::from_executable(Pathbuf::from("../examples/")).unwrap(); + /// ``` + pub fn from_executable(executable: impl AsRef) -> Result { + #[cfg(target_os="macos")] + use crate::utils::MACOS_PATH; + + // check and verify that the executable exist. + // first line for validating blender executable. + let path = executable.as_ref(); + + // macOS is special. To invoke the blender application, I need to navigate inside Blender.app, which is an app bundle that contains stuff to run blender. + // Command::Process needs to access the content inside app bundle to perform the operation correctly. + // To do this - I need to append additional path args to correctly invoke the right application for this to work. + #[cfg(target_os = "macos")] + let path = if !&path.ends_with(MACOS_PATH) { + &path.join(MACOS_PATH) + } else { + path + }; + + // this should be clear and explicit that I must have a valid path? + if !path.exists() { + return Err(BlenderError::ExecutableNotFound(path.to_path_buf())); + } + + let blender = Self::check_version(path)?; + Ok(blender) + } + + // this is used to read and see blend file friendly view mode + #[cfg(test)] + #[allow(dead_code)] + fn explore_value<'a>(obj: &Instance<'a>) { + for i in &obj.fields { + match i.1.is_primitive { + true => { + match i.1.info { + blend::parsers::field::FieldInfo::Value => { + match i.1.type_name.as_str() { + "int" => { + println!("{}: {} = {} ", i.0, i.1.type_name, &obj.get_i32(i.0)); + } + "short" => { + println!("{}: {} = {} ", i.0, i.1.type_name, &obj.get_u16(i.0)); + } + "char" => { + println!( + "{}: {} = {} ", + i.0, + i.1.type_name, + &obj.get_string(i.0) + ); + } + "float" => { + println!("{}: {} = {}", i.0, i.1.type_name, &obj.get_f32(i.0)); + } + "uint64_t" => { + println!("{}: {} = {}", i.0, i.1.type_name, &obj.get_u64(i.0)); + } + _ => println!("Unhandle value for {} | {}", i.1.type_name, i.0), + }; + } + blend::parsers::field::FieldInfo::ValueArray { .. } => { + match i.1.type_name.as_str() { + "char" => { + println!("{}: String = {}", i.0, &obj.get_string(i.0)); + } + "float" => { + println!("{}: vec = {:?}", i.0, &obj.get_f32_vec(i.0)); + } + _ => { + println!("Unhandle Value Array for {} | {}", i.1.type_name, i.0) + } + } + } + // blend::parsers::field::FieldInfo::PointerArray { .. } => todo!(), + // blend::parsers::field::FieldInfo::Pointer { indirection_count } => todo!(), + // blend::parsers::field::FieldInfo::FnPointer => todo!(), + _ => { + println!("Unhandle: {} | {} ", i.0, i.1.type_name) + } + } + } + false => { + println!("{}: TYPE = {}", i.0, i.1.type_name); + } + } + } + } + + /// Render one frame - can we make the assumption that ProjectFile may have configuration predefined Or is that just a system global setting to apply on? + /// # Examples + /// ``` + /// use blender::Blender; + /// use blender::args::Args; + /// let blender = Blender::from_executable("path/to/blender").unwrap(); + /// let args = Args::new(PathBuf::from("path/to/project.blend"), PathBuf::from("path/to/output.png")); + /// let final_output = blender.render(&args).unwrap(); + /// ``` + // so instead of just returning the string of render result or blender error, we'll simply use the single producer to produce result from this class. + // issue here is that we need to lock thread. If we are rendering, we need to be able to call abort. + pub async fn render(&self, args: Args) -> Result, BlenderError> { + // I'm not even sure why we have two mpsc here for setup_listening_blender to use? + let (signal, listener) = mpsc::channel::(); + + // let settings = args.parse_from(&self.version).to_owned(); + let listening_handle = self.setup_listening_server(listener).await?; + + let (rx, tx) = mpsc::channel::(); + let blender = self.clone(); + + spawn(async move { + if let Err(e) = &blender.setup_listening_blender(&args, rx, signal).await { + // where can we get this log info? + println!("Received blender error from setup listening blender logs {e:?}"); + listening_handle.abort(); + } + }); + + // channel to invoke commands to blender while blender is running. + Ok(tx) + } + + #[inline] + async fn setup_listening_server( + &self, + listener: Receiver, + ) -> Result, BlenderError> { + let handle = spawn(async move { + loop { + // TODO: The logic here doesn't make much sense for this class / program to handle and substitute the state. + // I believe this function was design to stop the listening server if blender was completed or closed unexpected. + // We don't have any other state to control and govern this threaded task. + // if the program shut down or if we've completed the render, then we should stop the server + match listener.recv() { + Ok(BlenderEvent::Exit) => break, + Ok(status) => { + println!("Listener received unconditionally: {status:?}"); + } + Err(_e) => { + // TODO: Find a way to switch on verbosity to print these kind of logs. + // eprintln!("Received Error: {_e:?}"); + break; + } + } + } + }); + + Ok(handle) + } + + // setup xml-rpc listening server for blender's IPC + async fn setup_listening_blender( + &self, + args: &Args, + tx: Sender, // Transmission to Application subscribing to this class logger + signal: Sender, // Used to stop the listening service. + ) -> Result<(), BlenderError> { + // TODO: Eventually in the future update, we can ask for the user's override version instead of blender file's last opened version. + let settings = args.parse_from(None); + let col = &args.file.setup_args(&settings)?; + // TODO: Find a way to remove unwrap() + // TODO: How do I know if the program has successfully exit? what is keeping the stream open? + let stdout = Command::new(self.get_executable()) + .args(col) + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .stdout + .unwrap(); + + let reader = BufReader::new(stdout); + + // parse stdout for human to read + let mut frame: i32 = 0; + + reader.lines().for_each(|line| match line { + Ok(line) => Self::handle_blender_stdio(line, &mut frame, &tx, &signal), + Err(e) => eprintln!("Received error from Blender Bufreader: {e:?}"), + }); + + Ok(()) + } + + // TODO: This function updates a value above this scope -> See if we can just return the value instead? + // TODO: Can we use stream instead? how can we parse data from blender into recognizable style? + fn handle_blender_stdio( + line: String, + frame: &mut i32, + tx: &Sender, // Transmission to Application subscribing events produce by this struct + signal: &Sender, // Signal for this class to listen and act upon. + ) { + match line { + // TODO: find a more elegant way to parse the string std out and handle invocation action. + line if line.contains("Fra:") => { + let col = line.split('|').collect::>(); + + // this seems a bit expensive? + let init = col[0].split(" ").next(); + if let Some(value) = init { + *frame = value.replace("Fra:", "").parse().unwrap_or(*frame); + } + let last = col.last().unwrap().trim(); + let slice = last.split(' ').collect::>(); + let msg = match slice[0] { + "Rendering" => { + let current = slice[1].parse::().unwrap(); + let total = slice[3].parse::().unwrap(); + BlenderEvent::Rendering { current, total } + } + _ => BlenderEvent::Unhandled(line), + }; + tx.send(msg).unwrap(); + } + + line if line.starts_with("Time:") => { + tx.send(BlenderEvent::Log(line)).unwrap(); + } + // Python logs get injected to stdio + line if line.starts_with("SUCCESS:") => { + // somehow I received an error from sending? + tx.send(BlenderEvent::Log(line)).unwrap(); + } + line if line.starts_with("LOG:") => { + tx.send(BlenderEvent::Log(line)).unwrap(); + } + line if line.contains("Use:") => { + tx.send(BlenderEvent::Log(line)).unwrap(); + } + line if line.contains("RENDER_START:") => { + tx.send(BlenderEvent::Log(line)).unwrap(); + } + + // it would be nice if we can somehow make this as a struct or enum of types? + line if line.contains("Saved:") => { + // TODO: Test this for OSX compatibility + let location = line.split('\'').collect::>(); + let result = PathBuf::from(location[1]); + tx.send(BlenderEvent::Completed { + frame: *frame, + result, + }) + .unwrap(); + } + + // Strange how this was thrown, but doesn't report back to this program? + // [ERR] Error: Engine 'BLENDER_EEVEE_NEXT' not available for scene 'Scene' (an add-on may need to be installed or enabled) + line if line.starts_with("EXCEPTION:") => { + // Why would this crash? + if let Err(e) = signal.send(BlenderEvent::Exit) { + println!("Fail to send error! {e:?}\n{line}"); + } + if let Err(e) = tx.send(BlenderEvent::Error(line.to_owned())) { + println!("Fail to send error! {e:?}\n{line}"); + } + } + + line if line.starts_with("COMPLETED") => { + signal.send(BlenderEvent::Exit).unwrap(); + tx.send(BlenderEvent::Exit).unwrap(); + } + + // When launch blender for the first time, it prints out the version number and the hash information about the build) + line if line.starts_with("Blender ") => { + // if the line reads "Blender quit", we should send BlenderEvent::Exit signal + if line.eq_ignore_ascii_case("blender quit") { + tx.send(BlenderEvent::Exit).unwrap(); + // Here we need to stop the runner? + } else { + tx.send(BlenderEvent::Log(line)).unwrap(); + } + } + + // Blender prints out reading blender files, here we'll just log the info anyway (We already have the information) + line if line.starts_with("Read blend: ") => { + tx.send(BlenderEvent::Log(line)).unwrap(); + } + + line if line.starts_with("regiondata free error") => { + tx.send(BlenderEvent::Warning(line)).unwrap() + } + + line if line.starts_with("Color management: ") => { + tx.send(BlenderEvent::Log(line)).unwrap(); + } + + // TODO: Warning keyword is used multiple of times. Consider removing warning apart and submit remaining content above + line if line.contains("Warning:") => { + tx.send(BlenderEvent::Warning(line.to_owned())).unwrap(); + } + + line if line.contains("Error:") => { + let msg = BlenderEvent::Error(line.to_owned()); + tx.send(msg).unwrap(); + } + + line if line.contains("Blender quit") => { + // ignoring this... + println!("Blender quit! Should we handle something about this here at this point of time?"); + } + + // any unhandle handler is submitted raw in console output here. + line if !line.is_empty() => { + // somehow it was able to pick up the blender version and commit hash value? + let msg = format!("[Unhandle Blender Event]:{line}"); + let event = BlenderEvent::Unhandled(msg); + tx.send(event).unwrap(); + } + _ => { + // Only empty log entry would show up here... + } + }; + } +} + +// TODO: impl unit test for blender specifically. +/* +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn should_run() {} + + #[test] + fn should_render() {} +} +*/ diff --git a/blender_rs/src/constant.rs b/blender_rs/src/constant.rs new file mode 100644 index 00000000..197ef20e --- /dev/null +++ b/blender_rs/src/constant.rs @@ -0,0 +1,3 @@ +pub const MAX_VALID_DAYS: u64 = 30; +pub const MAX_FRAME_CHUNK_SIZE: i32 = 35; +pub const MIN_THRESHOLD_FETCH: usize = 2; diff --git a/blender_rs/src/lib.rs b/blender_rs/src/lib.rs new file mode 100644 index 00000000..52e3a9cd --- /dev/null +++ b/blender_rs/src/lib.rs @@ -0,0 +1,11 @@ +#![crate_type = "lib"] +#![crate_name = "blender"] +#![cfg(not(doctest))] +pub mod blend_file; +pub mod blender; +pub mod constant; +pub mod manager; +pub mod models; +pub mod services; +pub mod page_cache; +mod utils; diff --git a/blender_rs/src/main.rs b/blender_rs/src/main.rs new file mode 100644 index 00000000..092c95a2 --- /dev/null +++ b/blender_rs/src/main.rs @@ -0,0 +1,8 @@ +use std::env::current_dir; + +fn main() { + if let Ok(path) = current_dir() { + let project_path = path.to_string_lossy(); + println!("Please read the example to learn more about Blender crate - ${}/examples/render/README.md", project_path); + } +} diff --git a/blender_rs/src/manager.rs b/blender_rs/src/manager.rs new file mode 100644 index 00000000..97f75871 --- /dev/null +++ b/blender_rs/src/manager.rs @@ -0,0 +1,311 @@ +/* +Developer blog: +This manager class will serve the following purpose: +- Keep track of blender installation on this active machine. +- Prevent downloading of the same blender version if we have one already installed. +- If user fetch for list of installation, verify all path exist before returning the list. +- Implements download and install code + +Story: + Pretend this as a factory. What should a manager do to perform this program execution. + This manager responsibility accounts for holding the list of known blender installation. + If the installation does not exist, we provide customer the ability to install Blender from known location. (Blender.org) + We download, extract, and symbolic link (Feature). + - Updated BlenderCategory to use different method of blender location. + Originally default to use BlenderOrg, but could point to Local (Can request intranet distribution service- Feature)?) + - Manager implements PhantomData to acknowledge modified data. This expose additional function to help ensure user can save the + configuration modification (New blender installation, download new version, cache refresh, etc). Limits API usage once we update phantom state to save or load. + +*/ +use crate::blender::Blender; +use crate::models::blender_config::BlenderConfig; +use crate::page_cache::PageCache; +use crate::services::category; +use crate::services::packages::package::{Package, PackageT}; +use crate::services::portal::Portal; + +use semver::Version; +use std::path::Path; +use std::{fs, path::PathBuf}; +use thiserror::Error; +use url::Url; + +// I would like this to be a feature only crate. blender by itself should be lightweight and interface with the program directly. +// could also implement serde as optionals? +#[derive(Debug, Error)] +pub enum ManagerError { + #[error("Unsupported OS: {0}")] + UnsupportedOS(String), + #[error("Unsupported Archtecture: {0}")] + UnsupportedArch(String), + #[error("Unable to extract content: {0}")] + UnableToExtract(String), + #[error("Unable to fetch download from the source! {0}")] + FetchError(String), + #[error("Cannot find target download link for blender! os: {os} | arch: {arch} | url: {url}")] + DownloadNotFound { + arch: String, + os: String, + url: String, + }, + #[error("Unable to fetch blender! {0}")] + RequestError(String), + #[error("IO Error: {0}")] + IoError(#[from] std::io::Error), + #[error("Serde_Json: {0}")] + SerdeJson(#[from] serde_json::Error), + #[error("Category error: {0}")] + Category(#[from] category::BlenderCategoryError), + #[error("Url ParseError: {0}")] + UrlParseError(String), + #[error("Page cache error: {0}")] + PageCacheError(String), + #[error("Blender error: {source}")] + BlenderError { + #[from] + source: crate::blender::BlenderError, + }, +} + +// TODO: Look into OnceCell andsee how I can utilize lazy implementations? +#[derive(Debug)] +pub struct Manager { + /// Store all known installation of blender directory information + /// Manager's rulebook. Should only be available in this struct scope + config: BlenderConfig, + // Online interface (Download blender, look up version, etc) + portal: Portal, // Todo this will get extracted away, leaving only blender configs. + // page cache + page_cache: PageCache, +} + +// I have a config file, which contains list of local installed blender +// and install path. This Config struct is serialized and st + +// Manager should only govern local installed blenders (Or blenders that was added by users) +impl Manager { + fn new(config: BlenderConfig, portal: Portal, page_cache: PageCache) -> Self { + Manager { + config, + portal, + page_cache, + } + } + + pub fn check_compressed_by_file_name(&self, zip_file_name: &str) -> Option { + self.portal.check_compressed_blender_by_file_name(zip_file_name) + } + + // Initialize Manager + pub fn initialize(config: BlenderConfig) -> Result { + // TODO: figure out what to do with PageCache? Another BlenderConfig entry? + let mut page_cache = PageCache::load().expect("TODO: ?"); + + let portal = Portal::fetch(&config.install_path, &mut page_cache)?; + if let Err(e) = page_cache.save() { + eprintln!("Unable to save the cache configuration! {e:?}"); + } + + Ok(Self { + config, + portal, + page_cache, + }) + } + + /// Load the manager data from the config file. + pub fn load_from_path(config_path: impl AsRef) -> Result { + // if the config file does not exist on the system, create a new one and return a new struct instead. + let config = BlenderConfig::load(config_path).unwrap_or(BlenderConfig::default()); + let download_path = &config.install_path; + + // TODO: we'll load cache services here + // let cache_path = &config.cache_dir; + let mut page_cache = PageCache::load().expect("Had issue loading PageCache!"); + let portal = Portal::fetch(&download_path, &mut page_cache)?; + page_cache.save()?; + Ok(Self::new(config, portal, page_cache)) + } + + // Save the configuration, and restore to Unmodified state + pub fn save(&self) -> Result<(), ManagerError> { + let data = serde_json::to_string(&self.config).map_err(ManagerError::SerdeJson)?; + fs::write(self.config.get_config_path(), data).map_err(ManagerError::IoError) + } + + #[deprecated(note = "Provide me an example where this would be useful?")] + #[allow(dead_code)] + fn set_config(self, config: BlenderConfig) -> Manager { + Self { + config: config, + portal: self.portal, + page_cache: self.page_cache, + } + } + + /// Return a reference to the vector list of all known blender installations + pub fn get_blenders(&self) -> Vec<&Blender> { + self.config.get_blenders() + } + + /// Returns a list of url path to download and version (For UI models) + pub fn get_online_version(&self) -> Vec<(Url, Version)> { + self.portal + .get_downloads() + .iter() + .map(|package| { + match package { + Package::Metadata(download_link) => ( + download_link.download_url.to_owned(), + download_link.get_version().to_owned(), + ), + Package::Downloaded(downloaded) => ( + downloaded.origin.download_url.to_owned(), + downloaded.get_version().to_owned(), + ), + Package::Bundle(bundle) => ( + bundle.content.origin.download_url.to_owned(), + bundle.get_version().to_owned(), + ), // Package::Executable(custom) => , + } + // (package.get_version()) + }) + .collect::>() + } + + // It's used to display the information on the website. + pub fn get_install_path(&self) -> &Path { + &self.config.install_path + } + + /// Set path for blender download and installation + pub fn set_install_path(mut self, new_path: &Path) -> Manager { + // Consider the design behind this. Should we move blender installations to new path? + self.config.install_path = new_path.to_path_buf().clone(); + + Self { + config: self.config, + portal: self.portal, + page_cache: self.page_cache, + } + } + + /// Add a new blender installation to the manager list. + // would require consuming manager. + /// Returns old blender value that was replaced by the new updated value. + pub fn add_blender(&mut self, blender: &Blender) -> Result, ManagerError> { + // make sure it doesn't exist already. + // Returns None if previously doesn't exist, or Some(old_value) when the record has been updated. + Ok(self.config.insert_blender(blender)) + } + + // This is weird and a hack. We should let people try to give us valid blender struct. That's all we care about. + /// Check and add a local installation of blender to manager's registry of blender version to use from. + // #[deprecated( + // note = "Consider asking for valid blender struct. Let the client try to get blender working first" + // )] + // pub fn add_blender_path(&mut self, path: &impl AsRef) -> Result { + // // Here is where we verify the integrity of blender before adding to manager collection. + // let blender = + // Blender::from_executable(path).map_err(|e| ManagerError::BlenderError { source: e })?; + + // if let Some(_old_value) = self.add_blender(&blender)? { + // eprintln!("Record updated"); + // } + + // // TODO: This is a hack - Would prefer to understand why program does not auto save file after closing. + // // Or look into better saving mechanism than this. + + // // let _ = self.save()?; + // Ok(blender) + // } + + /// Remove blender installation from the manager list. + pub fn remove_blender(&mut self, blender: &Blender) -> Result<(), ManagerError> { + let _ = self.config.remove_blender(blender); + Ok(()) + } + + /// Deletes the parent directory that blender reside in. This might be a dangerous function as this involves removing the directory blender executable is in. + /// TODO: verify that this doesn't break macos path executable... Why mac gotta be special with appbundle? + // If this is a dangerous function, we should instead make this private and handle it carefully. + // TODO: Limiting scope visibility until we can make it private. I'm not sure where it's used atm, but making it work atm. 1 hour work + #[allow(dead_code)] + pub(crate) fn delete_blender(&mut self, blender: &Blender) -> Result<(), ManagerError> { + // this deletes blender from the system. You have been warn! + // BEWARE - MacOS is special that the executable path is referencing inside the bundle. I would need to get the app path instead of the bundle inside. + if std::env::consts::OS == "macos" { + panic!( + "Need to handle mac app path reference instead of path inside bundle! {:?}", + blender.get_executable() + ); + } + + // I'm still concern about this, why are we deleting the parent? Need to perform unit test for this to make sure it doesn't delete anything else. + fs::remove_dir_all(blender.get_executable().parent().unwrap()).unwrap(); + self.remove_blender(blender)?; + Ok(()) + } + + /// This will first check if blender is installed locally, otherwise download the version online. + pub fn fetch_blender(&mut self, version: &Version) -> Result { + match self.config.get_blender(version) { + Some(blender) => Ok(blender.clone()), + None => { + let blender = self.portal.download_blender(version)?; + // Expects no history previously stored due to match conditions above. If it breaks, something is seriously wrong. + if let Some(old_value) = self.add_blender(&blender)? { + panic!("Record contain existing record, but filter above assure we didn't have it? {old_value:?}\n{:?}", &blender); + } + Ok(blender) + } + } + } + + pub fn have_blender(&self, version: &Version) -> Option<&Blender> { + self.config.get_blender(version) + } + + pub fn have_blender_partial(&self, major: u64, minor: u64) -> Option<&Blender> { + self.config.get_blender_partial(major, minor) + } + + /// Fetch the latest version available on this local machine + pub fn latest_local_avail(&mut self, version: &Version) -> Option<&Blender> { + // in this case I need to contact Manager class or BlenderDownloadLink somewhere and fetch the latest blender information + // I think the data is already sorted to begin with? No need to resort this list again. + self.config.get_latest_blender_available(version) + } +} + +impl AsRef for Manager { + fn as_ref(&self) -> &PathBuf { + &self.config.install_path + } +} + +#[cfg(test)] +mod tests { + // use super::*; + + #[test] + fn should_pass() { + // let _manager = Manager::load(); + } + /* + fn test_download_blender_home_link() { + let mut manager = Manager::load(); + let link = manager.latest_local_avail(None).or(manager + .download_latest_version() + .map_or(None, |l| Some(l.to_owned()))); + match link { + Some(link) => { + dbg!(link); + } + None => println!("No blender found and unable to connect to internet! Skipping!"), + } + } + */ + + // TODO: Write unit test for Drop if that's possible? +} diff --git a/blender_rs/src/models.rs b/blender_rs/src/models.rs new file mode 100644 index 00000000..573c1712 --- /dev/null +++ b/blender_rs/src/models.rs @@ -0,0 +1,12 @@ +pub mod args; +pub mod blender_config; +pub mod blender_scene; +pub(crate) mod config; +pub mod device; +pub mod engine; +pub mod event; +pub mod format; +pub mod mode; +pub mod peek_response; +pub mod render_setting; +pub mod window; diff --git a/blender_rs/src/models/args.rs b/blender_rs/src/models/args.rs new file mode 100644 index 00000000..d85617a9 --- /dev/null +++ b/blender_rs/src/models/args.rs @@ -0,0 +1,99 @@ +/* + Developer blog + + - Having done extensive research, Blender have two ways to interface to the program + 1. Through CLI + 2. Through Python API via "bpy" library + + Review online for possible solution to interface blender via CAPI, but was strongly suggested to use a python script instead + this limits what I can do in term of functionality, but it'll be a good start. + FEATURE - See if python allows pointers/buffer access to obtain job render progress - Allows node to send host progress result. Possibly viewport network rendering? + + Do note that blender is open source - it's not impossible to create FFI that interfaces blender directly, but rather, there's no support to perform this kind of action (yet). +*/ +// May Subject to change. +use crate::{ + blend_file::BlendFile, blender::Frame, models::{config::BlenderConfiguration, /* engine::Engine, */ format::Format, peek_response::PeekResponse} +}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use super::device::Processor; + +// Blender 4.2 introduce a new enum called BLENDER_EEVEE_NEXT, which is currently handle in python file atm. +// const EEVEE_SWITCH: Version = Version::new(4, 2, 0); + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum HardwareMode { + CPU, + GPU, + BOTH, +} + +// ref: https://docs.blender.org/manual/en/latest/advanced/command_line/render.html +/// Field must be public to offer context to render the scene. Let user mutate however they see fits +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Args { + pub file: BlendFile, // required + pub output: PathBuf, // optional + pub processor: Processor, + pub mode: HardwareMode, // optional + pub format: Format, // optional - default to Png + pub start: Frame, + pub end: Frame, +} + +impl Args { + pub fn new(file: BlendFile, output: PathBuf, start: Frame, end: Frame) -> Self { + Args { + file: file, + output: output, + processor: Processor::NONE, + mode: HardwareMode::CPU, + format: Format::default(), + start, + end + } + } + + /// Args are user provided value - this should not correlate to the machine's hardware (CUDA/OPTIX/GPU usage) + pub fn parse_from(&self, version: Option<&Version>) -> BlenderConfiguration { + let info: PeekResponse = self.file.peek_response(version); + BlenderConfiguration::new( + self.output.clone(), + info.current.clone(), + self.processor.clone(), + self.mode.clone(), + info.current.render_setting.sample, + info.current.render_setting.format, + self.start, + self.end + ) + } +} + + +#[cfg(test)] +mod tests { + // use super::*; + + // TODO: Need to write a unit test to ensure the correct engine is used per blender version. + #[test] + fn blender_test_eevee_engine_enum_switch() { + // let file = + // TODO: How can I mock up a blendfile for unit test? + // reference it from blendfile? + todo!("Because we can't explicitly define the engine enum anymore, we'll have to make a mock file instead. Otherwise this unit test will become meaningless."); + /* + let path_to_blend_file = PathBuf::from("./examples/assets/test.blend"); + // TODO: Create a mock blendfile for unit testing purposes. + let file = BlendFile::new(&path_to_blend_file).expect("Must have a valid blend file!"); + let output = PathBuf::new(); + let args = Args::new(file, output, 1,1 ); + let parsed = args.parse_from(&Version::new(4,1,0)); + assert_ne!(parsed.engine, engine); + let parsed = args.parse_from(&EEVEE_SWITCH); + assert_eq!(parsed.engine, engine); + */ + } +} diff --git a/blender_rs/src/models/blender_config.rs b/blender_rs/src/models/blender_config.rs new file mode 100644 index 00000000..91acbd2a --- /dev/null +++ b/blender_rs/src/models/blender_config.rs @@ -0,0 +1,153 @@ +use crate::blender::Blender; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + fs, + io::Error, + path::{Path, PathBuf}, +}; + +const SETTINGS_DIR: &str = "BlendFarm/"; +const SETTINGS_NAME: &str = "BlenderManager.json"; + +// rename this to manager config somehow? +#[derive(Debug, Serialize, Deserialize)] +pub struct BlenderConfig { + #[serde(skip)] + inner: PathBuf, + + /// List of installed blenders + blenders: HashMap, + + /// Installation path. By default set to `$HOME/Downloads/Blender` + pub install_path: PathBuf, + // cache dir? + // cache_dir: PathBuf, +} + +impl BlenderConfig { + // this path should always be fixed and stored under machine specific. + // this path should not be shared across machines. + #[inline] + pub fn get_default_config_path() -> PathBuf { + // This is stored under the library usage of dirs::config_dir() + "BlendFarm" - the application name by default. + // This ensure directory must exist before returning PathBuf, else report back as permission issue. We must have a place to save the files to. + Self::get_default_config_dir().join(SETTINGS_NAME) + } + + pub fn get_default_config_dir() -> PathBuf { + dirs::config_dir() + .expect("Must have access to config directory for application persistent storage") + .join(SETTINGS_DIR) + } + + pub fn get_config_path(&self) -> &PathBuf { + &self.inner + } + + pub fn load(file_path: impl AsRef) -> Result { + let content = fs::read_to_string(&file_path)?; + let mut config = serde_json::from_str::(&content)?; + config.remove_invalid_blender(); + config.inner = file_path.as_ref().to_path_buf(); + Ok(config) + } + + pub fn get_download_destination(&self, category_folder_name: &str) -> PathBuf { + self.install_path.join(category_folder_name) + } + + // Fetch best matching version of blender if provided, or latest version available if none was provided. + pub fn get_latest_blender_available(&self, version: &Version) -> Option<&Blender> { + self.get_blender(version) + .or_else(|| self.get_blender_partial(version.major, version.minor)) + } + + /// Return matching exact blender version + pub(crate) fn get_blender(&self, version: &Version) -> Option<&Blender> { + self.blenders.values().find(|x| x.get_version().eq(version)) + } + + // return a immutable reference list of installed blender. + // useful to display on website of some sort. + pub(crate) fn get_blenders(&self) -> Vec<&Blender> { + self.blenders + .iter() + .fold(Vec::new(), |mut map, (_, blender)| { + map.push(blender); + map + }) + } + + /// Return a reference to matching partial version, but uses latest patch + /// Major must match, Minor will match if greater than 0. Patch will always be the latest version possible. + pub(crate) fn get_blender_partial(&self, major: u64, minor: u64) -> Option<&Blender> { + self.blenders + .values() + .fold(None, |latest: Option<&Blender>, item| { + let current_version = item.get_version(); + + if current_version.major.ne(&major) { + return latest; + } + + // custom rule: If minor = 0 (default), use latest, otherwise compare all others. + if minor > 0 && current_version.minor.ne(&minor) { + return latest; + } + + if let Some(recent) = latest { + if recent.get_version().ge(current_version) { + return latest; + } + } + + Some(item) + }) + } + + /// Remove any invalid blender path entry from BlenderConfig + pub fn remove_invalid_blender(&mut self) { + self.blenders.retain(|_, v| v.get_executable().exists()); + } + + /// remove target blender + pub fn remove_blender(&mut self, blender: &Blender) -> Option { + self.blenders.remove(blender.get_version()) + } + + /// Append blender entry to database + /// This will create a new record if the key does not exist, or update record, returning old value. + pub fn insert_blender(&mut self, blender: &Blender) -> Option { + // If Some returns, it means we override record. None means no previous record exist and a new entry is added. + self.blenders + .insert(blender.get_version().to_owned(), blender.clone()) + } +} + +impl Default for BlenderConfig { + fn default() -> Self { + let install_path = dirs::download_dir() + .expect("Must have place to download!") + .join(SETTINGS_DIR); + + // ensure path location must exist to save and store to + // - we've been given a place with permission access. + if let Err(e) = fs::create_dir_all(&install_path) { + eprintln!("Unable to create {e:?}"); + } + + Self { + inner: Self::get_default_config_path(), + blenders: Default::default(), + install_path, + } + } +} + +impl Into for BlenderConfig { + fn into(self) -> PathBuf { + self.install_path + } +} diff --git a/blender_rs/src/models/blender_scene.rs b/blender_rs/src/models/blender_scene.rs new file mode 100644 index 00000000..4b842f0a --- /dev/null +++ b/blender_rs/src/models/blender_scene.rs @@ -0,0 +1,30 @@ +use serde::{Deserialize, Serialize}; +use super::render_setting::RenderSetting; + +pub type SceneName = String; +pub type Camera = String; +pub type Sample = i32; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BlenderScene { + /// Name of the scene + pub scene: SceneName, + /// Camera reference name to render from + pub camera: Camera, + /// Render Settings + pub render_setting: RenderSetting, +} + +impl BlenderScene { + pub fn new( + scene: SceneName, + camera: Camera, + render_setting: RenderSetting, + ) -> Self { + Self { + scene, + camera, + render_setting, + } + } +} \ No newline at end of file diff --git a/blender_rs/src/models/config.rs b/blender_rs/src/models/config.rs new file mode 100644 index 00000000..bb3577e9 --- /dev/null +++ b/blender_rs/src/models/config.rs @@ -0,0 +1,65 @@ +use crate::blender::Frame; + +use super::{ + args::HardwareMode, + blender_scene::{BlenderScene, Sample}, + device::Processor, + format::Format, +}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use uuid::Uuid; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +// TODO: could rename this to something else? +pub struct BlenderConfiguration { + #[serde(rename = "TaskID")] + id: Uuid, + // output various + output: PathBuf, + scene_info: BlenderScene, + cores: usize, + processor: Processor, + hardware_mode: HardwareMode, + sample: Sample, + format: Format, + start: Frame, + end: Frame, + // Py:- Value assign to use_crop_to_border, additionally, false set film_transparent true + crop: bool, +} + +impl BlenderConfiguration { + pub fn new( + output: PathBuf, + scene_info: BlenderScene, + processor: Processor, + hardware_mode: HardwareMode, + samples: Sample, + format: Format, + start: Frame, + end: Frame, + ) -> Self { + let cores = match std::thread::available_parallelism() { + Ok(f) => f.get(), + Err(e) => { + println!("{e:?}"); + 1 + } + }; + Self { + id: Uuid::new_v4(), + output, + scene_info, + cores, + processor, + hardware_mode, + sample: samples, + format, + crop: false, + start, + end + } + } +} diff --git a/blender_rs/src/models/device.rs b/blender_rs/src/models/device.rs new file mode 100644 index 00000000..8089137b --- /dev/null +++ b/blender_rs/src/models/device.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +/* +Developer blog- +The only reason why we need to add number that may or may not match blender's enum number list +is because we're passing in the arguments to the python file instead of Blender CLI. +Once I get this part of the code working, then I'll go back and refactor python code. +*/ + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +// TODO: Find a way to convert enum into String literal for json de/serialize +pub enum Processor { + NONE, + CUDA, + OPTIX, + HIP, + ONEAPI, + // is there METAL? +} \ No newline at end of file diff --git a/blender_rs/src/models/engine.rs b/blender_rs/src/models/engine.rs new file mode 100644 index 00000000..98074ea2 --- /dev/null +++ b/blender_rs/src/models/engine.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum Engine { + #[allow(non_camel_case_types)] + BLENDER_EEVEE, // Pre 4.2.0 + #[allow(non_camel_case_types)] + BLENDER_EEVEE_NEXT, // After 4.2.0 + #[default] + CYCLES, + OPTIX, +} diff --git a/blender_rs/src/models/event.rs b/blender_rs/src/models/event.rs new file mode 100644 index 00000000..41fdc238 --- /dev/null +++ b/blender_rs/src/models/event.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum BlenderEvent { + Log(String), + Warning(String), + Rendering { current: f32, total: f32 }, + Completed { frame: i32, result: PathBuf }, + Unhandled(String), + Exit, + Error(String), +} diff --git a/blender_rs/src/models/format.rs b/blender_rs/src/models/format.rs new file mode 100644 index 00000000..c2326783 --- /dev/null +++ b/blender_rs/src/models/format.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +pub enum FormatError { + InvalidInput, +} + +// More context: https://docs.blender.org/manual/en/latest/advanced/command_line/arguments.html#format-options +#[derive(Debug, Copy, Clone, Default, PartialEq, Serialize, Deserialize)] +pub enum Format { + TGA, + RAWTGA, + JPEG, + IRIS, + AVIRAW, + AVIJPEG, + #[default] + PNG, + BMP, + HDR, + TIFF, +} diff --git a/blender_rs/src/models/mode.rs b/blender_rs/src/models/mode.rs new file mode 100644 index 00000000..53d808cc --- /dev/null +++ b/blender_rs/src/models/mode.rs @@ -0,0 +1,48 @@ +use serde::{Deserialize, Serialize}; +use std::num::ParseIntError; + +// context for serde: https://serde.rs/enum-representations.html +#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] +pub enum RenderMode { + // JSON: "Frame": "i32", + // render a single frame. + Frame(i32), + + // JSON: "Animation": {"start":"i32", "end":"i32"} + // contains the target start frame to the end target frame. + Animation { start: i32, end: i32 }, + + // future project - allow network node to only render section of the frame instead of whole to visualize realtime rendering view solution. + // JSON: "Section": {"frame":"i32", "coord":{"i32", "i32"}, "size": {"i32", "i32"} } + // Section { + // frame: i32, + // coord: (i32, i32), + // size: (i32, i32), + // }, +} + +impl RenderMode { + pub fn new(start: i32, end: i32) -> RenderMode { + let mut start = start; + let mut end = end; + + // start needs to be the lowest number of all. If it's backward, flip it around. + if start > end { + (start, end) = (end, start); + } + + if start + 1 == end { + RenderMode::Frame(start) + } else { + RenderMode::Animation{ start, end } + } + } + + pub fn try_new(start: &str, end: &str) -> Result { + // stop if the parser fail to parse. + let start = start.parse::()?; + let end = end.parse::()?; + + Ok(RenderMode::new(start, end)) + } +} diff --git a/blender_rs/src/models/peek_response.rs b/blender_rs/src/models/peek_response.rs new file mode 100644 index 00000000..e35dda89 --- /dev/null +++ b/blender_rs/src/models/peek_response.rs @@ -0,0 +1,37 @@ +use super::blender_scene::{BlenderScene, Camera, SceneName}; +use crate::blender::Frame; +use semver::Version; +use serde::{Deserialize, Serialize}; + +// TODO: Find a way to get preference saved Processor from the file? +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct PeekResponse { + pub last_version: Version, + pub current: BlenderScene, + pub frame_start: Frame, + pub frame_end: Frame, + #[serde(rename = "FPS")] + pub cameras: Vec, + pub scenes: Vec, +} + +impl PeekResponse { + pub fn new( + last_version: Version, + frame_start: Frame, + frame_end: Frame, + cameras: Vec, + scenes: Vec, + current: BlenderScene, + ) -> Self { + Self { + last_version, + frame_start, + frame_end, + cameras, + scenes, + current, + } + } +} diff --git a/blender_rs/src/models/render_setting.rs b/blender_rs/src/models/render_setting.rs new file mode 100644 index 00000000..b70abd31 --- /dev/null +++ b/blender_rs/src/models/render_setting.rs @@ -0,0 +1,59 @@ +use super::{blender_scene::Sample, /* engine::Engine, */ format::Format, window::Window}; +use crate::blender::Frame; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +pub type FrameRate = u16; // u32 convert into string for xml-rpc. BEWARE! + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct RenderSetting { + /// output of where our stored image will save to + output: PathBuf, + /// Render frame Width + pub width: Frame, // Not to be confused with animation frame + /// Render frame height + pub height: Frame, // Not to be confused with animation frame + /// Samples capture from the scene + pub sample: Sample, + /// Frame per second + #[serde(rename = "FPS")] + pub fps: FrameRate, + /// What render engine to use (Optix/CUDA) + // pub engine: Engine, + /// Image format + pub format: Format, + /// Borders + pub border: Window, +} + +impl RenderSetting { + pub fn new( + output: PathBuf, + width: Frame, + height: Frame, + sample: Sample, + fps: FrameRate, + /* engine: Engine,*/ format: Format, + border: Window, + ) -> Self { + Self { + output, + width, + height, + sample, + fps, + // engine, + format, + border, + } + } + + pub fn set_output(mut self, output: PathBuf) -> Self { + self.output = output; + self + } + + pub fn get_output(&self) -> &PathBuf { + &self.output + } +} diff --git a/blender_rs/src/models/window.rs b/blender_rs/src/models/window.rs new file mode 100644 index 00000000..3783a01b --- /dev/null +++ b/blender_rs/src/models/window.rs @@ -0,0 +1,74 @@ +use serde::{de::Visitor, ser::SerializeStruct, Deserialize, Serialize}; +use std::ops::Range; + +// In the python script, this Window values gets assigned to border of scn.render.border_* +// Here - I'm calling it as window instead. +#[derive(Debug, Clone, PartialEq)] +pub struct Window { + pub x: Range, + pub y: Range, +} + +impl Default for Window { + fn default() -> Self { + Self { + x: Range { + start: 0.0, + end: 1.0, + }, + y: Range { + start: 0.0, + end: 1.0, + }, + } + } +} + +// TODO: Remove this as this may no longer be needed +impl Serialize for Window { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("Border", 4)?; + state.serialize_field("X", &self.x.start)?; + state.serialize_field("X2", &self.x.end)?; + state.serialize_field("Y", &self.y.start)?; + state.serialize_field("Y2", &self.y.end)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for Window { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct WindowVisitor; + + impl<'de> Visitor<'de> for WindowVisitor { + type Value = Window; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct Border") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: serde::de::SeqAccess<'de>, + { + let x = seq.next_element()?.unwrap_or(0.0); + let x2 = seq.next_element()?.unwrap_or(1.0); + let y = seq.next_element()?.unwrap_or(0.0); + let y2 = seq.next_element()?.unwrap_or(1.0); + Ok(Window { + x: Range { start: x, end: x2 }, + y: Range { start: y, end: y2 }, + }) + } + } + + const FIELDS: &[&str] = &["X", "X2", "Y", "Y2"]; + deserializer.deserialize_struct("Window", FIELDS, WindowVisitor) + } +} diff --git a/blender_rs/src/page_cache.rs b/blender_rs/src/page_cache.rs new file mode 100644 index 00000000..7d3c9479 --- /dev/null +++ b/blender_rs/src/page_cache.rs @@ -0,0 +1,274 @@ +use crate::constant::MAX_VALID_DAYS; +use lazy_regex::regex_replace_all; +use serde::{Deserialize, Serialize}; +use std::io::{BufReader, ErrorKind, Error, Read, Result}; +use std::path::Path; +use std::time::Duration; +use std::{collections::HashMap, fs, path::PathBuf, time::SystemTime}; +use url::Url; + +#[derive(Debug, Clone, Serialize, Deserialize)] +enum ExpirationUnits { + Disable, + Day(i8), + Week(i8), + Month(i8), + // Year(i8), +} + +impl ExpirationUnits { + + const DAYS_TO_WEEK: u64 = 7; + const DAYS_TO_MONTH: u64 = 30; + const DAY_INTO_HOURS: u64 = 24; + const WEEK_INTO_HOURS: u64 = Self::DAY_INTO_HOURS * Self::DAYS_TO_WEEK; + const MONTH_INTO_HOURS: u64 = Self::DAY_INTO_HOURS * Self::DAYS_TO_MONTH; + + fn cast_to_duration(&self) -> Option { + match self { + ExpirationUnits::Day(d) => { + Some(Duration::from_hours((*d as u64) * Self::DAY_INTO_HOURS)) + }, + ExpirationUnits::Week(w) => { + Some(Duration::from_hours((*w as u64) * Self::WEEK_INTO_HOURS)) + } + ExpirationUnits::Month(m) => { + Some(Duration::from_hours((*m as u64) * Self::MONTH_INTO_HOURS)) + }, + ExpirationUnits::Disable => None + } + } + + // None is return when ExpirationUnits is disabled + pub fn get_expiration_date(&self) -> Option { + let current_date = SystemTime::now(); + let duration = self.cast_to_duration()?; + current_date.checked_sub(duration) + } +} + +impl Default for ExpirationUnits { + fn default() -> Self { + ExpirationUnits::Month(6) + } +} +// Hide this for now, +#[doc(hidden)] +// rely the cache creation date on file metadata. +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct PageCache { + #[serde(skip)] + inner: PathBuf, + cache: HashMap, + expiration_duration: ExpirationUnits, + cache_dir: PathBuf, +} + +// the whole idea behind this was to store information from blender with minimal connectivity +// interface as possible. Rely on cache if we need to lookup again. This separate us from ChatGPT and other LLM agents. +impl PageCache { + const CACHE_DIR: &str = "cache"; + const CONFIG_NAME: &str = "cache.json"; + const SECONDS_TO_HOUR: u64 = 3600; + const HOURS_TO_DAY: u64 = 24; + + // fetch cache directory + fn get_default_dir() -> Result { + let mut tmp = dirs::cache_dir().ok_or(Error::new( + ErrorKind::NotFound, + "Unable to fetch cache directory! Must have permission to create cache directory!", + ))?; + // append our program folder name. + tmp.push(Self::CACHE_DIR); + // ensure directory exist and created. + fs::create_dir_all(&tmp).and(Ok(tmp)) + } + + // fetch path to cache file + #[inline] + fn get_cache_path() -> Result { + Ok(Self::get_default_dir()?.join(Self::CONFIG_NAME)) + } + + // private method, only used to save when cache has changed. + pub(crate) fn save(&mut self) -> Result<()> { + let data = serde_json::to_string(&self)?; + fs::write(&self.inner, data) + } + + // TODO: See where and how we can utilize this validation process? + #[allow(dead_code)] + fn validate_cache(&mut self) { + // Here we run a check of all of the cache we have stored, and then check the last modified date. If it exceed page cache's + // TODO: Present a "Delete cache after X Y" Where X is a number and Y is enum such as Day, Weeks, or Month + // - We should be realistic, protective, and caution about security and delete cache older than 6 months as default value, + // unless someone objects this idea and creates a PR request removing this comment and prove me wrong why we should store cache older than a year? + // At this point, you might as well just turn off this feature? + + // gather a list of files currently in the cache directory (excluding cache.json) + // this will help us clean the cache folder of files ready to be deleted from the system. + // let files_found = fs::read_dir(&self.cache_dir).map_or(Vec::new(), f); + + self.cache.retain(|_, v| { + if !&v.exists() { + return false; + } + + if let Some(expiration_date) = self.expiration_duration.get_expiration_date() { + match fs::metadata(&v) { + Ok(m) => { + // the error would raise if field doesn't exist on specific platform. I believe we're safeguarded to use latest major OS platform (Linux/Mac/Win) + return match m.created() { + Ok(date) => expiration_date.ge(&date), + Err(e) => { + eprintln!("Shouldn't be possible to error unless the feature doesn't exist on target platform: {e:?}"); + return false; + } + } + }, + Err(e) => { + eprintln!("[PageCache] Unable to read metadata!{e:?}"); + return false; + } + } + } + + // how do we handle with files existing in the cache? + + // because of "disable" enum, disable retains all records. + true + }); + } + + // suppressing this for now, I'm testing the program out without having to worry about invalidating cache files for now. + // Currently used in commented code in PageCache::load() implementation. + #[allow(dead_code)] + fn check_expiration(cache_path: impl AsRef) -> bool { + let current = SystemTime::now(); + let fallback = current.clone(); + // read the metadata of the cache.json file. + // if the creation date is beyond the configuration expiration rule, we should delete the file and refresh from the source of truth. + let created_date = match fs::metadata(&cache_path) { + Ok(m) => m + .is_file() + .then(|| m.created().unwrap_or(fallback)) + .unwrap_or_else(|| fallback), + _ => fallback, + }; + + // TODO: For now I'm trying to test this out without having to redownload everything again from the internet source. + // if file exist and provides duration date. + if let Ok(duration) = current.duration_since(created_date) { + // must be within valid window timeframe. + if duration.as_secs() < MAX_VALID_DAYS * Self::SECONDS_TO_HOUR * Self::HOURS_TO_DAY { // TODO: Magic values. Try to define them as constexpr + // logger + println!( + "Time still valid: Remaining {}hrs", + duration.as_secs() / Self::SECONDS_TO_HOUR - (MAX_VALID_DAYS * Self::HOURS_TO_DAY) // TODO: Magic values. Try to define them as constexpr + ); + return true; + } + } + false + } + + // TODO: name is too ambiguous. What is load? What are we loading? What does it do? Does it load the program? File? Something? + pub fn load() -> Result { + // use define path to cache file + let path = Self::get_cache_path()?; + + // TODO: For now I'm trying to test this out without having to redownload everything again from the internet source. + // use define path to cache file + // if Self::check_expiration(&path) == false { + // return Ok(Self::default()); + // } + + let reader = BufReader::new(fs::File::open(&path)?); + let mut data: PageCache = serde_json::from_reader(reader)?; + data.inner = path; + Ok(data) + } + + fn generate_file_name(url: &Url) -> String { + let mut file_name = url.to_string(); + // Rule: find any invalid file name characters + // remove trailing slash + file_name.ends_with('/').then(|| file_name.pop()); + // Replace any invalid characters with hyphens + regex_replace_all!(r#"[/\\?%*:|."<>]"#, &file_name, "-").to_string() + } + + /// check and see if the url matches the cache, + /// otherwise, fetch the page from the internet, and save it to storage cache, + /// then return the page result. + pub fn fetch_or_update(&mut self, url: &Url) -> Result { + + // TODO can we avoid using to_owned()/clone()? + let path = self.cache.entry(url.clone()).or_insert( { + let file_name = Self::generate_file_name( url ); + let destination_path = self.cache_dir.join(file_name); + + // Are we making the assumption that if the file is not in the entry then we can just presume it's valid? + if !destination_path.exists() { + let mut response = ureq::get(url.as_ref()).call().map_err(Error::other)?; + let mut body = Vec::new(); + if let Err(e) = response.body_mut().as_reader().read_to_end(&mut body) { + eprintln!("Fail to read data for cache: {e:?}"); + } + + // write the content to the file + fs::write(&destination_path, body)?; + } + + destination_path + }); + + fs::read_to_string(path) + } + + pub fn fetch(self, url: &Url) -> Option { + let path = self.cache.get(url)?; + fs::read_to_string(path).ok() + } +} + +impl Drop for PageCache { + fn drop(&mut self) { + if let Err(e) = self.save() { + println!("Error saving cache file: {}", e); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // This automation test does not make a lot of sense at all. It should be per each function callings. + #[test] + fn should_pass() { + let cache = PageCache::load(); + assert!(cache.is_ok()); + let mut cache = cache.unwrap(); + let url = Url::parse("http://www.google.com").unwrap(); + let content = cache.fetch_or_update(&url); + assert_eq!(content.is_ok(), true); + } + + #[test] + fn should_fail() { + // TODO: How can I fail page_cache? + // - lack of permission for directory asking to store and save web contents. + // - logic condition inside Drop method scope. We try to invoke some Io operation on drop. Discouraging? Maybe? + // - fetch_str rely on url parsing. + let cache = PageCache::load(); + assert!(cache.is_ok()); + } + + // TODO: write unit test for get_dir() + #[test] + fn get_dir_succeed() { + let cache = PageCache::get_default_dir(); + assert!(cache.is_ok()); + } +} diff --git a/blender_rs/src/render.py b/blender_rs/src/render.py new file mode 100644 index 00000000..850d9f00 --- /dev/null +++ b/blender_rs/src/render.py @@ -0,0 +1,140 @@ +# TODO: Sybren mention that Cycle will perform better if the render was sent out as +# a batch instead of individual renders. Consider using Range() +# TODO: See if there's a way to adjust blender render batch if possible? +# TODO: What's the earliest python version blender supports? Wanted to make sure we are compilance with older version to use supported built-in library stacks. + +import bpy # type: ignore +import xmlrpc.client +import json +import sys # used for argparse - does not work well with blender! +# from typing import Optional +# from dataclasses import dataclass +from multiprocessing import cpu_count + +def eprint(msg): + print("EXCEPTION: %s\n" % str(msg), flush=True) + +def log(msg): + print("LOG: %s\n" % str(msg), flush=True) + +# Feature thing, For now keep it dynamic. +# @dataclass +# class SceneInfo(object): +# scene: Optional[str] + +# @dataclass +# class Config(object): +# scene_info: SceneInfo + +# @classmethod +# def from_json(cls, json_key): +# file = json.load(open("h.json")) +# return cls(**file[json_key]) + +# hardware:[CPU,GPU,BOTH], kind: [NONE, CUDA, OPTIX, HIP, ONEAPI, (METAL?)] +# Eventually in the future we could distribute to a point of using certain GPU for certain render? +def configureSystemRenderDevices(processor, hardware): + # log("Setting up Cycles Render Devices") + pref = bpy.context.preferences.addons["cycles"].preferences + pref.compute_device_type = processor + devices = pref.get_devices_for_type(pref.compute_device_type) + + for d in devices: + # devices do not show GPU, instead they show what your GPU supports (CUDA for RTX) + # CPU GPU ALL + d.use = (d.type == hardware) or (d.type != 'CPU' and hardware == 'GPU') or ( hardware == "BOTH") + +def setRenderSettings(scn, config): + sceneInfo = config["SceneInfo"] + renderSetting = sceneInfo["render_setting"] + + #Set Camera + camera = sceneInfo["camera"] + if(camera is not None and bpy.data.objects[camera] is not None): + scn.camera = bpy.data.objects[camera] + + # set scene render engine + # *We should rely on the scene file engine configuration, rather than explicitly assigning before batch jobs. + # scn.render.engine = config["Engine"] + + # this attribute only accepts 'CPU' or 'GPU' - only available in Cycles Render Engine + scn.cycles.device = config["HardwareMode"] + + # Conifgure System Render Devices + configureSystemRenderDevices(config["Processor"], scn.cycles.device) + + #Set Samples + scn.cycles.samples = renderSetting["sample"] + scn.render.use_persistent_data = True + + # Set Frames Per Second + fps = renderSetting["FPS"] + if fps is not None and fps > 0: + scn.render.fps = fps + + #Set Resolution + scn.render.resolution_x = renderSetting["width"] + scn.render.resolution_y = renderSetting["height"] + scn.render.resolution_percentage = 100 + + # Set borders + border = renderSetting["border"] + scn.render.border_min_x = border["X"] + scn.render.border_max_x = border["X2"] + scn.render.border_min_y = border["Y"] + scn.render.border_max_y = border["Y2"] + + # set render format + file_format = config["Format"] + if(file_format is not None): + scn.render.image_settings.file_format = file_format + + # Set threading + threads = config["Cores"] + scn.render.threads_mode = 'FIXED' + scn.render.threads = max(cpu_count(), threads) + + # Set constraints + scn.render.use_border = True + scn.render.use_crop_to_border = config["Crop"] + if not scn.render.use_crop_to_border: + scn.render.film_transparent = True + +#Renders provided settings with id to path +def renderFrame(scn, config): + # Set frame and output + # ref: https://docs.blender.org/api/current/bpy.types.Scene.html#bpy.types.Scene.frame_start + scn.frame_start = int(config["Start"]) + scn.frame_end = int(config["End"]) + + # We must override the output path to a valid known location + scn.render.filepath = config["Output"] + '''/#####''' + + # Render + id = str(config["TaskID"]) + # TODO: How do I stream this? Why do I have to "flush"? + print("RENDER_START: %s\n" % id, flush=True) + # TODO: Research what use_viewport does? What about animation? + bpy.ops.render.render(animation=True, write_still=True, use_viewport=False) + # TODO: How do I stream this? Why do I have to "flush"? + print("SUCCESS: %s\n" % id, flush=True) + +def main(config) -> None: + # proxy = xmlrpc.client.ServerProxy("http://%s:%s" % (ip, port)) + scn = bpy.context.scene + setRenderSettings(scn, config) + renderFrame(scn, config) + +if __name__ == "__main__": + # argparse.ArgumentParser does not work well with blender! Avoid using argparse! + args = sys.argv + try: + content = args[args.index("-c")+1] + config = json.loads(content) + # config = json.loads(proxy.fetch_info(1)) + main(config) + except Exception as e: + print(e) + sys.exit(-1) + sys.exit(0) + \ No newline at end of file diff --git a/blender_rs/src/services/category.rs b/blender_rs/src/services/category.rs new file mode 100644 index 00000000..d2a338f4 --- /dev/null +++ b/blender_rs/src/services/category.rs @@ -0,0 +1,239 @@ +use crate::blender::Blender; +use crate::services::packages::BlenderPath; +use crate::services::packages::{download_link::DownloadLink, package::Package}; +use crate::utils::{get_extension, get_valid_arch}; +use lazy_regex::{self, regex_captures_iter}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::env::consts; +use std::path::Path; +use thiserror::Error; +use url::Url; + +// I have a situation where I can create this object, but not yet populate the download list. +// There are two ways to load the list, one from page cache, assuming we have already visited the website +// and the second is to load the website content, but also update the page cache to avoid revisitation and suspectible to DDoS/IP ban + +#[derive(Debug, Error)] +pub enum BlenderCategoryError { + #[error("Architecture type \"{0}\" is not supported!")] + InvalidArch(String), + #[error("Unsupported operating system: {0}")] + UnsupportedOS(String), + #[error("Not found")] + NotFound, + #[error("Io Error")] + Io(#[from] std::io::Error), +} + +// Blender Category is a sub page within download.blender.org/release page, this page contains all of the urls associated with arch, os, and bits. +// In this struct, on initialization, we parse the content of this website and generate a structure data we can run functions on. +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct BlenderCategory { + base_url: Url, + major: u64, + minor: u64, + links: HashMap, +} + +impl PartialOrd for BlenderCategory { + fn partial_cmp(&self, other: &Self) -> Option { + let result = match self.major.cmp(&other.major) { + Ordering::Equal => self.minor.cmp(&other.minor), + ord => ord, + }; + Some(result) + } +} + +impl Ord for BlenderCategory { + fn cmp(&self, other: &Self) -> Ordering { + match self.major.cmp(&other.major) { + Ordering::Equal => self.minor.cmp(&other.minor), + ord => ord, + } + } +} + +impl PartialEq for BlenderCategory { + fn eq(&self, other: &Self) -> bool { + self.base_url.cmp(&other.base_url).is_eq() + } +} + +impl Eq for BlenderCategory {} + +// content of https://download.blender.org/release/Blender{major}.{minor}/ +impl BlenderCategory { + // TODO: [BUG] for some reason I was fetching this multiple of times already. Expensive to call. Profile test? + // should only be called once when this class is created. + // TODO: Try to make this private as much as possible! this parse content is a hack to help reduce function complexity. + // But instead it creates a spaghetti mess. Will handle this with context of some sort in the future. + pub(crate) fn parse_content( + content: &str, + base_url: &Url, + download_path: impl AsRef, + ) -> Result, BlenderCategoryError> { + let current_arch = + get_valid_arch().map_err(|e| BlenderCategoryError::InvalidArch(e.into()))?; + let valid_ext = + get_extension().map_err(|e| BlenderCategoryError::UnsupportedOS(e.into()))?; + + // The rule has changed. The extension will not include a period symbol. Additional period will be treated as extension of extension, e.g. tar.xz + let iter = regex_captures_iter!( + r#""#, + &content + ); + let links = iter.map(|c| c.extract()).fold( + HashMap::new(), + |mut map, (_, [url, major, minor, patch, os, arch, ext])| { + // Check and see if the extension is valid + if ext.ne(valid_ext) { + return map; + } + + // Must match running operating system. + // TODO: Does this matter? We have arch and ext to validate against? + if os.ne(consts::OS) { + return map; + } + + // Compatible with existing archtecture + if arch.ne(current_arch) { + return map; + } + + // *filter out any major version 3 or below. We will not be supporting legacy blender at the moment. + let major: u64 = match major.parse() { + Ok(v) if v >= 3 => v, + Ok(_) => return map, + Err(e) => { + eprintln!("{e:?}"); + return map; + } + }; + + let minor: u64 = match minor.parse() { + Ok(v) => v, + Err(e) => { + eprintln!("{e:?}"); + return map; + } + }; + + let patch: u64 = match patch.parse() { + Ok(v) => v, + Err(e) => { + eprintln!("{e:?}"); + return map; + } + }; + + let version = Version::new(major, minor, patch); + let url = match base_url.join(&url) { + Ok(url) => url, + Err(e) => { + eprintln!("{e:?}"); + return map; + } + }; + let link = match DownloadLink::new(url, version.clone()) { + Ok(link) => link, + Err(e) => { + eprintln!("{e:?}"); + return map; + } + }; + if let Ok(package) = Package::check_package(link, &download_path) { + map.insert(version, package); + } + + map + }, + ); + + Ok(links) + } + + pub fn new(base_url: Url, major: u64, minor: u64, links: HashMap) -> Self { + Self { + base_url, + major, + minor, + links, + } + } + + // fetch latest version of blender if it's available. + // TODO: Refactor this class down. + // pub(crate) fn fetch_latest( + // &mut self, + // download_path: impl AsRef, + // ) -> Result { + // // first I need is pop the entry from the links vector, as we're going to mutate the value. + // let package = self + // .links + // .iter() + // .fold(None, |result: Option<&Package>, (version, link)| { + // if let Some(latest) = result { + // if latest.get_version().ge(version) { + // return result; + // } + // } + // Some(link) + // }) + // .ok_or(BlenderCategoryError::NotFound)?; + + // let target_version = package.get_version().clone(); + // let package = self + // .links + // .remove(&target_version) + // .expect("Would expect at least a valid location?"); + + // let link = package.get_package_ready(download_path)?; + // let blender = link.get_blender().ok_or(BlenderCategoryError::NotFound)?; + // if let Some(old_value) = self.links.insert(link.get_version().clone(), link) { + // eprintln!("Not possible? Value must have been popped to mutate value before insert back in \n{old_value:?}"); + // } + // Ok(blender) + // } + + // for the sake of this, we will trust that the user wants Blender from this. + // Function renamed from retrieve + /// Retrieve blender if it already installed, otherwise install from known source and return blender. + pub fn get_blender( + &mut self, + download_path: impl AsRef, + target_version: &Version, + ) -> Result { + // pop entry. we can mutate this now. + let package = self + .links + .remove(target_version) + .ok_or(BlenderCategoryError::NotFound)?; + + // repeated method as described above: + let link = package.get_package_ready(download_path)?; + let blender = link.get_blender().ok_or(BlenderCategoryError::NotFound)?; + + // append back to the record. + if let Some(old_value) = self.links.insert(target_version.clone(), link) { + eprintln!("Somehow received a record updated? Not possible? {old_value:?}"); + } + Ok(blender) + } + + pub fn get_packages(&self) -> Vec<&Package> { + self.links + .iter() + .map(|(_, package)| package) + .collect::>() + } + + // return the version range for this category + pub fn get_version(&self) -> Version { + Version::new(self.major, self.minor, 0) // will always be the lowest patch for category only. + } +} diff --git a/blender_rs/src/services/mod.rs b/blender_rs/src/services/mod.rs new file mode 100644 index 00000000..1fd33667 --- /dev/null +++ b/blender_rs/src/services/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod category; +pub(crate) mod packages; +pub(crate) mod portal; diff --git a/blender_rs/src/services/packages/bundle.rs b/blender_rs/src/services/packages/bundle.rs new file mode 100644 index 00000000..312c0715 --- /dev/null +++ b/blender_rs/src/services/packages/bundle.rs @@ -0,0 +1,33 @@ +use crate::{ + blender::Blender, + services::packages::{downloaded::Downloaded, package::PackageT, BlenderPath}, +}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub struct Bundle { + pub content: Downloaded, + executable: PathBuf, +} + +impl Bundle { + pub(crate) fn new(content: Downloaded, executable: PathBuf) -> Self { + Self { + content, + executable, + } + } +} + +impl BlenderPath for Bundle { + fn get_blender(&self) -> Option { + Blender::from_executable(&self.executable).ok() + } +} + +impl PackageT for Bundle { + fn get_version(&self) -> &semver::Version { + &self.content.origin.version + } +} diff --git a/blender_rs/src/services/packages/custom.rs b/blender_rs/src/services/packages/custom.rs new file mode 100644 index 00000000..1eb6628e --- /dev/null +++ b/blender_rs/src/services/packages/custom.rs @@ -0,0 +1,37 @@ +use crate::{ + blender::{Blender, BlenderError}, + services::packages::{package::PackageT, BlenderPath}, +}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; + +/// Design to let user upload path to blender executables. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct Custom { + version: Version, + executable: PathBuf, +} + +impl Custom { + #[allow(dead_code)] + pub fn new(path: impl AsRef) -> Result { + let blender = Blender::from_executable(path)?; + Ok(Self { + version: blender.get_version().to_owned(), + executable: blender.get_executable().to_owned(), + }) + } +} + +impl BlenderPath for Custom { + fn get_blender(&self) -> Option { + Blender::from_executable(&self.executable).ok() + } +} + +impl PackageT for Custom { + fn get_version(&self) -> &semver::Version { + &self.version + } +} diff --git a/blender_rs/src/services/packages/download_link.rs b/blender_rs/src/services/packages/download_link.rs new file mode 100644 index 00000000..118a9040 --- /dev/null +++ b/blender_rs/src/services/packages/download_link.rs @@ -0,0 +1,98 @@ +use crate::services::{ + category::BlenderCategoryError, + packages::{downloaded::Downloaded, package::PackageT}, +}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::{ + fs, + io::{Error as IoError, Read}, + path::{Path, PathBuf}, +}; +use url::Url; + +// TODO: Could I implement Hash traits? Use version as hash id +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct DownloadLink { + pub version: Version, + pub file_name: String, // contains extensions! + pub download_url: Url, +} + +impl DownloadLink { + pub fn new(url: Url, version: Version) -> Result { + let name = url + .path_segments() + .ok_or(BlenderCategoryError::NotFound)? + .last() + .ok_or(BlenderCategoryError::NotFound)? + .to_owned(); + + Ok(Self { + file_name: name, + download_url: url, + version, + }) + } + + fn download_path(&self, install_path: impl AsRef) -> PathBuf { + install_path.as_ref().join(&self.file_name) + } + + // Destination expects absolute path + pub fn content_exist(self, destination: impl AsRef) -> Result { + let path = self.download_path(destination); + if path.exists() { + let downloaded = Downloaded { + origin: self, + content: path, + }; + return Ok(downloaded); + } + Err(self) + } + + // at this point here we will download the link and return an updated state + pub fn download(self, destination: impl AsRef) -> Result { + // got a permission denied here? Interesting? + // I need to figure out why and how I can stop this from happening? + fs::create_dir_all(&destination)?; + + // create a target name + let target = self.download_path(destination); + + // Check and see if we haven't download the file already + if !target.exists() { + // Download the file from the internet + let mut response = ureq::get(self.download_url.as_str()) + .call() + .map_err(IoError::other)?; + let mut body: Vec = Vec::new(); + // TODO: See if there's a better way to save or store the file? + // It's like why can't we stream directly to io? + if let Err(e) = response.body_mut().as_reader().read_to_end(&mut body) { + eprintln!("Fail to read data from response! {e:?}"); + } + // save the content to target + fs::write(&target, &body)?; + } + + // Assume the file we download are zipped/compressed. + Ok(Downloaded { + origin: self, + content: target, + }) + } +} + +impl PackageT for DownloadLink { + fn get_version(&self) -> &Version { + &self.version + } +} + +impl AsRef for DownloadLink { + fn as_ref(&self) -> &Version { + &self.version + } +} diff --git a/blender_rs/src/services/packages/downloaded.rs b/blender_rs/src/services/packages/downloaded.rs new file mode 100644 index 00000000..cf2ab5b8 --- /dev/null +++ b/blender_rs/src/services/packages/downloaded.rs @@ -0,0 +1,190 @@ +use crate::services::category::BlenderCategoryError; +use crate::services::packages::bundle::Bundle; +use crate::services::packages::package::{Package, PackageT}; +#[cfg(target_os="macos")] +use crate::utils::MACOS_PATH; +use crate::{services::packages::download_link::DownloadLink, utils::get_extension}; +use semver::Version; +use serde::{Deserialize, Serialize}; +#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] +use std::env::consts::OS; +use std::io::Error as IoError; +use std::path::{Path, PathBuf}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct Downloaded { + pub origin: DownloadLink, + pub content: PathBuf, +} + +impl Downloaded { + // return the path of execution entry point (mac specific) + fn get_executable_path(&self) -> Result { + let path = self.get_content_path()?; + // TODO: Need to make a decision on this; + // Do we want to return the absolute executable path, or path to application source? + #[cfg(target_os = "macos")] + return Ok(path.join("Blender.app").join(MACOS_PATH)); + #[cfg(target_os = "linux")] + return Ok(path.join("blender")); + #[cfg(target_os = "windows")] + return Ok(path.join("Blender.exe")); + } + + // return the destination of application source and bundle (mac specific) + fn get_content_path(&self) -> Result { + #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] + return Err(BlenderCategoryError::UnsupportedOS(OS.into())); + + let ext = get_extension() + .map_err(|e| IoError::other(format!("Cannot run blender under this OS: {}!", e)))?; + // A hack- get_extension does not include period, so we need to include the period to generate the folder name correctly + let folder_name = self.origin.file_name.replace(&format!(".{ext}"), ""); // remove the extension + Ok(self.content.parent().unwrap().join(folder_name)) + } + + // Currently being used for MacOS (I wonder if I need to do the same for windows?) + #[cfg(target_os = "macos")] + fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> Result<(), IoError> { + use std::fs; + + fs::create_dir_all(&dst)?; + for entry in fs::read_dir(src)? { + let entry = entry.unwrap(); + if entry.file_type().unwrap().is_dir() { + Self::copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name())).unwrap(); + } else { + fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; + } + } + Ok(()) + } + + /// Extract tar.xz file from destination path, and return blender executable path + // TODO: Tested on Linux - something didn't work right here. Need to investigate/debug through + #[cfg(target_os = "linux")] + fn extract_content( + download_path: impl AsRef, + destination: impl AsRef, + ) -> Result { + use std::fs::File; + use tar::Archive; + use xz::read::XzDecoder; + + let path = download_path.as_ref(); + // Get file handler to download location + let file = File::open(path)?; + + // decode compressed xz file + let tar = XzDecoder::new(file); + + // unarchive content from decompressed file + let mut archive = Archive::new(tar); + + let destination = destination.as_ref(); + + // extract content to destination + archive.unpack(destination)?; + + // return extracted executable path + Ok(destination.join("blender")) + } + + /// Mounts dmg target to volume, then extract the contents to a new folder using the folder_name, + /// lastly, provide a path to the blender executable inside the content. + #[cfg(target_os = "macos")] + fn extract_content( + download_path: impl AsRef, + destination: impl AsRef, + ) -> Result { + use crate::utils::MACOS_PATH; + use dmg::Attach; + use std::fs; + const APP_NAME: &str = "Blender.app"; + + let source = download_path.as_ref(); + let dst = destination.as_ref(); + + if !dst.exists() { + let _ = fs::create_dir_all(&dst)?; + } + + // now append the app name and set that as our unpack destination. + let dst = dst.join(APP_NAME); + + let dmg = Attach::new(&source).attach()?; // attach dmg to volume + let src = PathBuf::from(&dmg.mount_point.join(APP_NAME)); // create source path from mount point + Self::copy_dir_all(&src, &dst)?; // Extract content inside Blender.app to destination + dmg.detach()?; // detach dmg volume + Ok(dst.join(MACOS_PATH)) // return path with additional path to invoke blender directly + } + + // TODO: verify this is working for windows (.zip)? + #[cfg(target_os = "windows")] + fn extract_content( + download_path: impl AsRef, + folder_name: &str, // TODO: Change this to destination instead. + ) -> Result { + use std::fs::File; + use zip::ZipArchive; + + let source = download_path.as_ref(); + // On windows, unzipped content includes a new folder underneath. Instead of doing this, we will just unzip from the parent instead... weird + let zip_loc = source.parent().unwrap(); + let output = zip_loc.join(folder_name); + + // check if the directory exist + match &output.exists() { + // if it does, check and see if blender exist. + true => { + // if it does exist, then we can skip extracting the file entirely. + if output.join("Blender.exe").exists() { + return Ok(output.join("Blender.exe")); + } + } + _ => {} + } + + let file = File::open(source).unwrap(); + let mut archive = ZipArchive::new(file).unwrap(); + if let Err(e) = archive.extract(zip_loc) { + println!("Unable to extract content to target: {e:?}"); + } + + Ok(output.join("Blender.exe")) + } + + pub fn check_unpacked(self) -> Result { + // here we would navigate to the extracted directory based on the rules generated in this struct, if the path to executable exist, then return Bundle, otherwise return itself. + // assuming the logic goes - in the same path destination as compressed content, there should be a folder containing the extracted content. + if let Ok(executable_path) = self.get_executable_path() { + if executable_path.exists() { + return Ok(Bundle::new(self, executable_path)); + } + } + Err(self) + } + + pub fn extract(self) -> Package { + let destination = match self.get_content_path() { + Ok(path) => path, + Err(e) => { + eprintln!("Unable to find content path! {e:?}"); + return Package::Downloaded(self); + } + }; + match Self::extract_content(&self.content, destination) { + Ok(executable_path) => Package::Bundle(Bundle::new(self, executable_path)), + Err(e) => { + eprintln!("Unable to Extract Contents: {e:?}"); + Package::Downloaded(self) + } + } + } +} + +impl PackageT for Downloaded { + fn get_version(&self) -> &Version { + self.origin.get_version() + } +} diff --git a/blender_rs/src/services/packages/mod.rs b/blender_rs/src/services/packages/mod.rs new file mode 100644 index 00000000..59090288 --- /dev/null +++ b/blender_rs/src/services/packages/mod.rs @@ -0,0 +1,11 @@ +use crate::blender::Blender; + +pub(crate) mod custom; +pub(crate) mod download_link; +pub(crate) mod downloaded; +pub(crate) mod bundle; +pub(crate) mod package; + +pub(crate) trait BlenderPath { + fn get_blender(&self) -> Option; +} \ No newline at end of file diff --git a/blender_rs/src/services/packages/package.rs b/blender_rs/src/services/packages/package.rs new file mode 100644 index 00000000..9b893c4c --- /dev/null +++ b/blender_rs/src/services/packages/package.rs @@ -0,0 +1,97 @@ +use crate::{ + blender::Blender, + services::{ + category::BlenderCategoryError, + packages::{ + bundle::Bundle, /* custom::Custom, */ download_link::DownloadLink, + downloaded::Downloaded, BlenderPath, + }, + }, +}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +pub(crate) trait PackageT { + fn get_version(&self) -> &Version; +} + +/* + Package is thought of having a single source of truth to get blender specific versions. + Depends on the phase, we would need to download if it's not found within local system. + Otherwise, use the uncompressed version of the executable and treat as final source of truth. + We have method implementations to gracefully fetch the package. +*/ +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum Package { + // Only contains download link + Metadata(DownloadLink), + // contains download origin and path to downloaded content + Downloaded(Downloaded), + // Contains complete set, do not download, do not unpact, should provide executable path + Bundle(Bundle), + // Only contains executable location, user defined variable + // Executable(Custom), + // TODO: Feature request - Would there ever be a chances for any of the data above would mutate and become invalid? Test this out? + // In some extreme cases - if something goes wrong, we can put them in malform state until user corrects them into Bundle state, or lesser state known. + // Malformed { origin: Option, downloaded: Option, executable: Option }, +} + +impl Package { + // This is design to check internal source and verify the package is indeed correct, otherwise return the current state it failed in + // we are only provided with a source. + pub fn check_package( + link: DownloadLink, + destination: impl AsRef, + ) -> Result { + // This ideally should return something... + // we'll start here first + let downloaded = match link.content_exist(destination) { + Ok(downloaded) => downloaded, + Err(download_link) => return Ok(Package::Metadata(download_link)), + }; + + match downloaded.check_unpacked() { + Ok(bundle) => Ok(Package::Bundle(bundle)), + // Do not unzip, simply return the current state and move on. + Err(downloaded) => Ok(Package::Downloaded(downloaded)), + } + } + + // This is an attempt to download from url, extract, and provide package ready to be used for blender. + pub fn get_package_ready( + self, + destination: impl AsRef, + ) -> Result { + match self { + Package::Metadata(link) => Ok(link.download(&destination)?.extract()), + Package::Downloaded(link) => Ok(link.extract()), + // These two are ok since they were already ready to begin with + // Package::Executable(..) => Ok(self), + Package::Bundle(..) => Ok(self), + } + } +} + +impl PackageT for Package { + fn get_version(&self) -> &Version { + match self { + Package::Metadata(link) => link.get_version(), + Package::Downloaded(content) => content.get_version(), + // Package::Executable(path) => path.get_version(), + Package::Bundle(bundle) => bundle.get_version(), + // Package::Malformed { origin, downloaded, executable } => todo!(), + } + } +} + +impl BlenderPath for Package { + // without modifying itself, we can only provide as much. + fn get_blender(&self) -> Option { + match self { + Package::Bundle(bundle) => bundle.get_blender(), + // Package::Executable(custom) => custom.get_blender(), + _ => None, + } + } +} diff --git a/blender_rs/src/services/portal.rs b/blender_rs/src/services/portal.rs new file mode 100644 index 00000000..74583561 --- /dev/null +++ b/blender_rs/src/services/portal.rs @@ -0,0 +1,226 @@ +use crate::blender::Blender; +use crate::services::category::BlenderCategory; +use crate::services::packages::package::Package; +use crate::{blender::ManagerError, page_cache::PageCache}; +use lazy_regex::regex_captures_iter; +use semver::Version; +use std::env::consts::{ARCH, OS}; +use std::path::{Path, PathBuf}; +use url::Url; + +// I want this struct to remain private for now. +// This struct should be used as an component to fetch from reliable resources. +// alternatively, I could swap this out and use my own custom storage solution. +#[derive(Debug)] +pub(crate) struct Portal { + // list of category on download.blender.org + list: Vec, + + // Path to install and download zip content - Usually driven by BlenderConfig + download_path: PathBuf, +} + +impl Portal { + const ROOT_URL: &str = "https://download.blender.org/release/"; + + fn new(download_path: PathBuf, list: Vec) -> Self { + Self { + list, + download_path, + } + } + + // Only used in this state. + #[inline] + fn get_parent(major: u64, minor: u64) -> String { + format!("Blender{major}.{minor}") + } + + // function generator for closures in regex patterns. + fn generate_blender_category( + parent: &Url, + url: &str, + major: &str, + minor: &str, + download_path: &Path, + cache: &mut PageCache, + ) -> Option { + // create the link for blender category location + let url = match parent.join(url) { + Ok(path) => path, + Err(e) => { + eprintln!("unable to join paths! {e:?}"); + return None; + } + }; + + let major: u64 = match major.parse() { + Ok(val) if val >= 3 => val, + Ok(_) => { + // TODO: impl a debug switch mode to allow printing these verbose console logs. + // eprintln!("Omitting outdated major version."); + return None; + } + Err(e) => { + eprintln!("{e:?}"); + return None; + } + }; + + let minor: u64 = match minor.parse() { + Ok(val) => val, + Err(e) => { + eprintln!("{e:?}"); + return None; + } + }; + + // Append the download path to the category's folder path. + // E.g. ~/Downloads/Blender/Blender4.2/ + let destination_path = download_path.join(Self::get_parent(major, minor)); + + if let Ok(content) = &cache.fetch_or_update(&url) { + if let Ok(links) = BlenderCategory::parse_content(&content, &url, &destination_path) { + return Some(BlenderCategory::new(url, major, minor, links)); + } + } + None + } + + /// This method will fetch the list of blender category that's listed under download.blender.org/releases webpage. + /// This helps prefetch information ahead of time for cache lookup. It does require a bit of initial setup to ensure + /// files are available and ready to be used. Note we will not download Blender until we receive user invocation to do so. + pub fn fetch( + download_path: impl AsRef, + cache: &mut PageCache, + ) -> Result { + // TODO: Remove unwrap(). Could this be made into static/singleton/OnceCell? + let parent = Url::parse(Self::ROOT_URL).unwrap(); + + // we fetch the content from the website above. + let content = cache + .fetch_or_update(&parent) + .map_err(ManagerError::IoError)?; + + // Omit any blender version 2.8 and below + // TODO: BUG: It's not omitting version 2.8 and below. Would like to omit any version 3.8 and below for now. + let iter = regex_captures_iter!( + r#"Blender(?[3-9]|\d{1,}).(?\d*)/"#, + &content + ); + + let mut list = iter.map(|c| c.extract()).fold( + Vec::new(), + |mut map: Vec, (_, [url, major, minor])| { + if let Some(category) = Portal::generate_blender_category( + &parent, + url, + major, + minor, + download_path.as_ref(), + cache, + ) { + map.push(category); + } + map + }, + ); + + list.sort_by(|a, b| b.cmp(a)); + Ok(Self::new(download_path.as_ref().to_path_buf(), list)) + } + + // TODO: Find a better way to deal with this + // why do i want to get blender state? + fn get_blender_state_by_version(&mut self, version: &Version) -> Option<&mut BlenderCategory> { + // need to pop the element from the collection. + self.list.iter_mut().fold(None, |result, item| { + let current_version = item.get_version(); + + if current_version.major.ne(&version.major) { + return result; + } + + if version.minor != 0 && current_version.minor.ne(&version.minor) { + return result; + } + + if let Some(latest) = &result { + if latest.get_version().le(¤t_version) { + return result; + } + } + + Some(item) + }) + } + + pub fn get_downloads(&self) -> Vec<&Package> { + let mut result = Vec::with_capacity(self.list.capacity()); + for item in &self.list { + let mut col = item.get_packages(); + result.append(&mut col); + } + result + } + + pub fn check_compressed_blender_by_file_name(&self, zip_file_name: &str) -> Option { + self.list.iter().fold(None, |_ , category| { + category.get_packages().iter().find_map(|package| { + let path = match package { + Package::Downloaded(downloaded) => Some(downloaded.content.clone()), + Package::Bundle(bundle) => Some(bundle.content.content.clone()), + _ => None, + }; + + if let Some(zip) = &path { + if zip.eq(zip_file_name) { + return path; + } + } + None + }) + }) + } + + /// retrieve the blender executable if it's already downloaded, otherwise download the executable and return Blender instance. + /// Should we download the blender instances from the internet? + #[deprecated(note = "This is not used? Is this true?")] + #[allow(dead_code)] + pub fn fetch_blender(&mut self, version: &Version) -> Result { + let download_path = self.download_path.clone(); + if let Some(category) = self.get_blender_state_by_version(version) { + return category + .get_blender(&download_path, version) + .map_err(ManagerError::Category); + } + + Err(ManagerError::FetchError("Unknown, reached EOF!".to_owned())) + } + + /// Download Blender of matching version, install on this machine, and returns blender struct. + /// This function will update PageCache if not previously visited. Hence mutation requirement. + // TODO: could this be made async? + pub(crate) fn download_blender(&mut self, version: &Version) -> Result { + // TODO: As a extra security measure, I would like to verify the hash of the content before extracting the files. + // Main reason for fetching consts lib was to identify the host target hardware machine to provide extended diagnostic to manager for more info debugging through. + let download_path = &self.download_path.clone(); + + let category = + self.get_blender_state_by_version(version) + .ok_or(ManagerError::DownloadNotFound { + arch: ARCH.to_owned(), + os: OS.to_owned(), + url: format!( + "Blender version {}.{} for {}-{} was not found!", + version.major, version.minor, OS, ARCH + ), + })?; + // generate a destination for the folder path + // e.g. ~/Downloads/Blender/Blender4.3/ + let destination = download_path.join(Self::get_parent(version.major, version.minor)); + category + .get_blender(destination, &version) + .map_err(ManagerError::Category) + } +} diff --git a/blender_rs/src/utils.rs b/blender_rs/src/utils.rs new file mode 100644 index 00000000..b5dd63f2 --- /dev/null +++ b/blender_rs/src/utils.rs @@ -0,0 +1,50 @@ +#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] +use std::env::consts::ARCH; +#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] +use std::env::consts::OS; +use std::{path::PathBuf, sync::OnceLock}; + +static EXT: OnceLock = OnceLock::new(); +static ARCH: OnceLock = OnceLock::new(); + +/// Return extension matching to the current operating system. Windows(zip), Linux(tar.xz), or MacOS(dmg) +/// This will return extension name without the initial period. Any period is treated as extension of extension (e.g. tar.xz) +#[inline] +pub(crate) fn get_extension() -> Result<&'static str, &'static str> { + #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))] + return Err(OS); + Ok(&EXT.get_or_init(|| { + #[cfg(target_os = "windows")] + return "zip".to_owned(); + #[cfg(target_os = "macos")] + return "dmg".to_owned(); + #[cfg(target_os = "linux")] + return "tar.xz".to_owned(); + })) +} + +/// Fetch Valid architecture. "x64" or "arm64"(apple silicon) +#[inline] +pub(crate) fn get_valid_arch() -> Result<&'static str, &'static str> { + #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] + return Err(ARCH); + Ok(&ARCH.get_or_init(|| { + #[cfg(target_arch = "x86_64")] + return "x64".to_owned(); + #[cfg(target_arch = "aarch64")] + return "arm64".to_owned(); + })) +} + +/// Fetch the configuration path for blender. +/// This is used to store temporary files and configuration files for blender. +/// TODO: Consider loading this from user preferences? +pub(crate) fn get_config_path() -> PathBuf { + dirs::config_dir().unwrap().join("BlendFarm") +} + +// TODO: this is ugly, and I want to get rid of this. How can I improve this? +// Backstory: Win and linux can be invoked via their direct app link. However, MacOS .app is just a bundle, which contains the executable inside. +// To run process::Command, I must properly reference the executable path inside the blender.app on MacOS, using the hardcoded path below. +#[cfg(target_os="macos")] +pub(crate) const MACOS_PATH: &str = "Contents/MacOS/Blender"; diff --git a/obsidian/.obsidian/app.json b/obsidian/.obsidian/app.json new file mode 100644 index 00000000..e609a07e --- /dev/null +++ b/obsidian/.obsidian/app.json @@ -0,0 +1,3 @@ +{ + "promptDelete": false +} \ No newline at end of file diff --git a/obsidian/.obsidian/appearance.json b/obsidian/.obsidian/appearance.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/obsidian/.obsidian/appearance.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/obsidian/.obsidian/core-plugins.json b/obsidian/.obsidian/core-plugins.json new file mode 100644 index 00000000..436f43cf --- /dev/null +++ b/obsidian/.obsidian/core-plugins.json @@ -0,0 +1,30 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "properties": false, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": false +} \ No newline at end of file diff --git a/obsidian/.obsidian/workspace.json b/obsidian/.obsidian/workspace.json new file mode 100644 index 00000000..bd002b6c --- /dev/null +++ b/obsidian/.obsidian/workspace.json @@ -0,0 +1,185 @@ +{ + "main": { + "id": "8feadfda19abf729", + "type": "split", + "children": [ + { + "id": "851a88eb97dcae8a", + "type": "tabs", + "children": [ + { + "id": "1610ed8efcefe535", + "type": "leaf", + "state": { + "type": "empty", + "state": {}, + "icon": "lucide-file", + "title": "New tab" + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "9a6ebaaf46a183c1", + "type": "split", + "children": [ + { + "id": "6773760fa693399a", + "type": "tabs", + "children": [ + { + "id": "4cd97139ec477c9a", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + }, + "icon": "lucide-folder-closed", + "title": "Files" + } + }, + { + "id": "7a0a8e5d4082139f", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "Search" + } + }, + { + "id": "225092051244b87c", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "Bookmarks" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "60591356ab10c700", + "type": "split", + "children": [ + { + "id": "3c3953cdabdfb81d", + "type": "tabs", + "children": [ + { + "id": "8b521025b8d5e6d8", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Backlinks" + } + }, + { + "id": "d9ed51fe2faaa56c", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Outgoing links" + } + }, + { + "id": "7382b9f98d4a9384", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + }, + "icon": "lucide-tags", + "title": "Tags" + } + }, + { + "id": "acabdbc43090b872", + "type": "leaf", + "state": { + "type": "outline", + "state": { + "file": "blendfarm/Bugs/Unit test fail - cannot validate .blend file path.md" + }, + "icon": "lucide-list", + "title": "Outline of Unit test fail - cannot validate .blend file path" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "switcher:Open quick switcher": false, + "graph:Open graph view": false, + "canvas:Create new canvas": false, + "daily-notes:Open today's daily note": false, + "templates:Insert template": false, + "command-palette:Open command palette": false + } + }, + "active": "1610ed8efcefe535", + "lastOpenFiles": [ + "blendfarm/Bugs/Unable to discover localhost with no internet connection is established or provided..md", + "blendfarm/Bugs/Node identification not store in database.md", + "blendfarm/Bugs/Render not saved to database.md", + "blendfarm/Task/Features.md", + "blendfarm/Bugs/Unit test fail - symbol _EMBED_INFO_PLIST already defined.md", + "blendfarm/Bugs/Deleting Blender from UI cause app to crash..md", + "blendfarm/Bugs/Program cannot discover itself on the same network.md", + "blendfarm/Task/Task.md", + "blendfarm/Images/RenderJobDialog.png", + "blendfarm/Images/RenderJobDialog.png", + "blendfarm/Images/SettingPage.png", + "blendfarm/Images/RemoteJobPage.png", + "blendfarm/Pages/Settings.md", + "blendfarm/Pages/Render Job window.md", + "blendfarm/Pages/Remote Render.md", + "blendfarm/Task/TODO.md", + "blendfarm/Context.md", + "blendfarm/Network code notes.md", + "blendfarm/Yamux.md", + "blendfarm/Bugs/Import Job does nothing.md", + "blendfarm/Bugs/Cannot open dialog.md", + "main/Untitled.md", + "main/Main Story.md" + ] +} \ No newline at end of file diff --git a/obsidian/blendfarm/.obsidian/app.json b/obsidian/blendfarm/.obsidian/app.json new file mode 100644 index 00000000..5b10960c --- /dev/null +++ b/obsidian/blendfarm/.obsidian/app.json @@ -0,0 +1,5 @@ +{ + "alwaysUpdateLinks": true, + "promptDelete": false, + "useMarkdownLinks": true +} \ No newline at end of file diff --git a/obsidian/blendfarm/.obsidian/appearance.json b/obsidian/blendfarm/.obsidian/appearance.json new file mode 100644 index 00000000..5a3f401a --- /dev/null +++ b/obsidian/blendfarm/.obsidian/appearance.json @@ -0,0 +1,4 @@ +{ + "accentColor": "", + "theme": "obsidian" +} \ No newline at end of file diff --git a/obsidian/blendfarm/.obsidian/core-plugins-migration.json b/obsidian/blendfarm/.obsidian/core-plugins-migration.json new file mode 100644 index 00000000..436f43cf --- /dev/null +++ b/obsidian/blendfarm/.obsidian/core-plugins-migration.json @@ -0,0 +1,30 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "properties": false, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": false +} \ No newline at end of file diff --git a/obsidian/blendfarm/.obsidian/core-plugins.json b/obsidian/blendfarm/.obsidian/core-plugins.json new file mode 100644 index 00000000..8e719d83 --- /dev/null +++ b/obsidian/blendfarm/.obsidian/core-plugins.json @@ -0,0 +1,33 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "properties": false, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": false, + "webviewer": false, + "footnotes": false, + "bases": true +} \ No newline at end of file diff --git a/obsidian/blendfarm/.obsidian/graph.json b/obsidian/blendfarm/.obsidian/graph.json new file mode 100644 index 00000000..564de653 --- /dev/null +++ b/obsidian/blendfarm/.obsidian/graph.json @@ -0,0 +1,22 @@ +{ + "collapse-filter": true, + "search": "", + "showTags": false, + "showAttachments": false, + "hideUnresolved": false, + "showOrphans": true, + "collapse-color-groups": true, + "colorGroups": [], + "collapse-display": true, + "showArrow": false, + "textFadeMultiplier": 0, + "nodeSizeMultiplier": 1, + "lineSizeMultiplier": 1, + "collapse-forces": true, + "centerStrength": 0.518713248970312, + "repelStrength": 10, + "linkStrength": 1, + "linkDistance": 250, + "scale": 0.9070762383010631, + "close": true +} \ No newline at end of file diff --git a/obsidian/blendfarm/.obsidian/workspace.json b/obsidian/blendfarm/.obsidian/workspace.json new file mode 100644 index 00000000..74949841 --- /dev/null +++ b/obsidian/blendfarm/.obsidian/workspace.json @@ -0,0 +1,199 @@ +{ + "main": { + "id": "78337d635dbb6873", + "type": "split", + "children": [ + { + "id": "4b403841bfcfb5d5", + "type": "tabs", + "children": [ + { + "id": "1351db734f3d900e", + "type": "leaf", + "state": { + "type": "empty", + "state": {}, + "icon": "lucide-file", + "title": "New tab" + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "e6315f2e9a577efa", + "type": "split", + "children": [ + { + "id": "eca3db1b7ab0c78d", + "type": "tabs", + "children": [ + { + "id": "b8e74c2efd380365", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical", + "autoReveal": false + }, + "icon": "lucide-folder-closed", + "title": "Files" + } + }, + { + "id": "73232a02b1c2e739", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + }, + "icon": "lucide-search", + "title": "Search" + } + }, + { + "id": "137e05f70b72093a", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {}, + "icon": "lucide-bookmark", + "title": "Bookmarks" + } + } + ] + } + ], + "direction": "horizontal", + "width": 490.5 + }, + "right": { + "id": "2cc1b4442ff01725", + "type": "split", + "children": [ + { + "id": "6cfb792e40cb1461", + "type": "tabs", + "children": [ + { + "id": "4a7e73098dd67e05", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-coming-in", + "title": "Backlinks" + } + }, + { + "id": "cbd94ab7fb0d96c5", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "linksCollapsed": false, + "unlinkedCollapsed": true + }, + "icon": "links-going-out", + "title": "Outgoing links" + } + }, + { + "id": "c81e9aaf518413f3", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + }, + "icon": "lucide-tags", + "title": "Tags" + } + }, + { + "id": "4c4e869fbb38e6d7", + "type": "leaf", + "state": { + "type": "outline", + "state": {}, + "icon": "lucide-list", + "title": "Outline" + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "bases:Create new base": false, + "switcher:Open quick switcher": false, + "graph:Open graph view": false, + "canvas:Create new canvas": false, + "daily-notes:Open today's daily note": false, + "templates:Insert template": false, + "command-palette:Open command palette": false + } + }, + "active": "1351db734f3d900e", + "lastOpenFiles": [ + "Architecture/Portal.md", + "Bugs/Deleting Blender from UI cause app to crash..md", + "Bugs/Program cannot discover itself on the same network.md", + "Bugs/Render not saved to database.md", + "Bugs/Unable to discover localhost with no internet connection is established or provided..md", + "Bugs/Unit test fail - symbol _EMBED_INFO_PLIST already defined.md", + "Bugs/Buglist.md", + "Pages/Pagelist.md", + "Pages/Remote Render.md", + "Pages/Render Job window.md", + "Pages/Settings.md", + "Images/SettingPage.png", + "Images/RenderJobDialog.png", + "Images/RemoteJobPage.png", + "Features/Exchange IP address from client to render.py.md", + "Bugs/Node identification not store in database.md", + "Home.md", + "Task/TODO.md", + "Task/Task.md", + "Task/Features.md", + "Architecture", + "Features", + "README.md", + "Network code notes.md", + "Yamux.md", + "Job list disappear after switching window.md", + "Makefile.md", + "About.md", + "Bugs/Import Job does nothing.md", + "Bugs/Unit test fail - cannot validate .blend file path.md", + "Images/dialog_open_bug.png", + "Bugs/Cannot open dialog.md", + "Images/Setting_page.png", + "Images", + "Pages", + "Task", + "Bugs" + ] +} \ No newline at end of file diff --git a/obsidian/blendfarm/Architecture/Portal.md b/obsidian/blendfarm/Architecture/Portal.md new file mode 100644 index 00000000..4c28088f --- /dev/null +++ b/obsidian/blendfarm/Architecture/Portal.md @@ -0,0 +1,4 @@ +This struct is design to handle and manage online services to download, fetch, and install blender. +This can be treated as a way to fetch blender across the network. + +TODO: Implement key information to distribute blender executable via intranet using DHT services. (Ask for blender Downloads) diff --git a/obsidian/blendfarm/Bugs/Buglist.md b/obsidian/blendfarm/Bugs/Buglist.md new file mode 100644 index 00000000..1f7098e9 --- /dev/null +++ b/obsidian/blendfarm/Bugs/Buglist.md @@ -0,0 +1,9 @@ +[Deleting Blender from UI cause app to crash.](Deleting%20Blender%20from%20UI%20cause%20app%20to%20crash..md) +[Node identification not store in database](Node%20identification%20not%20store%20in%20database.md) +[Program cannot discover itself on the same network](Program%20cannot%20discover%20itself%20on%20the%20same%20network.md) +[Render not saved to database](Render%20not%20saved%20to%20database.md) +[Unable to discover localhost with no internet connection is established or provided.](Unable%20to%20discover%20localhost%20with%20no%20internet%20connection%20is%20established%20or%20provided..md) +[Unit test fail - symbol _EMBED_INFO_PLIST already defined](Unit%20test%20fail%20-%20symbol%20_EMBED_INFO_PLIST%20already%20defined.md) + + +Todo: \ No newline at end of file diff --git a/obsidian/blendfarm/Bugs/Deleting Blender from UI cause app to crash..md b/obsidian/blendfarm/Bugs/Deleting Blender from UI cause app to crash..md new file mode 100644 index 00000000..193a6c51 --- /dev/null +++ b/obsidian/blendfarm/Bugs/Deleting Blender from UI cause app to crash..md @@ -0,0 +1,6 @@ +Seems like the code was not implemented to delete local content of blender file. +We should provide a dialog asking user to disconnect blender link or delete local content where blender is store/installed. + +Expected behaviour - when user deletes blender from the settings.rs, it should delete the blender content from the local machine and clear the row entry from settings page (Refresh/update?). + +Actual behaviour - Program will crashed on macos - we need to verify that the path is correct and not linked to the executable inside appbundle \ No newline at end of file diff --git a/obsidian/blendfarm/Bugs/Node identification not store in database.md b/obsidian/blendfarm/Bugs/Node identification not store in database.md new file mode 100644 index 00000000..6b4e1f37 --- /dev/null +++ b/obsidian/blendfarm/Bugs/Node identification not store in database.md @@ -0,0 +1,3 @@ +Expected behaviour - when a client becomes available and connected to the manager, a new record is added to the database containing computer information in JSON format. + +Actual behaviour - no record is stored when a node is discovered and established. \ No newline at end of file diff --git a/obsidian/blendfarm/Bugs/Program cannot discover itself on the same network.md b/obsidian/blendfarm/Bugs/Program cannot discover itself on the same network.md new file mode 100644 index 00000000..f0eb9e57 --- /dev/null +++ b/obsidian/blendfarm/Bugs/Program cannot discover itself on the same network.md @@ -0,0 +1,8 @@ +If the wifi connection is disabled and there are no other network bridge/adapter, this program cannot identify itself. + +Expected behaviour - When starting up both manager and client (order does not matter) - The program should be able to establish connection while in offline mode. It shouldn't be able to peer out internet connection, but it should simply invoke the job when resources are available locally. + +Actual behaviour - The program continues to fail to send message out stating "NoPeersSubscribedToTopic" and unable to discover each other node in offline mode. Both manager and client fail to discover each other, despite listening on correct address and port. (No loopback?) + +Thoughts: +If we want to run manager and client on the same machine then ideally we'd use manager to invoke the client via cli ways. \ No newline at end of file diff --git a/obsidian/blendfarm/Bugs/Render not saved to database.md b/obsidian/blendfarm/Bugs/Render not saved to database.md new file mode 100644 index 00000000..7ab38392 --- /dev/null +++ b/obsidian/blendfarm/Bugs/Render not saved to database.md @@ -0,0 +1,3 @@ +Expected behaviour - Whenever client node completes a render image, before broadcasting out to the network for status update, a new record is appended to database containing information related to the job task. This information should be persistent across app lifespan. + +Actual Behaviour - No data is saved to the database. \ No newline at end of file diff --git a/obsidian/blendfarm/Bugs/Unable to discover localhost with no internet connection is established or provided..md b/obsidian/blendfarm/Bugs/Unable to discover localhost with no internet connection is established or provided..md new file mode 100644 index 00000000..764a1fec --- /dev/null +++ b/obsidian/blendfarm/Bugs/Unable to discover localhost with no internet connection is established or provided..md @@ -0,0 +1,7 @@ +Currently in the offline mode (Airplane mode/wifi turned off/no ethernet adapter/etc), having both manager and client will not discover itself. + +Todo - contact the community or research online on how to achieve loopback rules for the firewall? I thought it was possible to communicate through a separate channel? + +Expected behavior, Both manager and client should discover itself and begin communication within 0.0.0.0 address + +Actual behavior: Client and manager unable to find each other. \ No newline at end of file diff --git a/obsidian/blendfarm/Bugs/Unit test fail - symbol _EMBED_INFO_PLIST already defined.md b/obsidian/blendfarm/Bugs/Unit test fail - symbol _EMBED_INFO_PLIST already defined.md new file mode 100644 index 00000000..77a02e26 --- /dev/null +++ b/obsidian/blendfarm/Bugs/Unit test fail - symbol _EMBED_INFO_PLIST already defined.md @@ -0,0 +1,23 @@ +Currently unit test fails when generating a new context. I am not sure why I received this error message? I'm on a airplane with no wifi or internet connection whatsoever, so this makes troubleshooting a bit difficult to perform while in air. + +Expected behaviour - Should be able to run unit test and return result. + +Actual behaviour - unable to run unit test as the compiler complains about symbol embed_info_plist is already defined. + +**error****: symbol `_EMBED_INFO_PLIST` is already defined** + +   **-->** src/routes/job.rs:301:23 + +    **|** + +**301** **|**         let context = tauri::generate_context!("tauri.conf.json"); + +    **|**                       **^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^** + +    **|** + +    **=** **note**: this error originates in the macro `$crate::embed_info_plist_bytes` which comes from the expansion of the macro `tauri::generate_context` (in Nightly builds, run with -Z macro-backtrace for more info) + + +TODO: +Try running with `-Z macro-backtrace` Chances are, need to clean and rebuild mac directory. Don't think I've ran into this problem again? Verify this. \ No newline at end of file diff --git a/obsidian/blendfarm/Features/Exchange IP address from client to render.py.md b/obsidian/blendfarm/Features/Exchange IP address from client to render.py.md new file mode 100644 index 00000000..9420911d --- /dev/null +++ b/obsidian/blendfarm/Features/Exchange IP address from client to render.py.md @@ -0,0 +1 @@ +Accepts ip and port argument input. Reject all other for now, for future features. \ No newline at end of file diff --git a/obsidian/blendfarm/Home.md b/obsidian/blendfarm/Home.md new file mode 100644 index 00000000..e6c3d17d --- /dev/null +++ b/obsidian/blendfarm/Home.md @@ -0,0 +1,7 @@ +[About](README.md) +[Features](./Task/Features.md) +[TODO](./Task/TODO.md) +[Task](./Task/Task.md) +[Buglist](Buglist.md) +[Pagelist](Pagelist.md) +[Network code notes](Network%20code%20notes.md) diff --git a/obsidian/blendfarm/Images/RemoteJobPage.png b/obsidian/blendfarm/Images/RemoteJobPage.png new file mode 100644 index 00000000..dbd6581f Binary files /dev/null and b/obsidian/blendfarm/Images/RemoteJobPage.png differ diff --git a/obsidian/blendfarm/Images/RenderJobDialog.png b/obsidian/blendfarm/Images/RenderJobDialog.png new file mode 100644 index 00000000..ac257095 Binary files /dev/null and b/obsidian/blendfarm/Images/RenderJobDialog.png differ diff --git a/obsidian/blendfarm/Images/SettingPage.png b/obsidian/blendfarm/Images/SettingPage.png new file mode 100644 index 00000000..e6f9652b Binary files /dev/null and b/obsidian/blendfarm/Images/SettingPage.png differ diff --git a/obsidian/blendfarm/Network code notes.md b/obsidian/blendfarm/Network code notes.md new file mode 100644 index 00000000..c80bfafd --- /dev/null +++ b/obsidian/blendfarm/Network code notes.md @@ -0,0 +1,12 @@ +Server must first create a socket object +then we bind the socket to the address, port. +afterward, we listen to the ports. +finally, we accept the listen call. + +client must create socket object, makes connection. +client will "connect" through socket. +client must connect to the same ip:port as the server socket. + +once the server accepts, we now have a connection established between client and server to freely exchange data between those two connections. + +socket file descriptor that can write and read data. diff --git a/obsidian/blendfarm/Pages/Pagelist.md b/obsidian/blendfarm/Pages/Pagelist.md new file mode 100644 index 00000000..55b671cf --- /dev/null +++ b/obsidian/blendfarm/Pages/Pagelist.md @@ -0,0 +1,3 @@ +[Remote Render](Remote%20Render.md) +[Render Job window](Render%20Job%20window.md) +[Settings](Settings.md) diff --git a/obsidian/blendfarm/Pages/Remote Render.md b/obsidian/blendfarm/Pages/Remote Render.md new file mode 100644 index 00000000..cab5dec3 --- /dev/null +++ b/obsidian/blendfarm/Pages/Remote Render.md @@ -0,0 +1,5 @@ +![[RemoteJobPage.png]] +This page display all jobs that this server utilize. You can click on the individual job run to see more detail information below. + +Features: +Clicking on Blendfile path opens blender with provided scene file. diff --git a/obsidian/blendfarm/Pages/Render Job window.md b/obsidian/blendfarm/Pages/Render Job window.md new file mode 100644 index 00000000..d62e11e7 --- /dev/null +++ b/obsidian/blendfarm/Pages/Render Job window.md @@ -0,0 +1,9 @@ +![[RenderJobDialog.png]] +This window appears when you create a new job from the remote render setting page. + +Project file path allows user to select which project to create a new render job from. Once a job is created, this path will be used to distribute the project file to the client node. +Rendering mode lets the user define what job this is. There are currently two options for now, Frame and animation. +Frame will let the user pick a frame from the project file and start a rendering job to render that scene's target frame window. + +Feature: +Find a way to load project information for which camera/frame to select from? \ No newline at end of file diff --git a/obsidian/blendfarm/Pages/Settings.md b/obsidian/blendfarm/Pages/Settings.md new file mode 100644 index 00000000..5fb0339f --- /dev/null +++ b/obsidian/blendfarm/Pages/Settings.md @@ -0,0 +1,17 @@ +![[SettingPage.png]] +This page will list out all of the configuration this program can provide to the user. This allows to define custom blender installation path - render cache path to store all completed job renders, and local blender installation. + +The Blender Installation Path outlines where the program can download and install blender from Blender's download page (https://download.blender.org/release) + +[Obsolete(This feature is only meant for the client to utilize, host have no purposes for this?)] +Blender File Cache Path is used for the client computer to utilize where to store and keep incoming project files from the server (host) + +Render Cache Directory is used to store and keep all of the completed render images from the host and client node. When the client is completed with the render job, the client will send the completed image to the host, and the host will store the image to the provided path. + +Blender Installation + +Add from local storage lets the user of the machine to locally point to Blender installed path. (Dev - should we also distribute this to other client if os/arch matches?) + +Install version expose all of blender's latest version available from the website, and install Blender automatically for the user. + +The list below display all known blender installation the program can utilize and access. This list will only appear after validating Blender's executable path. (Feature - Allow user to run blender from here?) \ No newline at end of file diff --git a/obsidian/blendfarm/README.md b/obsidian/blendfarm/README.md new file mode 100644 index 00000000..85f5cfe7 --- /dev/null +++ b/obsidian/blendfarm/README.md @@ -0,0 +1 @@ +Blendfarm is a network service application, similar to flamango, but with memory safety in mind for high level applications and maintain uptime distribution across system schematics. \ No newline at end of file diff --git a/obsidian/blendfarm/Task/Features.md b/obsidian/blendfarm/Task/Features.md new file mode 100644 index 00000000..b7974fd0 --- /dev/null +++ b/obsidian/blendfarm/Task/Features.md @@ -0,0 +1,8 @@ +[ ] - Find a way to allow GUI interface to run as client mode for non cli users. +[ ] - Consider using channel to stream data - https://v2.tauri.app/develop/calling-frontend/#channels +[ ] - Before release - find a way to add an auto updater? https://v2.tauri.app/plugin/updater/ +[ ] - Provide user feedback when download/installing blender from the web. +[ ] - Implement FFmpeg usage so that we can generate preview gif images within our preview window. +[ ] - Write a python plugin to display Blender Manager from blender. We could operate blendfarm as cli mode within blender? + []- CLI mode implemented. Need to implement Server side parser. TODO: Look into python ffi +[ ] - Allow FFI interface to blenderManager from blender using python as a add-on scripts. diff --git a/obsidian/blendfarm/Task/TODO.md b/obsidian/blendfarm/Task/TODO.md new file mode 100644 index 00000000..95af2c30 --- /dev/null +++ b/obsidian/blendfarm/Task/TODO.md @@ -0,0 +1,9 @@ +Get network iron out and established. - Need to make a flow diagram of network support and how this is suppose to be treated. + +Job - display job event +Node - display node activity + +Update pages and image to reflect new UI layout design + +Currently the manager can send the job to the client and can successfully run the run on the client. However the client isn't sending the job info to the manager machine. The job completes, with render details information stored in database, but there's no calling to fetch the image to the host machine or send information about the node status/completion of the job. + diff --git a/obsidian/blendfarm/Task/Task.md b/obsidian/blendfarm/Task/Task.md new file mode 100644 index 00000000..d7233d8a --- /dev/null +++ b/obsidian/blendfarm/Task/Task.md @@ -0,0 +1,16 @@ +Handle conditions where the blendfarm is running offline. +- Let user continue to run the program, but notify the user they're running offline. +- Currently - offline mode cause panic- prevents application from starting up. + +Currently, unable to send files to other server, or run jobs locally? + +Clean up remote_render.tsx view so that it shows a table at the top - then a general overview of information display below + +Find a way to run unit test - Here are a few examples. +- starting server upp +- connect to client +- send a temp file +- test render +- expect a image back. + +Finalize setting page. What needs to be done there? \ No newline at end of file diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b2a3deee..35b29b4c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "blendfarm" "authors" = ["Jordan Bejar"] -description = "A open-source, cross-platform, stand-alone Network Render farm for Blender" +description = "A Network Render Farm Manager and Service" license = "MIT" repository = "https://github.com/tiberiumboy/BlendFarm" -edition = "2021" -version = "0.1.0" +edition = "2024" +version = "0.1.2" [lib] name = "blenderfarm_lib" @@ -19,11 +19,11 @@ lto = "thin" [profile.release] lto = true strip = true -opt-level = "z" +opt-level = "s" panic = "abort" codegen-units = 1 incremental = true -debug = 0 +# debug = 0 [profile.dev.package.sqlx-macros] opt-level = 3 @@ -33,12 +33,7 @@ opt-level = 3 tauri-build = { version = "^2.0", features = [] } [dependencies] -anyhow = "^1.0.95" -async-trait = "^0.1.86" -async-std = "^1.13" -blend = "^0.8" -blender = { path = "./../blender/" } -libp2p = { version = "^0.55", features = [ +libp2p = { version = "^0.56", features = [ "mdns", "macros", "gossipsub", @@ -51,10 +46,17 @@ libp2p = { version = "^0.55", features = [ "quic", "kad", ] } -libp2p-request-response = { version = "^0.28", features = ["cbor"] } -bincode = "1.3.3" +anyhow = "^1.0" dirs = "^6.0" -semver = "^1.0.25" +async-trait = "^0.1" +async-std = "^1.13" +blend = "^0.8" +blender = { path = "./../blender_rs/" } +postcard = "^1.1.3" +dunce = "^1.0" +libp2p-request-response = { version = "^0.29", features = ["cbor"] } +futures = "^0.3" +semver = "^1.0" # Use to extract system information machine-info = "^1.0.9" thiserror = "^2.0.11" @@ -64,30 +66,35 @@ tauri-plugin-os = "^2.2" tauri-plugin-persisted-scope = "^2.2" tauri-plugin-shell = "^2.2" tokio = { version = "^1.43", features = ["full"] } -clap = { version = "^4.5.29", features = ["derive"] } -futures = "0.3.31" -sqlx = { version = "0.8.2", features = [ +clap = { version = "^4.5", features = ["derive"] } +sqlx = { version = "^0.8", features = [ "runtime-tokio", "tls-native-tls", "sqlite", + "uuid", + "json", ] } -tauri-plugin-sql = { version = "2", features = ["sqlite"] } -dotenvy = "0.15.7" +dotenvy = "^0.15" # TODO: Compile restriction: Test and deploy using stable version of Rust! Recommends development on Nightly releases -maud = "0.27.0" +maud = "^0.27" +urlencoding = "^2.1" +bitflags = "2.10.0" # this came autogenerated. I don't think I will develop this in the future, but would consider this as an april fools joke. Yes I totally would. [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] -tauri-plugin-cli = "^2.2.0" -tauri = { version = "^2.2.5", features = ["protocol-asset"] } -serde = { version = "^1.0.217", features = ["derive"] } -serde_json = "^1.0.138" -uuid = { version = "^1.13.1", features = [ +tauri-plugin-cli = "^2.2" +tauri = { version = "^2.6", features = ["protocol-asset", "tray-icon", "test"] } +serde = { version = "^1.0", features = ["derive"] } +serde_json = "^1.0" +uuid = { version = "^1.3", features = [ "v4", "fast-rng", "macro-diagnostics", "serde", ] } +[dev-dependencies] +ntest = "*" + # [build] # rustflags = ["-C", "link-arg=-fuse-ld=lld"] diff --git a/src-tauri/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json deleted file mode 100644 index 72cdddca..00000000 --- a/src-tauri/gen/schemas/acl-manifests.json +++ /dev/null @@ -1 +0,0 @@ -{"cli":{"default_permission":{"identifier":"default","description":"Allows reading the CLI matches","permissions":["allow-cli-matches"]},"permissions":{"allow-cli-matches":{"identifier":"allow-cli-matches","description":"Enables the cli_matches command without any pre-configured scope.","commands":{"allow":["cli_matches"],"deny":[]}},"deny-cli-matches":{"identifier":"deny-cli-matches","description":"Denies the cli_matches command without any pre-configured scope.","commands":{"allow":[],"deny":["cli_matches"]}}},"permission_sets":{},"global_scope_schema":null},"core":{"default_permission":{"identifier":"default","description":"Default core plugins set which includes:\n- 'core:path:default'\n- 'core:event:default'\n- 'core:window:default'\n- 'core:webview:default'\n- 'core:app:default'\n- 'core:image:default'\n- 'core:resources:default'\n- 'core:menu:default'\n- 'core:tray:default'\n","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"dialog":{"default_permission":{"identifier":"default","description":"This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n","permissions":["allow-ask","allow-confirm","allow-message","allow-save","allow-open"]},"permissions":{"allow-ask":{"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope.","commands":{"allow":["ask"],"deny":[]}},"allow-confirm":{"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope.","commands":{"allow":["confirm"],"deny":[]}},"allow-message":{"identifier":"allow-message","description":"Enables the message command without any pre-configured scope.","commands":{"allow":["message"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-save":{"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]}},"deny-ask":{"identifier":"deny-ask","description":"Denies the ask command without any pre-configured scope.","commands":{"allow":[],"deny":["ask"]}},"deny-confirm":{"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope.","commands":{"allow":[],"deny":["confirm"]}},"deny-message":{"identifier":"deny-message","description":"Denies the message command without any pre-configured scope.","commands":{"allow":[],"deny":["message"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-save":{"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]}}},"permission_sets":{},"global_scope_schema":null},"fs":{"default_permission":{"identifier":"default","description":"This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n","permissions":["create-app-specific-dirs","read-app-specific-dirs-recursive","deny-default"]},"permissions":{"allow-copy-file":{"identifier":"allow-copy-file","description":"Enables the copy_file command without any pre-configured scope.","commands":{"allow":["copy_file"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-exists":{"identifier":"allow-exists","description":"Enables the exists command without any pre-configured scope.","commands":{"allow":["exists"],"deny":[]}},"allow-fstat":{"identifier":"allow-fstat","description":"Enables the fstat command without any pre-configured scope.","commands":{"allow":["fstat"],"deny":[]}},"allow-ftruncate":{"identifier":"allow-ftruncate","description":"Enables the ftruncate command without any pre-configured scope.","commands":{"allow":["ftruncate"],"deny":[]}},"allow-lstat":{"identifier":"allow-lstat","description":"Enables the lstat command without any pre-configured scope.","commands":{"allow":["lstat"],"deny":[]}},"allow-mkdir":{"identifier":"allow-mkdir","description":"Enables the mkdir command without any pre-configured scope.","commands":{"allow":["mkdir"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-read":{"identifier":"allow-read","description":"Enables the read command without any pre-configured scope.","commands":{"allow":["read"],"deny":[]}},"allow-read-dir":{"identifier":"allow-read-dir","description":"Enables the read_dir command without any pre-configured scope.","commands":{"allow":["read_dir"],"deny":[]}},"allow-read-file":{"identifier":"allow-read-file","description":"Enables the read_file command without any pre-configured scope.","commands":{"allow":["read_file"],"deny":[]}},"allow-read-text-file":{"identifier":"allow-read-text-file","description":"Enables the read_text_file command without any pre-configured scope.","commands":{"allow":["read_text_file"],"deny":[]}},"allow-read-text-file-lines":{"identifier":"allow-read-text-file-lines","description":"Enables the read_text_file_lines command without any pre-configured scope.","commands":{"allow":["read_text_file_lines","read_text_file_lines_next"],"deny":[]}},"allow-read-text-file-lines-next":{"identifier":"allow-read-text-file-lines-next","description":"Enables the read_text_file_lines_next command without any pre-configured scope.","commands":{"allow":["read_text_file_lines_next"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-rename":{"identifier":"allow-rename","description":"Enables the rename command without any pre-configured scope.","commands":{"allow":["rename"],"deny":[]}},"allow-seek":{"identifier":"allow-seek","description":"Enables the seek command without any pre-configured scope.","commands":{"allow":["seek"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"allow-stat":{"identifier":"allow-stat","description":"Enables the stat command without any pre-configured scope.","commands":{"allow":["stat"],"deny":[]}},"allow-truncate":{"identifier":"allow-truncate","description":"Enables the truncate command without any pre-configured scope.","commands":{"allow":["truncate"],"deny":[]}},"allow-unwatch":{"identifier":"allow-unwatch","description":"Enables the unwatch command without any pre-configured scope.","commands":{"allow":["unwatch"],"deny":[]}},"allow-watch":{"identifier":"allow-watch","description":"Enables the watch command without any pre-configured scope.","commands":{"allow":["watch"],"deny":[]}},"allow-write":{"identifier":"allow-write","description":"Enables the write command without any pre-configured scope.","commands":{"allow":["write"],"deny":[]}},"allow-write-file":{"identifier":"allow-write-file","description":"Enables the write_file command without any pre-configured scope.","commands":{"allow":["write_file","open","write"],"deny":[]}},"allow-write-text-file":{"identifier":"allow-write-text-file","description":"Enables the write_text_file command without any pre-configured scope.","commands":{"allow":["write_text_file"],"deny":[]}},"create-app-specific-dirs":{"identifier":"create-app-specific-dirs","description":"This permissions allows to create the application specific directories.\n","commands":{"allow":["mkdir","scope-app-index"],"deny":[]}},"deny-copy-file":{"identifier":"deny-copy-file","description":"Denies the copy_file command without any pre-configured scope.","commands":{"allow":[],"deny":["copy_file"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-exists":{"identifier":"deny-exists","description":"Denies the exists command without any pre-configured scope.","commands":{"allow":[],"deny":["exists"]}},"deny-fstat":{"identifier":"deny-fstat","description":"Denies the fstat command without any pre-configured scope.","commands":{"allow":[],"deny":["fstat"]}},"deny-ftruncate":{"identifier":"deny-ftruncate","description":"Denies the ftruncate command without any pre-configured scope.","commands":{"allow":[],"deny":["ftruncate"]}},"deny-lstat":{"identifier":"deny-lstat","description":"Denies the lstat command without any pre-configured scope.","commands":{"allow":[],"deny":["lstat"]}},"deny-mkdir":{"identifier":"deny-mkdir","description":"Denies the mkdir command without any pre-configured scope.","commands":{"allow":[],"deny":["mkdir"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-read":{"identifier":"deny-read","description":"Denies the read command without any pre-configured scope.","commands":{"allow":[],"deny":["read"]}},"deny-read-dir":{"identifier":"deny-read-dir","description":"Denies the read_dir command without any pre-configured scope.","commands":{"allow":[],"deny":["read_dir"]}},"deny-read-file":{"identifier":"deny-read-file","description":"Denies the read_file command without any pre-configured scope.","commands":{"allow":[],"deny":["read_file"]}},"deny-read-text-file":{"identifier":"deny-read-text-file","description":"Denies the read_text_file command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file"]}},"deny-read-text-file-lines":{"identifier":"deny-read-text-file-lines","description":"Denies the read_text_file_lines command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file_lines"]}},"deny-read-text-file-lines-next":{"identifier":"deny-read-text-file-lines-next","description":"Denies the read_text_file_lines_next command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file_lines_next"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-rename":{"identifier":"deny-rename","description":"Denies the rename command without any pre-configured scope.","commands":{"allow":[],"deny":["rename"]}},"deny-seek":{"identifier":"deny-seek","description":"Denies the seek command without any pre-configured scope.","commands":{"allow":[],"deny":["seek"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}},"deny-stat":{"identifier":"deny-stat","description":"Denies the stat command without any pre-configured scope.","commands":{"allow":[],"deny":["stat"]}},"deny-truncate":{"identifier":"deny-truncate","description":"Denies the truncate command without any pre-configured scope.","commands":{"allow":[],"deny":["truncate"]}},"deny-unwatch":{"identifier":"deny-unwatch","description":"Denies the unwatch command without any pre-configured scope.","commands":{"allow":[],"deny":["unwatch"]}},"deny-watch":{"identifier":"deny-watch","description":"Denies the watch command without any pre-configured scope.","commands":{"allow":[],"deny":["watch"]}},"deny-webview-data-linux":{"identifier":"deny-webview-data-linux","description":"This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.","commands":{"allow":[],"deny":[]}},"deny-webview-data-windows":{"identifier":"deny-webview-data-windows","description":"This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.","commands":{"allow":[],"deny":[]}},"deny-write":{"identifier":"deny-write","description":"Denies the write command without any pre-configured scope.","commands":{"allow":[],"deny":["write"]}},"deny-write-file":{"identifier":"deny-write-file","description":"Denies the write_file command without any pre-configured scope.","commands":{"allow":[],"deny":["write_file"]}},"deny-write-text-file":{"identifier":"deny-write-text-file","description":"Denies the write_text_file command without any pre-configured scope.","commands":{"allow":[],"deny":["write_text_file"]}},"read-all":{"identifier":"read-all","description":"This enables all read related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","read_file","read","open","read_text_file","read_text_file_lines","read_text_file_lines_next","seek","stat","lstat","fstat","exists","watch","unwatch"],"deny":[]}},"read-app-specific-dirs-recursive":{"identifier":"read-app-specific-dirs-recursive","description":"This permission allows recursive read functionality on the application\nspecific base directories. \n","commands":{"allow":["read_dir","read_file","read_text_file","read_text_file_lines","read_text_file_lines_next","exists","scope-app-recursive"],"deny":[]}},"read-dirs":{"identifier":"read-dirs","description":"This enables directory read and file metadata related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","stat","lstat","fstat","exists"],"deny":[]}},"read-files":{"identifier":"read-files","description":"This enables file read related commands without any pre-configured accessible paths.","commands":{"allow":["read_file","read","open","read_text_file","read_text_file_lines","read_text_file_lines_next","seek","stat","lstat","fstat","exists"],"deny":[]}},"read-meta":{"identifier":"read-meta","description":"This enables all index or metadata related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","stat","lstat","fstat","exists","size"],"deny":[]}},"scope":{"identifier":"scope","description":"An empty permission you can use to modify the global scope.","commands":{"allow":[],"deny":[]}},"scope-app":{"identifier":"scope-app","description":"This scope permits access to all files and list content of top level directories in the application folders.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/*"},{"path":"$APPDATA"},{"path":"$APPDATA/*"},{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/*"},{"path":"$APPCACHE"},{"path":"$APPCACHE/*"},{"path":"$APPLOG"},{"path":"$APPLOG/*"}]}},"scope-app-index":{"identifier":"scope-app-index","description":"This scope permits to list all files and folders in the application directories.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPDATA"},{"path":"$APPLOCALDATA"},{"path":"$APPCACHE"},{"path":"$APPLOG"}]}},"scope-app-recursive":{"identifier":"scope-app-recursive","description":"This scope permits recursive access to the complete application folders, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/**"},{"path":"$APPDATA"},{"path":"$APPDATA/**"},{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/**"},{"path":"$APPCACHE"},{"path":"$APPCACHE/**"},{"path":"$APPLOG"},{"path":"$APPLOG/**"}]}},"scope-appcache":{"identifier":"scope-appcache","description":"This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"},{"path":"$APPCACHE/*"}]}},"scope-appcache-index":{"identifier":"scope-appcache-index","description":"This scope permits to list all files and folders in the `$APPCACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"}]}},"scope-appcache-recursive":{"identifier":"scope-appcache-recursive","description":"This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"},{"path":"$APPCACHE/**"}]}},"scope-appconfig":{"identifier":"scope-appconfig","description":"This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/*"}]}},"scope-appconfig-index":{"identifier":"scope-appconfig-index","description":"This scope permits to list all files and folders in the `$APPCONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"}]}},"scope-appconfig-recursive":{"identifier":"scope-appconfig-recursive","description":"This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/**"}]}},"scope-appdata":{"identifier":"scope-appdata","description":"This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"},{"path":"$APPDATA/*"}]}},"scope-appdata-index":{"identifier":"scope-appdata-index","description":"This scope permits to list all files and folders in the `$APPDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"}]}},"scope-appdata-recursive":{"identifier":"scope-appdata-recursive","description":"This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]}},"scope-applocaldata":{"identifier":"scope-applocaldata","description":"This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/*"}]}},"scope-applocaldata-index":{"identifier":"scope-applocaldata-index","description":"This scope permits to list all files and folders in the `$APPLOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"}]}},"scope-applocaldata-recursive":{"identifier":"scope-applocaldata-recursive","description":"This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/**"}]}},"scope-applog":{"identifier":"scope-applog","description":"This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"},{"path":"$APPLOG/*"}]}},"scope-applog-index":{"identifier":"scope-applog-index","description":"This scope permits to list all files and folders in the `$APPLOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"}]}},"scope-applog-recursive":{"identifier":"scope-applog-recursive","description":"This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"},{"path":"$APPLOG/**"}]}},"scope-audio":{"identifier":"scope-audio","description":"This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"},{"path":"$AUDIO/*"}]}},"scope-audio-index":{"identifier":"scope-audio-index","description":"This scope permits to list all files and folders in the `$AUDIO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"}]}},"scope-audio-recursive":{"identifier":"scope-audio-recursive","description":"This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"},{"path":"$AUDIO/**"}]}},"scope-cache":{"identifier":"scope-cache","description":"This scope permits access to all files and list content of top level directories in the `$CACHE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"},{"path":"$CACHE/*"}]}},"scope-cache-index":{"identifier":"scope-cache-index","description":"This scope permits to list all files and folders in the `$CACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"}]}},"scope-cache-recursive":{"identifier":"scope-cache-recursive","description":"This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"},{"path":"$CACHE/**"}]}},"scope-config":{"identifier":"scope-config","description":"This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"},{"path":"$CONFIG/*"}]}},"scope-config-index":{"identifier":"scope-config-index","description":"This scope permits to list all files and folders in the `$CONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"}]}},"scope-config-recursive":{"identifier":"scope-config-recursive","description":"This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"},{"path":"$CONFIG/**"}]}},"scope-data":{"identifier":"scope-data","description":"This scope permits access to all files and list content of top level directories in the `$DATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"},{"path":"$DATA/*"}]}},"scope-data-index":{"identifier":"scope-data-index","description":"This scope permits to list all files and folders in the `$DATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"}]}},"scope-data-recursive":{"identifier":"scope-data-recursive","description":"This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"},{"path":"$DATA/**"}]}},"scope-desktop":{"identifier":"scope-desktop","description":"This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"},{"path":"$DESKTOP/*"}]}},"scope-desktop-index":{"identifier":"scope-desktop-index","description":"This scope permits to list all files and folders in the `$DESKTOP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"}]}},"scope-desktop-recursive":{"identifier":"scope-desktop-recursive","description":"This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"},{"path":"$DESKTOP/**"}]}},"scope-document":{"identifier":"scope-document","description":"This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"},{"path":"$DOCUMENT/*"}]}},"scope-document-index":{"identifier":"scope-document-index","description":"This scope permits to list all files and folders in the `$DOCUMENT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"}]}},"scope-document-recursive":{"identifier":"scope-document-recursive","description":"This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"},{"path":"$DOCUMENT/**"}]}},"scope-download":{"identifier":"scope-download","description":"This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"},{"path":"$DOWNLOAD/*"}]}},"scope-download-index":{"identifier":"scope-download-index","description":"This scope permits to list all files and folders in the `$DOWNLOAD`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"}]}},"scope-download-recursive":{"identifier":"scope-download-recursive","description":"This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"},{"path":"$DOWNLOAD/**"}]}},"scope-exe":{"identifier":"scope-exe","description":"This scope permits access to all files and list content of top level directories in the `$EXE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"},{"path":"$EXE/*"}]}},"scope-exe-index":{"identifier":"scope-exe-index","description":"This scope permits to list all files and folders in the `$EXE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"}]}},"scope-exe-recursive":{"identifier":"scope-exe-recursive","description":"This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"},{"path":"$EXE/**"}]}},"scope-font":{"identifier":"scope-font","description":"This scope permits access to all files and list content of top level directories in the `$FONT` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"},{"path":"$FONT/*"}]}},"scope-font-index":{"identifier":"scope-font-index","description":"This scope permits to list all files and folders in the `$FONT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"}]}},"scope-font-recursive":{"identifier":"scope-font-recursive","description":"This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"},{"path":"$FONT/**"}]}},"scope-home":{"identifier":"scope-home","description":"This scope permits access to all files and list content of top level directories in the `$HOME` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"},{"path":"$HOME/*"}]}},"scope-home-index":{"identifier":"scope-home-index","description":"This scope permits to list all files and folders in the `$HOME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"}]}},"scope-home-recursive":{"identifier":"scope-home-recursive","description":"This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"},{"path":"$HOME/**"}]}},"scope-localdata":{"identifier":"scope-localdata","description":"This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"},{"path":"$LOCALDATA/*"}]}},"scope-localdata-index":{"identifier":"scope-localdata-index","description":"This scope permits to list all files and folders in the `$LOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"}]}},"scope-localdata-recursive":{"identifier":"scope-localdata-recursive","description":"This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"},{"path":"$LOCALDATA/**"}]}},"scope-log":{"identifier":"scope-log","description":"This scope permits access to all files and list content of top level directories in the `$LOG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"},{"path":"$LOG/*"}]}},"scope-log-index":{"identifier":"scope-log-index","description":"This scope permits to list all files and folders in the `$LOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"}]}},"scope-log-recursive":{"identifier":"scope-log-recursive","description":"This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"},{"path":"$LOG/**"}]}},"scope-picture":{"identifier":"scope-picture","description":"This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"},{"path":"$PICTURE/*"}]}},"scope-picture-index":{"identifier":"scope-picture-index","description":"This scope permits to list all files and folders in the `$PICTURE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"}]}},"scope-picture-recursive":{"identifier":"scope-picture-recursive","description":"This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"},{"path":"$PICTURE/**"}]}},"scope-public":{"identifier":"scope-public","description":"This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"},{"path":"$PUBLIC/*"}]}},"scope-public-index":{"identifier":"scope-public-index","description":"This scope permits to list all files and folders in the `$PUBLIC`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"}]}},"scope-public-recursive":{"identifier":"scope-public-recursive","description":"This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"},{"path":"$PUBLIC/**"}]}},"scope-resource":{"identifier":"scope-resource","description":"This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"},{"path":"$RESOURCE/*"}]}},"scope-resource-index":{"identifier":"scope-resource-index","description":"This scope permits to list all files and folders in the `$RESOURCE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"}]}},"scope-resource-recursive":{"identifier":"scope-resource-recursive","description":"This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"},{"path":"$RESOURCE/**"}]}},"scope-runtime":{"identifier":"scope-runtime","description":"This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"},{"path":"$RUNTIME/*"}]}},"scope-runtime-index":{"identifier":"scope-runtime-index","description":"This scope permits to list all files and folders in the `$RUNTIME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"}]}},"scope-runtime-recursive":{"identifier":"scope-runtime-recursive","description":"This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"},{"path":"$RUNTIME/**"}]}},"scope-temp":{"identifier":"scope-temp","description":"This scope permits access to all files and list content of top level directories in the `$TEMP` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"},{"path":"$TEMP/*"}]}},"scope-temp-index":{"identifier":"scope-temp-index","description":"This scope permits to list all files and folders in the `$TEMP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"}]}},"scope-temp-recursive":{"identifier":"scope-temp-recursive","description":"This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"},{"path":"$TEMP/**"}]}},"scope-template":{"identifier":"scope-template","description":"This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"},{"path":"$TEMPLATE/*"}]}},"scope-template-index":{"identifier":"scope-template-index","description":"This scope permits to list all files and folders in the `$TEMPLATE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"}]}},"scope-template-recursive":{"identifier":"scope-template-recursive","description":"This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"},{"path":"$TEMPLATE/**"}]}},"scope-video":{"identifier":"scope-video","description":"This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"},{"path":"$VIDEO/*"}]}},"scope-video-index":{"identifier":"scope-video-index","description":"This scope permits to list all files and folders in the `$VIDEO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"}]}},"scope-video-recursive":{"identifier":"scope-video-recursive","description":"This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"},{"path":"$VIDEO/**"}]}},"write-all":{"identifier":"write-all","description":"This enables all write related commands without any pre-configured accessible paths.","commands":{"allow":["mkdir","create","copy_file","remove","rename","truncate","ftruncate","write","write_file","write_text_file"],"deny":[]}},"write-files":{"identifier":"write-files","description":"This enables all file write related commands without any pre-configured accessible paths.","commands":{"allow":["create","copy_file","remove","rename","truncate","ftruncate","write","write_file","write_text_file"],"deny":[]}}},"permission_sets":{"allow-app-meta":{"identifier":"allow-app-meta","description":"This allows non-recursive read access to metadata of the application folders, including file listing and statistics.","permissions":["read-meta","scope-app-index"]},"allow-app-meta-recursive":{"identifier":"allow-app-meta-recursive","description":"This allows full recursive read access to metadata of the application folders, including file listing and statistics.","permissions":["read-meta","scope-app-recursive"]},"allow-app-read":{"identifier":"allow-app-read","description":"This allows non-recursive read access to the application folders.","permissions":["read-all","scope-app"]},"allow-app-read-recursive":{"identifier":"allow-app-read-recursive","description":"This allows full recursive read access to the complete application folders, files and subdirectories.","permissions":["read-all","scope-app-recursive"]},"allow-app-write":{"identifier":"allow-app-write","description":"This allows non-recursive write access to the application folders.","permissions":["write-all","scope-app"]},"allow-app-write-recursive":{"identifier":"allow-app-write-recursive","description":"This allows full recursive write access to the complete application folders, files and subdirectories.","permissions":["write-all","scope-app-recursive"]},"allow-appcache-meta":{"identifier":"allow-appcache-meta","description":"This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-appcache-index"]},"allow-appcache-meta-recursive":{"identifier":"allow-appcache-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-appcache-recursive"]},"allow-appcache-read":{"identifier":"allow-appcache-read","description":"This allows non-recursive read access to the `$APPCACHE` folder.","permissions":["read-all","scope-appcache"]},"allow-appcache-read-recursive":{"identifier":"allow-appcache-read-recursive","description":"This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.","permissions":["read-all","scope-appcache-recursive"]},"allow-appcache-write":{"identifier":"allow-appcache-write","description":"This allows non-recursive write access to the `$APPCACHE` folder.","permissions":["write-all","scope-appcache"]},"allow-appcache-write-recursive":{"identifier":"allow-appcache-write-recursive","description":"This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.","permissions":["write-all","scope-appcache-recursive"]},"allow-appconfig-meta":{"identifier":"allow-appconfig-meta","description":"This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-appconfig-index"]},"allow-appconfig-meta-recursive":{"identifier":"allow-appconfig-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-appconfig-recursive"]},"allow-appconfig-read":{"identifier":"allow-appconfig-read","description":"This allows non-recursive read access to the `$APPCONFIG` folder.","permissions":["read-all","scope-appconfig"]},"allow-appconfig-read-recursive":{"identifier":"allow-appconfig-read-recursive","description":"This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.","permissions":["read-all","scope-appconfig-recursive"]},"allow-appconfig-write":{"identifier":"allow-appconfig-write","description":"This allows non-recursive write access to the `$APPCONFIG` folder.","permissions":["write-all","scope-appconfig"]},"allow-appconfig-write-recursive":{"identifier":"allow-appconfig-write-recursive","description":"This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.","permissions":["write-all","scope-appconfig-recursive"]},"allow-appdata-meta":{"identifier":"allow-appdata-meta","description":"This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-appdata-index"]},"allow-appdata-meta-recursive":{"identifier":"allow-appdata-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-appdata-recursive"]},"allow-appdata-read":{"identifier":"allow-appdata-read","description":"This allows non-recursive read access to the `$APPDATA` folder.","permissions":["read-all","scope-appdata"]},"allow-appdata-read-recursive":{"identifier":"allow-appdata-read-recursive","description":"This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.","permissions":["read-all","scope-appdata-recursive"]},"allow-appdata-write":{"identifier":"allow-appdata-write","description":"This allows non-recursive write access to the `$APPDATA` folder.","permissions":["write-all","scope-appdata"]},"allow-appdata-write-recursive":{"identifier":"allow-appdata-write-recursive","description":"This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.","permissions":["write-all","scope-appdata-recursive"]},"allow-applocaldata-meta":{"identifier":"allow-applocaldata-meta","description":"This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-applocaldata-index"]},"allow-applocaldata-meta-recursive":{"identifier":"allow-applocaldata-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-applocaldata-recursive"]},"allow-applocaldata-read":{"identifier":"allow-applocaldata-read","description":"This allows non-recursive read access to the `$APPLOCALDATA` folder.","permissions":["read-all","scope-applocaldata"]},"allow-applocaldata-read-recursive":{"identifier":"allow-applocaldata-read-recursive","description":"This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.","permissions":["read-all","scope-applocaldata-recursive"]},"allow-applocaldata-write":{"identifier":"allow-applocaldata-write","description":"This allows non-recursive write access to the `$APPLOCALDATA` folder.","permissions":["write-all","scope-applocaldata"]},"allow-applocaldata-write-recursive":{"identifier":"allow-applocaldata-write-recursive","description":"This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.","permissions":["write-all","scope-applocaldata-recursive"]},"allow-applog-meta":{"identifier":"allow-applog-meta","description":"This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.","permissions":["read-meta","scope-applog-index"]},"allow-applog-meta-recursive":{"identifier":"allow-applog-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.","permissions":["read-meta","scope-applog-recursive"]},"allow-applog-read":{"identifier":"allow-applog-read","description":"This allows non-recursive read access to the `$APPLOG` folder.","permissions":["read-all","scope-applog"]},"allow-applog-read-recursive":{"identifier":"allow-applog-read-recursive","description":"This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.","permissions":["read-all","scope-applog-recursive"]},"allow-applog-write":{"identifier":"allow-applog-write","description":"This allows non-recursive write access to the `$APPLOG` folder.","permissions":["write-all","scope-applog"]},"allow-applog-write-recursive":{"identifier":"allow-applog-write-recursive","description":"This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.","permissions":["write-all","scope-applog-recursive"]},"allow-audio-meta":{"identifier":"allow-audio-meta","description":"This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.","permissions":["read-meta","scope-audio-index"]},"allow-audio-meta-recursive":{"identifier":"allow-audio-meta-recursive","description":"This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.","permissions":["read-meta","scope-audio-recursive"]},"allow-audio-read":{"identifier":"allow-audio-read","description":"This allows non-recursive read access to the `$AUDIO` folder.","permissions":["read-all","scope-audio"]},"allow-audio-read-recursive":{"identifier":"allow-audio-read-recursive","description":"This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.","permissions":["read-all","scope-audio-recursive"]},"allow-audio-write":{"identifier":"allow-audio-write","description":"This allows non-recursive write access to the `$AUDIO` folder.","permissions":["write-all","scope-audio"]},"allow-audio-write-recursive":{"identifier":"allow-audio-write-recursive","description":"This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.","permissions":["write-all","scope-audio-recursive"]},"allow-cache-meta":{"identifier":"allow-cache-meta","description":"This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-cache-index"]},"allow-cache-meta-recursive":{"identifier":"allow-cache-meta-recursive","description":"This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-cache-recursive"]},"allow-cache-read":{"identifier":"allow-cache-read","description":"This allows non-recursive read access to the `$CACHE` folder.","permissions":["read-all","scope-cache"]},"allow-cache-read-recursive":{"identifier":"allow-cache-read-recursive","description":"This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.","permissions":["read-all","scope-cache-recursive"]},"allow-cache-write":{"identifier":"allow-cache-write","description":"This allows non-recursive write access to the `$CACHE` folder.","permissions":["write-all","scope-cache"]},"allow-cache-write-recursive":{"identifier":"allow-cache-write-recursive","description":"This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.","permissions":["write-all","scope-cache-recursive"]},"allow-config-meta":{"identifier":"allow-config-meta","description":"This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-config-index"]},"allow-config-meta-recursive":{"identifier":"allow-config-meta-recursive","description":"This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-config-recursive"]},"allow-config-read":{"identifier":"allow-config-read","description":"This allows non-recursive read access to the `$CONFIG` folder.","permissions":["read-all","scope-config"]},"allow-config-read-recursive":{"identifier":"allow-config-read-recursive","description":"This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.","permissions":["read-all","scope-config-recursive"]},"allow-config-write":{"identifier":"allow-config-write","description":"This allows non-recursive write access to the `$CONFIG` folder.","permissions":["write-all","scope-config"]},"allow-config-write-recursive":{"identifier":"allow-config-write-recursive","description":"This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.","permissions":["write-all","scope-config-recursive"]},"allow-data-meta":{"identifier":"allow-data-meta","description":"This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.","permissions":["read-meta","scope-data-index"]},"allow-data-meta-recursive":{"identifier":"allow-data-meta-recursive","description":"This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.","permissions":["read-meta","scope-data-recursive"]},"allow-data-read":{"identifier":"allow-data-read","description":"This allows non-recursive read access to the `$DATA` folder.","permissions":["read-all","scope-data"]},"allow-data-read-recursive":{"identifier":"allow-data-read-recursive","description":"This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.","permissions":["read-all","scope-data-recursive"]},"allow-data-write":{"identifier":"allow-data-write","description":"This allows non-recursive write access to the `$DATA` folder.","permissions":["write-all","scope-data"]},"allow-data-write-recursive":{"identifier":"allow-data-write-recursive","description":"This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.","permissions":["write-all","scope-data-recursive"]},"allow-desktop-meta":{"identifier":"allow-desktop-meta","description":"This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.","permissions":["read-meta","scope-desktop-index"]},"allow-desktop-meta-recursive":{"identifier":"allow-desktop-meta-recursive","description":"This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.","permissions":["read-meta","scope-desktop-recursive"]},"allow-desktop-read":{"identifier":"allow-desktop-read","description":"This allows non-recursive read access to the `$DESKTOP` folder.","permissions":["read-all","scope-desktop"]},"allow-desktop-read-recursive":{"identifier":"allow-desktop-read-recursive","description":"This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.","permissions":["read-all","scope-desktop-recursive"]},"allow-desktop-write":{"identifier":"allow-desktop-write","description":"This allows non-recursive write access to the `$DESKTOP` folder.","permissions":["write-all","scope-desktop"]},"allow-desktop-write-recursive":{"identifier":"allow-desktop-write-recursive","description":"This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.","permissions":["write-all","scope-desktop-recursive"]},"allow-document-meta":{"identifier":"allow-document-meta","description":"This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.","permissions":["read-meta","scope-document-index"]},"allow-document-meta-recursive":{"identifier":"allow-document-meta-recursive","description":"This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.","permissions":["read-meta","scope-document-recursive"]},"allow-document-read":{"identifier":"allow-document-read","description":"This allows non-recursive read access to the `$DOCUMENT` folder.","permissions":["read-all","scope-document"]},"allow-document-read-recursive":{"identifier":"allow-document-read-recursive","description":"This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.","permissions":["read-all","scope-document-recursive"]},"allow-document-write":{"identifier":"allow-document-write","description":"This allows non-recursive write access to the `$DOCUMENT` folder.","permissions":["write-all","scope-document"]},"allow-document-write-recursive":{"identifier":"allow-document-write-recursive","description":"This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.","permissions":["write-all","scope-document-recursive"]},"allow-download-meta":{"identifier":"allow-download-meta","description":"This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.","permissions":["read-meta","scope-download-index"]},"allow-download-meta-recursive":{"identifier":"allow-download-meta-recursive","description":"This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.","permissions":["read-meta","scope-download-recursive"]},"allow-download-read":{"identifier":"allow-download-read","description":"This allows non-recursive read access to the `$DOWNLOAD` folder.","permissions":["read-all","scope-download"]},"allow-download-read-recursive":{"identifier":"allow-download-read-recursive","description":"This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.","permissions":["read-all","scope-download-recursive"]},"allow-download-write":{"identifier":"allow-download-write","description":"This allows non-recursive write access to the `$DOWNLOAD` folder.","permissions":["write-all","scope-download"]},"allow-download-write-recursive":{"identifier":"allow-download-write-recursive","description":"This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.","permissions":["write-all","scope-download-recursive"]},"allow-exe-meta":{"identifier":"allow-exe-meta","description":"This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.","permissions":["read-meta","scope-exe-index"]},"allow-exe-meta-recursive":{"identifier":"allow-exe-meta-recursive","description":"This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.","permissions":["read-meta","scope-exe-recursive"]},"allow-exe-read":{"identifier":"allow-exe-read","description":"This allows non-recursive read access to the `$EXE` folder.","permissions":["read-all","scope-exe"]},"allow-exe-read-recursive":{"identifier":"allow-exe-read-recursive","description":"This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.","permissions":["read-all","scope-exe-recursive"]},"allow-exe-write":{"identifier":"allow-exe-write","description":"This allows non-recursive write access to the `$EXE` folder.","permissions":["write-all","scope-exe"]},"allow-exe-write-recursive":{"identifier":"allow-exe-write-recursive","description":"This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.","permissions":["write-all","scope-exe-recursive"]},"allow-font-meta":{"identifier":"allow-font-meta","description":"This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.","permissions":["read-meta","scope-font-index"]},"allow-font-meta-recursive":{"identifier":"allow-font-meta-recursive","description":"This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.","permissions":["read-meta","scope-font-recursive"]},"allow-font-read":{"identifier":"allow-font-read","description":"This allows non-recursive read access to the `$FONT` folder.","permissions":["read-all","scope-font"]},"allow-font-read-recursive":{"identifier":"allow-font-read-recursive","description":"This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.","permissions":["read-all","scope-font-recursive"]},"allow-font-write":{"identifier":"allow-font-write","description":"This allows non-recursive write access to the `$FONT` folder.","permissions":["write-all","scope-font"]},"allow-font-write-recursive":{"identifier":"allow-font-write-recursive","description":"This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.","permissions":["write-all","scope-font-recursive"]},"allow-home-meta":{"identifier":"allow-home-meta","description":"This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.","permissions":["read-meta","scope-home-index"]},"allow-home-meta-recursive":{"identifier":"allow-home-meta-recursive","description":"This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.","permissions":["read-meta","scope-home-recursive"]},"allow-home-read":{"identifier":"allow-home-read","description":"This allows non-recursive read access to the `$HOME` folder.","permissions":["read-all","scope-home"]},"allow-home-read-recursive":{"identifier":"allow-home-read-recursive","description":"This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.","permissions":["read-all","scope-home-recursive"]},"allow-home-write":{"identifier":"allow-home-write","description":"This allows non-recursive write access to the `$HOME` folder.","permissions":["write-all","scope-home"]},"allow-home-write-recursive":{"identifier":"allow-home-write-recursive","description":"This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.","permissions":["write-all","scope-home-recursive"]},"allow-localdata-meta":{"identifier":"allow-localdata-meta","description":"This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-localdata-index"]},"allow-localdata-meta-recursive":{"identifier":"allow-localdata-meta-recursive","description":"This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-localdata-recursive"]},"allow-localdata-read":{"identifier":"allow-localdata-read","description":"This allows non-recursive read access to the `$LOCALDATA` folder.","permissions":["read-all","scope-localdata"]},"allow-localdata-read-recursive":{"identifier":"allow-localdata-read-recursive","description":"This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.","permissions":["read-all","scope-localdata-recursive"]},"allow-localdata-write":{"identifier":"allow-localdata-write","description":"This allows non-recursive write access to the `$LOCALDATA` folder.","permissions":["write-all","scope-localdata"]},"allow-localdata-write-recursive":{"identifier":"allow-localdata-write-recursive","description":"This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.","permissions":["write-all","scope-localdata-recursive"]},"allow-log-meta":{"identifier":"allow-log-meta","description":"This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.","permissions":["read-meta","scope-log-index"]},"allow-log-meta-recursive":{"identifier":"allow-log-meta-recursive","description":"This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.","permissions":["read-meta","scope-log-recursive"]},"allow-log-read":{"identifier":"allow-log-read","description":"This allows non-recursive read access to the `$LOG` folder.","permissions":["read-all","scope-log"]},"allow-log-read-recursive":{"identifier":"allow-log-read-recursive","description":"This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.","permissions":["read-all","scope-log-recursive"]},"allow-log-write":{"identifier":"allow-log-write","description":"This allows non-recursive write access to the `$LOG` folder.","permissions":["write-all","scope-log"]},"allow-log-write-recursive":{"identifier":"allow-log-write-recursive","description":"This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.","permissions":["write-all","scope-log-recursive"]},"allow-picture-meta":{"identifier":"allow-picture-meta","description":"This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.","permissions":["read-meta","scope-picture-index"]},"allow-picture-meta-recursive":{"identifier":"allow-picture-meta-recursive","description":"This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.","permissions":["read-meta","scope-picture-recursive"]},"allow-picture-read":{"identifier":"allow-picture-read","description":"This allows non-recursive read access to the `$PICTURE` folder.","permissions":["read-all","scope-picture"]},"allow-picture-read-recursive":{"identifier":"allow-picture-read-recursive","description":"This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.","permissions":["read-all","scope-picture-recursive"]},"allow-picture-write":{"identifier":"allow-picture-write","description":"This allows non-recursive write access to the `$PICTURE` folder.","permissions":["write-all","scope-picture"]},"allow-picture-write-recursive":{"identifier":"allow-picture-write-recursive","description":"This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.","permissions":["write-all","scope-picture-recursive"]},"allow-public-meta":{"identifier":"allow-public-meta","description":"This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.","permissions":["read-meta","scope-public-index"]},"allow-public-meta-recursive":{"identifier":"allow-public-meta-recursive","description":"This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.","permissions":["read-meta","scope-public-recursive"]},"allow-public-read":{"identifier":"allow-public-read","description":"This allows non-recursive read access to the `$PUBLIC` folder.","permissions":["read-all","scope-public"]},"allow-public-read-recursive":{"identifier":"allow-public-read-recursive","description":"This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.","permissions":["read-all","scope-public-recursive"]},"allow-public-write":{"identifier":"allow-public-write","description":"This allows non-recursive write access to the `$PUBLIC` folder.","permissions":["write-all","scope-public"]},"allow-public-write-recursive":{"identifier":"allow-public-write-recursive","description":"This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.","permissions":["write-all","scope-public-recursive"]},"allow-resource-meta":{"identifier":"allow-resource-meta","description":"This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.","permissions":["read-meta","scope-resource-index"]},"allow-resource-meta-recursive":{"identifier":"allow-resource-meta-recursive","description":"This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.","permissions":["read-meta","scope-resource-recursive"]},"allow-resource-read":{"identifier":"allow-resource-read","description":"This allows non-recursive read access to the `$RESOURCE` folder.","permissions":["read-all","scope-resource"]},"allow-resource-read-recursive":{"identifier":"allow-resource-read-recursive","description":"This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.","permissions":["read-all","scope-resource-recursive"]},"allow-resource-write":{"identifier":"allow-resource-write","description":"This allows non-recursive write access to the `$RESOURCE` folder.","permissions":["write-all","scope-resource"]},"allow-resource-write-recursive":{"identifier":"allow-resource-write-recursive","description":"This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.","permissions":["write-all","scope-resource-recursive"]},"allow-runtime-meta":{"identifier":"allow-runtime-meta","description":"This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.","permissions":["read-meta","scope-runtime-index"]},"allow-runtime-meta-recursive":{"identifier":"allow-runtime-meta-recursive","description":"This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.","permissions":["read-meta","scope-runtime-recursive"]},"allow-runtime-read":{"identifier":"allow-runtime-read","description":"This allows non-recursive read access to the `$RUNTIME` folder.","permissions":["read-all","scope-runtime"]},"allow-runtime-read-recursive":{"identifier":"allow-runtime-read-recursive","description":"This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.","permissions":["read-all","scope-runtime-recursive"]},"allow-runtime-write":{"identifier":"allow-runtime-write","description":"This allows non-recursive write access to the `$RUNTIME` folder.","permissions":["write-all","scope-runtime"]},"allow-runtime-write-recursive":{"identifier":"allow-runtime-write-recursive","description":"This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.","permissions":["write-all","scope-runtime-recursive"]},"allow-temp-meta":{"identifier":"allow-temp-meta","description":"This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.","permissions":["read-meta","scope-temp-index"]},"allow-temp-meta-recursive":{"identifier":"allow-temp-meta-recursive","description":"This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.","permissions":["read-meta","scope-temp-recursive"]},"allow-temp-read":{"identifier":"allow-temp-read","description":"This allows non-recursive read access to the `$TEMP` folder.","permissions":["read-all","scope-temp"]},"allow-temp-read-recursive":{"identifier":"allow-temp-read-recursive","description":"This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.","permissions":["read-all","scope-temp-recursive"]},"allow-temp-write":{"identifier":"allow-temp-write","description":"This allows non-recursive write access to the `$TEMP` folder.","permissions":["write-all","scope-temp"]},"allow-temp-write-recursive":{"identifier":"allow-temp-write-recursive","description":"This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.","permissions":["write-all","scope-temp-recursive"]},"allow-template-meta":{"identifier":"allow-template-meta","description":"This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.","permissions":["read-meta","scope-template-index"]},"allow-template-meta-recursive":{"identifier":"allow-template-meta-recursive","description":"This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.","permissions":["read-meta","scope-template-recursive"]},"allow-template-read":{"identifier":"allow-template-read","description":"This allows non-recursive read access to the `$TEMPLATE` folder.","permissions":["read-all","scope-template"]},"allow-template-read-recursive":{"identifier":"allow-template-read-recursive","description":"This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.","permissions":["read-all","scope-template-recursive"]},"allow-template-write":{"identifier":"allow-template-write","description":"This allows non-recursive write access to the `$TEMPLATE` folder.","permissions":["write-all","scope-template"]},"allow-template-write-recursive":{"identifier":"allow-template-write-recursive","description":"This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.","permissions":["write-all","scope-template-recursive"]},"allow-video-meta":{"identifier":"allow-video-meta","description":"This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.","permissions":["read-meta","scope-video-index"]},"allow-video-meta-recursive":{"identifier":"allow-video-meta-recursive","description":"This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.","permissions":["read-meta","scope-video-recursive"]},"allow-video-read":{"identifier":"allow-video-read","description":"This allows non-recursive read access to the `$VIDEO` folder.","permissions":["read-all","scope-video"]},"allow-video-read-recursive":{"identifier":"allow-video-read-recursive","description":"This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.","permissions":["read-all","scope-video-recursive"]},"allow-video-write":{"identifier":"allow-video-write","description":"This allows non-recursive write access to the `$VIDEO` folder.","permissions":["write-all","scope-video"]},"allow-video-write-recursive":{"identifier":"allow-video-write-recursive","description":"This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.","permissions":["write-all","scope-video-recursive"]},"deny-default":{"identifier":"deny-default","description":"This denies access to dangerous Tauri relevant files and folders by default.","permissions":["deny-webview-data-linux","deny-webview-data-windows"]}},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"description":"A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},{"properties":{"path":{"description":"A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"}},"required":["path"],"type":"object"}],"description":"FS scope entry.","title":"FsScopeEntry"}},"os":{"default_permission":{"identifier":"default","description":"This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n","permissions":["allow-arch","allow-exe-extension","allow-family","allow-locale","allow-os-type","allow-platform","allow-version"]},"permissions":{"allow-arch":{"identifier":"allow-arch","description":"Enables the arch command without any pre-configured scope.","commands":{"allow":["arch"],"deny":[]}},"allow-exe-extension":{"identifier":"allow-exe-extension","description":"Enables the exe_extension command without any pre-configured scope.","commands":{"allow":["exe_extension"],"deny":[]}},"allow-family":{"identifier":"allow-family","description":"Enables the family command without any pre-configured scope.","commands":{"allow":["family"],"deny":[]}},"allow-hostname":{"identifier":"allow-hostname","description":"Enables the hostname command without any pre-configured scope.","commands":{"allow":["hostname"],"deny":[]}},"allow-locale":{"identifier":"allow-locale","description":"Enables the locale command without any pre-configured scope.","commands":{"allow":["locale"],"deny":[]}},"allow-os-type":{"identifier":"allow-os-type","description":"Enables the os_type command without any pre-configured scope.","commands":{"allow":["os_type"],"deny":[]}},"allow-platform":{"identifier":"allow-platform","description":"Enables the platform command without any pre-configured scope.","commands":{"allow":["platform"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-arch":{"identifier":"deny-arch","description":"Denies the arch command without any pre-configured scope.","commands":{"allow":[],"deny":["arch"]}},"deny-exe-extension":{"identifier":"deny-exe-extension","description":"Denies the exe_extension command without any pre-configured scope.","commands":{"allow":[],"deny":["exe_extension"]}},"deny-family":{"identifier":"deny-family","description":"Denies the family command without any pre-configured scope.","commands":{"allow":[],"deny":["family"]}},"deny-hostname":{"identifier":"deny-hostname","description":"Denies the hostname command without any pre-configured scope.","commands":{"allow":[],"deny":["hostname"]}},"deny-locale":{"identifier":"deny-locale","description":"Denies the locale command without any pre-configured scope.","commands":{"allow":[],"deny":["locale"]}},"deny-os-type":{"identifier":"deny-os-type","description":"Denies the os_type command without any pre-configured scope.","commands":{"allow":[],"deny":["os_type"]}},"deny-platform":{"identifier":"deny-platform","description":"Denies the platform command without any pre-configured scope.","commands":{"allow":[],"deny":["platform"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"shell":{"default_permission":{"identifier":"default","description":"This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n","permissions":["allow-open"]},"permissions":{"allow-execute":{"identifier":"allow-execute","description":"Enables the execute command without any pre-configured scope.","commands":{"allow":["execute"],"deny":[]}},"allow-kill":{"identifier":"allow-kill","description":"Enables the kill command without any pre-configured scope.","commands":{"allow":["kill"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-spawn":{"identifier":"allow-spawn","description":"Enables the spawn command without any pre-configured scope.","commands":{"allow":["spawn"],"deny":[]}},"allow-stdin-write":{"identifier":"allow-stdin-write","description":"Enables the stdin_write command without any pre-configured scope.","commands":{"allow":["stdin_write"],"deny":[]}},"deny-execute":{"identifier":"deny-execute","description":"Denies the execute command without any pre-configured scope.","commands":{"allow":[],"deny":["execute"]}},"deny-kill":{"identifier":"deny-kill","description":"Denies the kill command without any pre-configured scope.","commands":{"allow":[],"deny":["kill"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-spawn":{"identifier":"deny-spawn","description":"Denies the spawn command without any pre-configured scope.","commands":{"allow":[],"deny":["spawn"]}},"deny-stdin-write":{"identifier":"deny-stdin-write","description":"Denies the stdin_write command without any pre-configured scope.","commands":{"allow":[],"deny":["stdin_write"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"cmd":{"description":"The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"}},"required":["cmd","name"],"type":"object"},{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"},"sidecar":{"description":"If this command is a sidecar command.","type":"boolean"}},"required":["name","sidecar"],"type":"object"}],"definitions":{"ShellScopeEntryAllowedArg":{"anyOf":[{"description":"A non-configurable argument that is passed to the command in the order it was specified.","type":"string"},{"additionalProperties":false,"description":"A variable that is set while calling the command from the webview API.","properties":{"raw":{"default":false,"description":"Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.","type":"boolean"},"validator":{"description":"[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ","type":"string"}},"required":["validator"],"type":"object"}],"description":"A command argument allowed to be executed by the webview API."},"ShellScopeEntryAllowedArgs":{"anyOf":[{"description":"Use a simple boolean to allow all or disable all arguments to this command configuration.","type":"boolean"},{"description":"A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.","items":{"$ref":"#/definitions/ShellScopeEntryAllowedArg"},"type":"array"}],"description":"A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."}},"description":"Shell scope entry.","title":"ShellScopeEntry"}},"sql":{"default_permission":{"identifier":"default","description":"### Default Permissions\n\nThis permission set configures what kind of\ndatabase operations are available from the sql plugin.\n\n### Granted Permissions\n\nAll reading related operations are enabled.\nAlso allows to load or close a connection.\n\n","permissions":["allow-close","allow-load","allow-select"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-execute":{"identifier":"allow-execute","description":"Enables the execute command without any pre-configured scope.","commands":{"allow":["execute"],"deny":[]}},"allow-load":{"identifier":"allow-load","description":"Enables the load command without any pre-configured scope.","commands":{"allow":["load"],"deny":[]}},"allow-select":{"identifier":"allow-select","description":"Enables the select command without any pre-configured scope.","commands":{"allow":["select"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-execute":{"identifier":"deny-execute","description":"Denies the execute command without any pre-configured scope.","commands":{"allow":[],"deny":["execute"]}},"deny-load":{"identifier":"deny-load","description":"Denies the load command without any pre-configured scope.","commands":{"allow":[],"deny":["load"]}},"deny-select":{"identifier":"deny-select","description":"Denies the select command without any pre-configured scope.","commands":{"allow":[],"deny":["select"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/src-tauri/gen/schemas/desktop-schema.json b/src-tauri/gen/schemas/desktop-schema.json deleted file mode 100644 index fd6f55d9..00000000 --- a/src-tauri/gen/schemas/desktop-schema.json +++ /dev/null @@ -1,5256 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CapabilityFile", - "description": "Capability formats accepted in a capability file.", - "anyOf": [ - { - "description": "A single capability.", - "allOf": [ - { - "$ref": "#/definitions/Capability" - } - ] - }, - { - "description": "A list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - }, - { - "description": "A list of capabilities.", - "type": "object", - "required": [ - "capabilities" - ], - "properties": { - "capabilities": { - "description": "The list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - } - } - } - ], - "definitions": { - "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", - "type": "object", - "required": [ - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", - "type": "string" - }, - "description": { - "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.", - "default": "", - "type": "string" - }, - "remote": { - "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", - "anyOf": [ - { - "$ref": "#/definitions/CapabilityRemote" - }, - { - "type": "null" - } - ] - }, - "local": { - "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", - "default": true, - "type": "boolean" - }, - "windows": { - "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "webviews": { - "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThis is only required when using on multiwebview contexts, by default all child webviews of a window that matches [`Self::windows`] are linked.\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionEntry" - }, - "uniqueItems": true - }, - "platforms": { - "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "CapabilityRemote": { - "description": "Configuration for remote URLs that are associated with the capability.", - "type": "object", - "required": [ - "urls" - ], - "properties": { - "urls": { - "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionEntry": { - "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", - "anyOf": [ - { - "description": "Reference a permission or permission set by identifier.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - { - "description": "Reference a permission or permission set by identifier and extends its scope.", - "type": "object", - "allOf": [ - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n", - "type": "string", - "const": "fs:default" - }, - { - "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta" - }, - { - "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the application folders.", - "type": "string", - "const": "fs:allow-app-read" - }, - { - "description": "This allows full recursive read access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-read-recursive" - }, - { - "description": "This allows non-recursive write access to the application folders.", - "type": "string", - "const": "fs:allow-app-write" - }, - { - "description": "This allows full recursive write access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-read" - }, - { - "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-write" - }, - { - "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-read" - }, - { - "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-write" - }, - { - "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-read" - }, - { - "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-write" - }, - { - "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-read" - }, - { - "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-write" - }, - { - "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-read" - }, - { - "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-write" - }, - { - "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-read" - }, - { - "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-write" - }, - { - "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-read" - }, - { - "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-write" - }, - { - "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-read" - }, - { - "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-write" - }, - { - "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-read" - }, - { - "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-write" - }, - { - "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-read" - }, - { - "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-write" - }, - { - "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-read" - }, - { - "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-write" - }, - { - "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-read" - }, - { - "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-write" - }, - { - "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-read" - }, - { - "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-write" - }, - { - "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-write-recursive" - }, - { - "description": "This denies access to dangerous Tauri relevant files and folders by default.", - "type": "string", - "const": "fs:deny-default" - }, - { - "description": "Enables the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-copy-file" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-create" - }, - { - "description": "Enables the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-exists" - }, - { - "description": "Enables the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-fstat" - }, - { - "description": "Enables the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-ftruncate" - }, - { - "description": "Enables the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-lstat" - }, - { - "description": "Enables the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-mkdir" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-open" - }, - { - "description": "Enables the read command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read" - }, - { - "description": "Enables the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-dir" - }, - { - "description": "Enables the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-file" - }, - { - "description": "Enables the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file" - }, - { - "description": "Enables the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines" - }, - { - "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines-next" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-remove" - }, - { - "description": "Enables the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-rename" - }, - { - "description": "Enables the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-seek" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-size" - }, - { - "description": "Enables the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-stat" - }, - { - "description": "Enables the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-truncate" - }, - { - "description": "Enables the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-unwatch" - }, - { - "description": "Enables the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-watch" - }, - { - "description": "Enables the write command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write" - }, - { - "description": "Enables the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-file" - }, - { - "description": "Enables the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-text-file" - }, - { - "description": "This permissions allows to create the application specific directories.\n", - "type": "string", - "const": "fs:create-app-specific-dirs" - }, - { - "description": "Denies the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-copy-file" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-create" - }, - { - "description": "Denies the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-exists" - }, - { - "description": "Denies the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-fstat" - }, - { - "description": "Denies the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-ftruncate" - }, - { - "description": "Denies the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-lstat" - }, - { - "description": "Denies the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-mkdir" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-open" - }, - { - "description": "Denies the read command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read" - }, - { - "description": "Denies the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-dir" - }, - { - "description": "Denies the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-file" - }, - { - "description": "Denies the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file" - }, - { - "description": "Denies the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines" - }, - { - "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines-next" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-remove" - }, - { - "description": "Denies the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-rename" - }, - { - "description": "Denies the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-seek" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-size" - }, - { - "description": "Denies the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-stat" - }, - { - "description": "Denies the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-truncate" - }, - { - "description": "Denies the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-unwatch" - }, - { - "description": "Denies the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-watch" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-linux" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-windows" - }, - { - "description": "Denies the write command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write" - }, - { - "description": "Denies the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-file" - }, - { - "description": "Denies the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-text-file" - }, - { - "description": "This enables all read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-all" - }, - { - "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", - "type": "string", - "const": "fs:read-app-specific-dirs-recursive" - }, - { - "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-dirs" - }, - { - "description": "This enables file read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-files" - }, - { - "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-meta" - }, - { - "description": "An empty permission you can use to modify the global scope.", - "type": "string", - "const": "fs:scope" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the application folders.", - "type": "string", - "const": "fs:scope-app" - }, - { - "description": "This scope permits to list all files and folders in the application directories.", - "type": "string", - "const": "fs:scope-app-index" - }, - { - "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", - "type": "string", - "const": "fs:scope-app-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", - "type": "string", - "const": "fs:scope-appcache" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", - "type": "string", - "const": "fs:scope-appcache-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appcache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:scope-appconfig" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", - "type": "string", - "const": "fs:scope-appconfig-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appconfig-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", - "type": "string", - "const": "fs:scope-appdata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", - "type": "string", - "const": "fs:scope-appdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:scope-applocaldata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", - "type": "string", - "const": "fs:scope-applocaldata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applocaldata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", - "type": "string", - "const": "fs:scope-applog" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", - "type": "string", - "const": "fs:scope-applog-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applog-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", - "type": "string", - "const": "fs:scope-audio" - }, - { - "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", - "type": "string", - "const": "fs:scope-audio-index" - }, - { - "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-audio-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", - "type": "string", - "const": "fs:scope-cache" - }, - { - "description": "This scope permits to list all files and folders in the `$CACHE`folder.", - "type": "string", - "const": "fs:scope-cache-index" - }, - { - "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-cache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", - "type": "string", - "const": "fs:scope-config" - }, - { - "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", - "type": "string", - "const": "fs:scope-config-index" - }, - { - "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-config-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", - "type": "string", - "const": "fs:scope-data" - }, - { - "description": "This scope permits to list all files and folders in the `$DATA`folder.", - "type": "string", - "const": "fs:scope-data-index" - }, - { - "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-data-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", - "type": "string", - "const": "fs:scope-desktop" - }, - { - "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", - "type": "string", - "const": "fs:scope-desktop-index" - }, - { - "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-desktop-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:scope-document" - }, - { - "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", - "type": "string", - "const": "fs:scope-document-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-document-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:scope-download" - }, - { - "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", - "type": "string", - "const": "fs:scope-download-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-download-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", - "type": "string", - "const": "fs:scope-exe" - }, - { - "description": "This scope permits to list all files and folders in the `$EXE`folder.", - "type": "string", - "const": "fs:scope-exe-index" - }, - { - "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-exe-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", - "type": "string", - "const": "fs:scope-font" - }, - { - "description": "This scope permits to list all files and folders in the `$FONT`folder.", - "type": "string", - "const": "fs:scope-font-index" - }, - { - "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-font-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", - "type": "string", - "const": "fs:scope-home" - }, - { - "description": "This scope permits to list all files and folders in the `$HOME`folder.", - "type": "string", - "const": "fs:scope-home-index" - }, - { - "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-home-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:scope-localdata" - }, - { - "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", - "type": "string", - "const": "fs:scope-localdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-localdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", - "type": "string", - "const": "fs:scope-log" - }, - { - "description": "This scope permits to list all files and folders in the `$LOG`folder.", - "type": "string", - "const": "fs:scope-log-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-log-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", - "type": "string", - "const": "fs:scope-picture" - }, - { - "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", - "type": "string", - "const": "fs:scope-picture-index" - }, - { - "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-picture-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", - "type": "string", - "const": "fs:scope-public" - }, - { - "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", - "type": "string", - "const": "fs:scope-public-index" - }, - { - "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-public-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", - "type": "string", - "const": "fs:scope-resource" - }, - { - "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", - "type": "string", - "const": "fs:scope-resource-index" - }, - { - "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-resource-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", - "type": "string", - "const": "fs:scope-runtime" - }, - { - "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", - "type": "string", - "const": "fs:scope-runtime-index" - }, - { - "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-runtime-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", - "type": "string", - "const": "fs:scope-temp" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMP`folder.", - "type": "string", - "const": "fs:scope-temp-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-temp-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:scope-template" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", - "type": "string", - "const": "fs:scope-template-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-template-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", - "type": "string", - "const": "fs:scope-video" - }, - { - "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", - "type": "string", - "const": "fs:scope-video-index" - }, - { - "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-video-recursive" - }, - { - "description": "This enables all write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-all" - }, - { - "description": "This enables all file write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-files" - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "FsScopeEntry", - "description": "FS scope entry.", - "anyOf": [ - { - "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - { - "type": "object", - "required": [ - "path" - ], - "properties": { - "path": { - "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - } - } - } - ] - } - }, - "deny": { - "items": { - "title": "FsScopeEntry", - "description": "FS scope entry.", - "anyOf": [ - { - "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - { - "type": "object", - "required": [ - "path" - ], - "properties": { - "path": { - "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - } - } - } - ] - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "const": "shell:default" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute" - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open" - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn" - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute" - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open" - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn" - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write" - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "deny": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - } - ], - "required": [ - "identifier" - ] - } - ] - }, - "Identifier": { - "description": "Permission identifier", - "oneOf": [ - { - "description": "Allows reading the CLI matches", - "type": "string", - "const": "cli:default" - }, - { - "description": "Enables the cli_matches command without any pre-configured scope.", - "type": "string", - "const": "cli:allow-cli-matches" - }, - { - "description": "Denies the cli_matches command without any pre-configured scope.", - "type": "string", - "const": "cli:deny-cli-matches" - }, - { - "description": "Default core plugins set which includes:\n- 'core:path:default'\n- 'core:event:default'\n- 'core:window:default'\n- 'core:webview:default'\n- 'core:app:default'\n- 'core:image:default'\n- 'core:resources:default'\n- 'core:menu:default'\n- 'core:tray:default'\n", - "type": "string", - "const": "core:default" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:app:default" - }, - { - "description": "Enables the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-hide" - }, - { - "description": "Enables the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-show" - }, - { - "description": "Enables the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-default-window-icon" - }, - { - "description": "Enables the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-name" - }, - { - "description": "Enables the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-set-app-theme" - }, - { - "description": "Enables the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-tauri-version" - }, - { - "description": "Enables the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-version" - }, - { - "description": "Denies the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-hide" - }, - { - "description": "Denies the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-show" - }, - { - "description": "Denies the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-default-window-icon" - }, - { - "description": "Denies the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-name" - }, - { - "description": "Denies the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-set-app-theme" - }, - { - "description": "Denies the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-tauri-version" - }, - { - "description": "Denies the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-version" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:event:default" - }, - { - "description": "Enables the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit" - }, - { - "description": "Enables the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit-to" - }, - { - "description": "Enables the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-listen" - }, - { - "description": "Enables the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-unlisten" - }, - { - "description": "Denies the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit" - }, - { - "description": "Denies the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit-to" - }, - { - "description": "Denies the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-listen" - }, - { - "description": "Denies the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-unlisten" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:image:default" - }, - { - "description": "Enables the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-bytes" - }, - { - "description": "Enables the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-path" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-new" - }, - { - "description": "Enables the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-rgba" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-size" - }, - { - "description": "Denies the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-bytes" - }, - { - "description": "Denies the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-path" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-new" - }, - { - "description": "Denies the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-rgba" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-size" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:menu:default" - }, - { - "description": "Enables the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-append" - }, - { - "description": "Enables the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-create-default" - }, - { - "description": "Enables the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-get" - }, - { - "description": "Enables the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-insert" - }, - { - "description": "Enables the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-checked" - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-enabled" - }, - { - "description": "Enables the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-items" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-new" - }, - { - "description": "Enables the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-popup" - }, - { - "description": "Enables the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-prepend" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove" - }, - { - "description": "Enables the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove-at" - }, - { - "description": "Enables the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-accelerator" - }, - { - "description": "Enables the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-app-menu" - }, - { - "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-help-menu-for-nsapp" - }, - { - "description": "Enables the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-window-menu" - }, - { - "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-windows-menu-for-nsapp" - }, - { - "description": "Enables the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-checked" - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-enabled" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-icon" - }, - { - "description": "Enables the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-text" - }, - { - "description": "Enables the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-text" - }, - { - "description": "Denies the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-append" - }, - { - "description": "Denies the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-create-default" - }, - { - "description": "Denies the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-get" - }, - { - "description": "Denies the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-insert" - }, - { - "description": "Denies the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-checked" - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-enabled" - }, - { - "description": "Denies the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-items" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-new" - }, - { - "description": "Denies the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-popup" - }, - { - "description": "Denies the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-prepend" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove" - }, - { - "description": "Denies the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove-at" - }, - { - "description": "Denies the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-accelerator" - }, - { - "description": "Denies the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-app-menu" - }, - { - "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-help-menu-for-nsapp" - }, - { - "description": "Denies the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-window-menu" - }, - { - "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-windows-menu-for-nsapp" - }, - { - "description": "Denies the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-checked" - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-enabled" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-icon" - }, - { - "description": "Denies the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-text" - }, - { - "description": "Denies the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-text" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:path:default" - }, - { - "description": "Enables the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-basename" - }, - { - "description": "Enables the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-dirname" - }, - { - "description": "Enables the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-extname" - }, - { - "description": "Enables the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-is-absolute" - }, - { - "description": "Enables the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-join" - }, - { - "description": "Enables the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-normalize" - }, - { - "description": "Enables the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve" - }, - { - "description": "Enables the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve-directory" - }, - { - "description": "Denies the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-basename" - }, - { - "description": "Denies the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-dirname" - }, - { - "description": "Denies the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-extname" - }, - { - "description": "Denies the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-is-absolute" - }, - { - "description": "Denies the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-join" - }, - { - "description": "Denies the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-normalize" - }, - { - "description": "Denies the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve" - }, - { - "description": "Denies the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve-directory" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:resources:default" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:allow-close" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:deny-close" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:tray:default" - }, - { - "description": "Enables the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-get-by-id" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-new" - }, - { - "description": "Enables the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-remove-by-id" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon" - }, - { - "description": "Enables the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon-as-template" - }, - { - "description": "Enables the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-menu" - }, - { - "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-show-menu-on-left-click" - }, - { - "description": "Enables the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-temp-dir-path" - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-title" - }, - { - "description": "Enables the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-tooltip" - }, - { - "description": "Enables the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-visible" - }, - { - "description": "Denies the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-get-by-id" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-new" - }, - { - "description": "Denies the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-remove-by-id" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon" - }, - { - "description": "Denies the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon-as-template" - }, - { - "description": "Denies the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-menu" - }, - { - "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-show-menu-on-left-click" - }, - { - "description": "Denies the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-temp-dir-path" - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-title" - }, - { - "description": "Denies the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-tooltip" - }, - { - "description": "Denies the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-visible" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:webview:default" - }, - { - "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-clear-all-browsing-data" - }, - { - "description": "Enables the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview" - }, - { - "description": "Enables the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview-window" - }, - { - "description": "Enables the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-get-all-webviews" - }, - { - "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-internal-toggle-devtools" - }, - { - "description": "Enables the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-print" - }, - { - "description": "Enables the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-reparent" - }, - { - "description": "Enables the set_webview_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-background-color" - }, - { - "description": "Enables the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-focus" - }, - { - "description": "Enables the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-position" - }, - { - "description": "Enables the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-size" - }, - { - "description": "Enables the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-zoom" - }, - { - "description": "Enables the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-close" - }, - { - "description": "Enables the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-hide" - }, - { - "description": "Enables the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-position" - }, - { - "description": "Enables the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-show" - }, - { - "description": "Enables the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-size" - }, - { - "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-clear-all-browsing-data" - }, - { - "description": "Denies the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview" - }, - { - "description": "Denies the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview-window" - }, - { - "description": "Denies the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-get-all-webviews" - }, - { - "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-internal-toggle-devtools" - }, - { - "description": "Denies the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-print" - }, - { - "description": "Denies the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-reparent" - }, - { - "description": "Denies the set_webview_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-background-color" - }, - { - "description": "Denies the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-focus" - }, - { - "description": "Denies the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-position" - }, - { - "description": "Denies the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-size" - }, - { - "description": "Denies the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-zoom" - }, - { - "description": "Denies the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-close" - }, - { - "description": "Denies the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-hide" - }, - { - "description": "Denies the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-position" - }, - { - "description": "Denies the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-show" - }, - { - "description": "Denies the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-size" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:window:default" - }, - { - "description": "Enables the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-available-monitors" - }, - { - "description": "Enables the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-center" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-close" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-create" - }, - { - "description": "Enables the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-current-monitor" - }, - { - "description": "Enables the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-cursor-position" - }, - { - "description": "Enables the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-destroy" - }, - { - "description": "Enables the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-get-all-windows" - }, - { - "description": "Enables the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-hide" - }, - { - "description": "Enables the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-position" - }, - { - "description": "Enables the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-size" - }, - { - "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-internal-toggle-maximize" - }, - { - "description": "Enables the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-closable" - }, - { - "description": "Enables the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-decorated" - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-enabled" - }, - { - "description": "Enables the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-focused" - }, - { - "description": "Enables the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-fullscreen" - }, - { - "description": "Enables the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximizable" - }, - { - "description": "Enables the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximized" - }, - { - "description": "Enables the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimizable" - }, - { - "description": "Enables the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimized" - }, - { - "description": "Enables the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-resizable" - }, - { - "description": "Enables the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-visible" - }, - { - "description": "Enables the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-maximize" - }, - { - "description": "Enables the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-minimize" - }, - { - "description": "Enables the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-monitor-from-point" - }, - { - "description": "Enables the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-position" - }, - { - "description": "Enables the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-size" - }, - { - "description": "Enables the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-primary-monitor" - }, - { - "description": "Enables the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-request-user-attention" - }, - { - "description": "Enables the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-scale-factor" - }, - { - "description": "Enables the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-bottom" - }, - { - "description": "Enables the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-top" - }, - { - "description": "Enables the set_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-background-color" - }, - { - "description": "Enables the set_badge_count command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-badge-count" - }, - { - "description": "Enables the set_badge_label command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-badge-label" - }, - { - "description": "Enables the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-closable" - }, - { - "description": "Enables the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-content-protected" - }, - { - "description": "Enables the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-grab" - }, - { - "description": "Enables the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-icon" - }, - { - "description": "Enables the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-position" - }, - { - "description": "Enables the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-visible" - }, - { - "description": "Enables the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-decorations" - }, - { - "description": "Enables the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-effects" - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-enabled" - }, - { - "description": "Enables the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-focus" - }, - { - "description": "Enables the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-fullscreen" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-icon" - }, - { - "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-ignore-cursor-events" - }, - { - "description": "Enables the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-max-size" - }, - { - "description": "Enables the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-maximizable" - }, - { - "description": "Enables the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-min-size" - }, - { - "description": "Enables the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-minimizable" - }, - { - "description": "Enables the set_overlay_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-overlay-icon" - }, - { - "description": "Enables the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-position" - }, - { - "description": "Enables the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-progress-bar" - }, - { - "description": "Enables the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-resizable" - }, - { - "description": "Enables the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-shadow" - }, - { - "description": "Enables the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size" - }, - { - "description": "Enables the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size-constraints" - }, - { - "description": "Enables the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-skip-taskbar" - }, - { - "description": "Enables the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-theme" - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title" - }, - { - "description": "Enables the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title-bar-style" - }, - { - "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-visible-on-all-workspaces" - }, - { - "description": "Enables the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-show" - }, - { - "description": "Enables the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-dragging" - }, - { - "description": "Enables the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-resize-dragging" - }, - { - "description": "Enables the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-theme" - }, - { - "description": "Enables the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-title" - }, - { - "description": "Enables the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-toggle-maximize" - }, - { - "description": "Enables the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unmaximize" - }, - { - "description": "Enables the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unminimize" - }, - { - "description": "Denies the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-available-monitors" - }, - { - "description": "Denies the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-center" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-close" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-create" - }, - { - "description": "Denies the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-current-monitor" - }, - { - "description": "Denies the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-cursor-position" - }, - { - "description": "Denies the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-destroy" - }, - { - "description": "Denies the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-get-all-windows" - }, - { - "description": "Denies the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-hide" - }, - { - "description": "Denies the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-position" - }, - { - "description": "Denies the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-size" - }, - { - "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-internal-toggle-maximize" - }, - { - "description": "Denies the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-closable" - }, - { - "description": "Denies the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-decorated" - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-enabled" - }, - { - "description": "Denies the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-focused" - }, - { - "description": "Denies the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-fullscreen" - }, - { - "description": "Denies the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximizable" - }, - { - "description": "Denies the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximized" - }, - { - "description": "Denies the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimizable" - }, - { - "description": "Denies the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimized" - }, - { - "description": "Denies the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-resizable" - }, - { - "description": "Denies the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-visible" - }, - { - "description": "Denies the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-maximize" - }, - { - "description": "Denies the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-minimize" - }, - { - "description": "Denies the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-monitor-from-point" - }, - { - "description": "Denies the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-position" - }, - { - "description": "Denies the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-size" - }, - { - "description": "Denies the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-primary-monitor" - }, - { - "description": "Denies the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-request-user-attention" - }, - { - "description": "Denies the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-scale-factor" - }, - { - "description": "Denies the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-bottom" - }, - { - "description": "Denies the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-top" - }, - { - "description": "Denies the set_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-background-color" - }, - { - "description": "Denies the set_badge_count command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-badge-count" - }, - { - "description": "Denies the set_badge_label command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-badge-label" - }, - { - "description": "Denies the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-closable" - }, - { - "description": "Denies the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-content-protected" - }, - { - "description": "Denies the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-grab" - }, - { - "description": "Denies the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-icon" - }, - { - "description": "Denies the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-position" - }, - { - "description": "Denies the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-visible" - }, - { - "description": "Denies the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-decorations" - }, - { - "description": "Denies the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-effects" - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-enabled" - }, - { - "description": "Denies the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-focus" - }, - { - "description": "Denies the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-fullscreen" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-icon" - }, - { - "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-ignore-cursor-events" - }, - { - "description": "Denies the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-max-size" - }, - { - "description": "Denies the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-maximizable" - }, - { - "description": "Denies the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-min-size" - }, - { - "description": "Denies the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-minimizable" - }, - { - "description": "Denies the set_overlay_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-overlay-icon" - }, - { - "description": "Denies the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-position" - }, - { - "description": "Denies the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-progress-bar" - }, - { - "description": "Denies the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-resizable" - }, - { - "description": "Denies the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-shadow" - }, - { - "description": "Denies the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size" - }, - { - "description": "Denies the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size-constraints" - }, - { - "description": "Denies the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-skip-taskbar" - }, - { - "description": "Denies the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-theme" - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title" - }, - { - "description": "Denies the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title-bar-style" - }, - { - "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-visible-on-all-workspaces" - }, - { - "description": "Denies the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-show" - }, - { - "description": "Denies the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-dragging" - }, - { - "description": "Denies the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-resize-dragging" - }, - { - "description": "Denies the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-theme" - }, - { - "description": "Denies the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-title" - }, - { - "description": "Denies the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-toggle-maximize" - }, - { - "description": "Denies the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unmaximize" - }, - { - "description": "Denies the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unminimize" - }, - { - "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n", - "type": "string", - "const": "dialog:default" - }, - { - "description": "Enables the ask command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-ask" - }, - { - "description": "Enables the confirm command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-confirm" - }, - { - "description": "Enables the message command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-message" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-open" - }, - { - "description": "Enables the save command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-save" - }, - { - "description": "Denies the ask command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-ask" - }, - { - "description": "Denies the confirm command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-confirm" - }, - { - "description": "Denies the message command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-message" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-open" - }, - { - "description": "Denies the save command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-save" - }, - { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n", - "type": "string", - "const": "fs:default" - }, - { - "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta" - }, - { - "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the application folders.", - "type": "string", - "const": "fs:allow-app-read" - }, - { - "description": "This allows full recursive read access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-read-recursive" - }, - { - "description": "This allows non-recursive write access to the application folders.", - "type": "string", - "const": "fs:allow-app-write" - }, - { - "description": "This allows full recursive write access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-read" - }, - { - "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-write" - }, - { - "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-read" - }, - { - "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-write" - }, - { - "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-read" - }, - { - "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-write" - }, - { - "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-read" - }, - { - "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-write" - }, - { - "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-read" - }, - { - "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-write" - }, - { - "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-read" - }, - { - "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-write" - }, - { - "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-read" - }, - { - "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-write" - }, - { - "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-read" - }, - { - "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-write" - }, - { - "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-read" - }, - { - "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-write" - }, - { - "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-read" - }, - { - "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-write" - }, - { - "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-read" - }, - { - "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-write" - }, - { - "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-read" - }, - { - "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-write" - }, - { - "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-read" - }, - { - "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-write" - }, - { - "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-write-recursive" - }, - { - "description": "This denies access to dangerous Tauri relevant files and folders by default.", - "type": "string", - "const": "fs:deny-default" - }, - { - "description": "Enables the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-copy-file" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-create" - }, - { - "description": "Enables the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-exists" - }, - { - "description": "Enables the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-fstat" - }, - { - "description": "Enables the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-ftruncate" - }, - { - "description": "Enables the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-lstat" - }, - { - "description": "Enables the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-mkdir" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-open" - }, - { - "description": "Enables the read command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read" - }, - { - "description": "Enables the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-dir" - }, - { - "description": "Enables the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-file" - }, - { - "description": "Enables the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file" - }, - { - "description": "Enables the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines" - }, - { - "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines-next" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-remove" - }, - { - "description": "Enables the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-rename" - }, - { - "description": "Enables the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-seek" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-size" - }, - { - "description": "Enables the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-stat" - }, - { - "description": "Enables the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-truncate" - }, - { - "description": "Enables the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-unwatch" - }, - { - "description": "Enables the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-watch" - }, - { - "description": "Enables the write command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write" - }, - { - "description": "Enables the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-file" - }, - { - "description": "Enables the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-text-file" - }, - { - "description": "This permissions allows to create the application specific directories.\n", - "type": "string", - "const": "fs:create-app-specific-dirs" - }, - { - "description": "Denies the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-copy-file" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-create" - }, - { - "description": "Denies the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-exists" - }, - { - "description": "Denies the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-fstat" - }, - { - "description": "Denies the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-ftruncate" - }, - { - "description": "Denies the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-lstat" - }, - { - "description": "Denies the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-mkdir" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-open" - }, - { - "description": "Denies the read command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read" - }, - { - "description": "Denies the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-dir" - }, - { - "description": "Denies the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-file" - }, - { - "description": "Denies the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file" - }, - { - "description": "Denies the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines" - }, - { - "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines-next" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-remove" - }, - { - "description": "Denies the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-rename" - }, - { - "description": "Denies the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-seek" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-size" - }, - { - "description": "Denies the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-stat" - }, - { - "description": "Denies the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-truncate" - }, - { - "description": "Denies the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-unwatch" - }, - { - "description": "Denies the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-watch" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-linux" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-windows" - }, - { - "description": "Denies the write command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write" - }, - { - "description": "Denies the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-file" - }, - { - "description": "Denies the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-text-file" - }, - { - "description": "This enables all read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-all" - }, - { - "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", - "type": "string", - "const": "fs:read-app-specific-dirs-recursive" - }, - { - "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-dirs" - }, - { - "description": "This enables file read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-files" - }, - { - "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-meta" - }, - { - "description": "An empty permission you can use to modify the global scope.", - "type": "string", - "const": "fs:scope" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the application folders.", - "type": "string", - "const": "fs:scope-app" - }, - { - "description": "This scope permits to list all files and folders in the application directories.", - "type": "string", - "const": "fs:scope-app-index" - }, - { - "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", - "type": "string", - "const": "fs:scope-app-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", - "type": "string", - "const": "fs:scope-appcache" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", - "type": "string", - "const": "fs:scope-appcache-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appcache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:scope-appconfig" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", - "type": "string", - "const": "fs:scope-appconfig-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appconfig-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", - "type": "string", - "const": "fs:scope-appdata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", - "type": "string", - "const": "fs:scope-appdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:scope-applocaldata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", - "type": "string", - "const": "fs:scope-applocaldata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applocaldata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", - "type": "string", - "const": "fs:scope-applog" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", - "type": "string", - "const": "fs:scope-applog-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applog-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", - "type": "string", - "const": "fs:scope-audio" - }, - { - "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", - "type": "string", - "const": "fs:scope-audio-index" - }, - { - "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-audio-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", - "type": "string", - "const": "fs:scope-cache" - }, - { - "description": "This scope permits to list all files and folders in the `$CACHE`folder.", - "type": "string", - "const": "fs:scope-cache-index" - }, - { - "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-cache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", - "type": "string", - "const": "fs:scope-config" - }, - { - "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", - "type": "string", - "const": "fs:scope-config-index" - }, - { - "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-config-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", - "type": "string", - "const": "fs:scope-data" - }, - { - "description": "This scope permits to list all files and folders in the `$DATA`folder.", - "type": "string", - "const": "fs:scope-data-index" - }, - { - "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-data-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", - "type": "string", - "const": "fs:scope-desktop" - }, - { - "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", - "type": "string", - "const": "fs:scope-desktop-index" - }, - { - "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-desktop-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:scope-document" - }, - { - "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", - "type": "string", - "const": "fs:scope-document-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-document-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:scope-download" - }, - { - "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", - "type": "string", - "const": "fs:scope-download-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-download-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", - "type": "string", - "const": "fs:scope-exe" - }, - { - "description": "This scope permits to list all files and folders in the `$EXE`folder.", - "type": "string", - "const": "fs:scope-exe-index" - }, - { - "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-exe-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", - "type": "string", - "const": "fs:scope-font" - }, - { - "description": "This scope permits to list all files and folders in the `$FONT`folder.", - "type": "string", - "const": "fs:scope-font-index" - }, - { - "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-font-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", - "type": "string", - "const": "fs:scope-home" - }, - { - "description": "This scope permits to list all files and folders in the `$HOME`folder.", - "type": "string", - "const": "fs:scope-home-index" - }, - { - "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-home-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:scope-localdata" - }, - { - "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", - "type": "string", - "const": "fs:scope-localdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-localdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", - "type": "string", - "const": "fs:scope-log" - }, - { - "description": "This scope permits to list all files and folders in the `$LOG`folder.", - "type": "string", - "const": "fs:scope-log-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-log-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", - "type": "string", - "const": "fs:scope-picture" - }, - { - "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", - "type": "string", - "const": "fs:scope-picture-index" - }, - { - "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-picture-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", - "type": "string", - "const": "fs:scope-public" - }, - { - "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", - "type": "string", - "const": "fs:scope-public-index" - }, - { - "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-public-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", - "type": "string", - "const": "fs:scope-resource" - }, - { - "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", - "type": "string", - "const": "fs:scope-resource-index" - }, - { - "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-resource-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", - "type": "string", - "const": "fs:scope-runtime" - }, - { - "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", - "type": "string", - "const": "fs:scope-runtime-index" - }, - { - "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-runtime-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", - "type": "string", - "const": "fs:scope-temp" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMP`folder.", - "type": "string", - "const": "fs:scope-temp-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-temp-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:scope-template" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", - "type": "string", - "const": "fs:scope-template-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-template-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", - "type": "string", - "const": "fs:scope-video" - }, - { - "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", - "type": "string", - "const": "fs:scope-video-index" - }, - { - "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-video-recursive" - }, - { - "description": "This enables all write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-all" - }, - { - "description": "This enables all file write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-files" - }, - { - "description": "This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n", - "type": "string", - "const": "os:default" - }, - { - "description": "Enables the arch command without any pre-configured scope.", - "type": "string", - "const": "os:allow-arch" - }, - { - "description": "Enables the exe_extension command without any pre-configured scope.", - "type": "string", - "const": "os:allow-exe-extension" - }, - { - "description": "Enables the family command without any pre-configured scope.", - "type": "string", - "const": "os:allow-family" - }, - { - "description": "Enables the hostname command without any pre-configured scope.", - "type": "string", - "const": "os:allow-hostname" - }, - { - "description": "Enables the locale command without any pre-configured scope.", - "type": "string", - "const": "os:allow-locale" - }, - { - "description": "Enables the os_type command without any pre-configured scope.", - "type": "string", - "const": "os:allow-os-type" - }, - { - "description": "Enables the platform command without any pre-configured scope.", - "type": "string", - "const": "os:allow-platform" - }, - { - "description": "Enables the version command without any pre-configured scope.", - "type": "string", - "const": "os:allow-version" - }, - { - "description": "Denies the arch command without any pre-configured scope.", - "type": "string", - "const": "os:deny-arch" - }, - { - "description": "Denies the exe_extension command without any pre-configured scope.", - "type": "string", - "const": "os:deny-exe-extension" - }, - { - "description": "Denies the family command without any pre-configured scope.", - "type": "string", - "const": "os:deny-family" - }, - { - "description": "Denies the hostname command without any pre-configured scope.", - "type": "string", - "const": "os:deny-hostname" - }, - { - "description": "Denies the locale command without any pre-configured scope.", - "type": "string", - "const": "os:deny-locale" - }, - { - "description": "Denies the os_type command without any pre-configured scope.", - "type": "string", - "const": "os:deny-os-type" - }, - { - "description": "Denies the platform command without any pre-configured scope.", - "type": "string", - "const": "os:deny-platform" - }, - { - "description": "Denies the version command without any pre-configured scope.", - "type": "string", - "const": "os:deny-version" - }, - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "const": "shell:default" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute" - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open" - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn" - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute" - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open" - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn" - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write" - }, - { - "description": "### Default Permissions\n\nThis permission set configures what kind of\ndatabase operations are available from the sql plugin.\n\n### Granted Permissions\n\nAll reading related operations are enabled.\nAlso allows to load or close a connection.\n\n", - "type": "string", - "const": "sql:default" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "sql:allow-close" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "sql:allow-execute" - }, - { - "description": "Enables the load command without any pre-configured scope.", - "type": "string", - "const": "sql:allow-load" - }, - { - "description": "Enables the select command without any pre-configured scope.", - "type": "string", - "const": "sql:allow-select" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "sql:deny-close" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "sql:deny-execute" - }, - { - "description": "Denies the load command without any pre-configured scope.", - "type": "string", - "const": "sql:deny-load" - }, - { - "description": "Denies the select command without any pre-configured scope.", - "type": "string", - "const": "sql:deny-select" - } - ] - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - }, - "ShellScopeEntryAllowedArg": { - "description": "A command argument allowed to be executed by the webview API.", - "anyOf": [ - { - "description": "A non-configurable argument that is passed to the command in the order it was specified.", - "type": "string" - }, - { - "description": "A variable that is set while calling the command from the webview API.", - "type": "object", - "required": [ - "validator" - ], - "properties": { - "raw": { - "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", - "default": false, - "type": "boolean" - }, - "validator": { - "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", - "type": "string" - } - }, - "additionalProperties": false - } - ] - }, - "ShellScopeEntryAllowedArgs": { - "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", - "anyOf": [ - { - "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", - "type": "boolean" - }, - { - "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", - "type": "array", - "items": { - "$ref": "#/definitions/ShellScopeEntryAllowedArg" - } - } - ] - } - } -} \ No newline at end of file diff --git a/src-tauri/gen/schemas/linux-schema.json b/src-tauri/gen/schemas/linux-schema.json deleted file mode 100644 index 8d0d785a..00000000 --- a/src-tauri/gen/schemas/linux-schema.json +++ /dev/null @@ -1,5186 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CapabilityFile", - "description": "Capability formats accepted in a capability file.", - "anyOf": [ - { - "description": "A single capability.", - "allOf": [ - { - "$ref": "#/definitions/Capability" - } - ] - }, - { - "description": "A list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - }, - { - "description": "A list of capabilities.", - "type": "object", - "required": [ - "capabilities" - ], - "properties": { - "capabilities": { - "description": "The list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - } - } - } - ], - "definitions": { - "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", - "type": "object", - "required": [ - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", - "type": "string" - }, - "description": { - "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.", - "default": "", - "type": "string" - }, - "remote": { - "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", - "anyOf": [ - { - "$ref": "#/definitions/CapabilityRemote" - }, - { - "type": "null" - } - ] - }, - "local": { - "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", - "default": true, - "type": "boolean" - }, - "windows": { - "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "webviews": { - "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThis is only required when using on multiwebview contexts, by default all child webviews of a window that matches [`Self::windows`] are linked.\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionEntry" - }, - "uniqueItems": true - }, - "platforms": { - "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "CapabilityRemote": { - "description": "Configuration for remote URLs that are associated with the capability.", - "type": "object", - "required": [ - "urls" - ], - "properties": { - "urls": { - "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionEntry": { - "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", - "anyOf": [ - { - "description": "Reference a permission or permission set by identifier.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - { - "description": "Reference a permission or permission set by identifier and extends its scope.", - "type": "object", - "allOf": [ - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n", - "type": "string", - "const": "fs:default" - }, - { - "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta" - }, - { - "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the application folders.", - "type": "string", - "const": "fs:allow-app-read" - }, - { - "description": "This allows full recursive read access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-read-recursive" - }, - { - "description": "This allows non-recursive write access to the application folders.", - "type": "string", - "const": "fs:allow-app-write" - }, - { - "description": "This allows full recursive write access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-read" - }, - { - "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-write" - }, - { - "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-read" - }, - { - "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-write" - }, - { - "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-read" - }, - { - "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-write" - }, - { - "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-read" - }, - { - "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-write" - }, - { - "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-read" - }, - { - "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-write" - }, - { - "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-read" - }, - { - "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-write" - }, - { - "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-read" - }, - { - "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-write" - }, - { - "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-read" - }, - { - "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-write" - }, - { - "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-read" - }, - { - "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-write" - }, - { - "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-read" - }, - { - "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-write" - }, - { - "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-read" - }, - { - "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-write" - }, - { - "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-read" - }, - { - "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-write" - }, - { - "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-read" - }, - { - "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-write" - }, - { - "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-write-recursive" - }, - { - "description": "This denies access to dangerous Tauri relevant files and folders by default.", - "type": "string", - "const": "fs:deny-default" - }, - { - "description": "Enables the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-copy-file" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-create" - }, - { - "description": "Enables the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-exists" - }, - { - "description": "Enables the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-fstat" - }, - { - "description": "Enables the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-ftruncate" - }, - { - "description": "Enables the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-lstat" - }, - { - "description": "Enables the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-mkdir" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-open" - }, - { - "description": "Enables the read command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read" - }, - { - "description": "Enables the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-dir" - }, - { - "description": "Enables the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-file" - }, - { - "description": "Enables the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file" - }, - { - "description": "Enables the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines" - }, - { - "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines-next" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-remove" - }, - { - "description": "Enables the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-rename" - }, - { - "description": "Enables the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-seek" - }, - { - "description": "Enables the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-stat" - }, - { - "description": "Enables the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-truncate" - }, - { - "description": "Enables the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-unwatch" - }, - { - "description": "Enables the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-watch" - }, - { - "description": "Enables the write command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write" - }, - { - "description": "Enables the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-file" - }, - { - "description": "Enables the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-text-file" - }, - { - "description": "This permissions allows to create the application specific directories.\n", - "type": "string", - "const": "fs:create-app-specific-dirs" - }, - { - "description": "Denies the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-copy-file" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-create" - }, - { - "description": "Denies the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-exists" - }, - { - "description": "Denies the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-fstat" - }, - { - "description": "Denies the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-ftruncate" - }, - { - "description": "Denies the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-lstat" - }, - { - "description": "Denies the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-mkdir" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-open" - }, - { - "description": "Denies the read command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read" - }, - { - "description": "Denies the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-dir" - }, - { - "description": "Denies the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-file" - }, - { - "description": "Denies the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file" - }, - { - "description": "Denies the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines" - }, - { - "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines-next" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-remove" - }, - { - "description": "Denies the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-rename" - }, - { - "description": "Denies the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-seek" - }, - { - "description": "Denies the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-stat" - }, - { - "description": "Denies the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-truncate" - }, - { - "description": "Denies the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-unwatch" - }, - { - "description": "Denies the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-watch" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-linux" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-windows" - }, - { - "description": "Denies the write command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write" - }, - { - "description": "Denies the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-file" - }, - { - "description": "Denies the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-text-file" - }, - { - "description": "This enables all read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-all" - }, - { - "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", - "type": "string", - "const": "fs:read-app-specific-dirs-recursive" - }, - { - "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-dirs" - }, - { - "description": "This enables file read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-files" - }, - { - "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-meta" - }, - { - "description": "An empty permission you can use to modify the global scope.", - "type": "string", - "const": "fs:scope" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the application folders.", - "type": "string", - "const": "fs:scope-app" - }, - { - "description": "This scope permits to list all files and folders in the application directories.", - "type": "string", - "const": "fs:scope-app-index" - }, - { - "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", - "type": "string", - "const": "fs:scope-app-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", - "type": "string", - "const": "fs:scope-appcache" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", - "type": "string", - "const": "fs:scope-appcache-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appcache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:scope-appconfig" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", - "type": "string", - "const": "fs:scope-appconfig-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appconfig-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", - "type": "string", - "const": "fs:scope-appdata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", - "type": "string", - "const": "fs:scope-appdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:scope-applocaldata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", - "type": "string", - "const": "fs:scope-applocaldata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applocaldata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", - "type": "string", - "const": "fs:scope-applog" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", - "type": "string", - "const": "fs:scope-applog-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applog-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", - "type": "string", - "const": "fs:scope-audio" - }, - { - "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", - "type": "string", - "const": "fs:scope-audio-index" - }, - { - "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-audio-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", - "type": "string", - "const": "fs:scope-cache" - }, - { - "description": "This scope permits to list all files and folders in the `$CACHE`folder.", - "type": "string", - "const": "fs:scope-cache-index" - }, - { - "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-cache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", - "type": "string", - "const": "fs:scope-config" - }, - { - "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", - "type": "string", - "const": "fs:scope-config-index" - }, - { - "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-config-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", - "type": "string", - "const": "fs:scope-data" - }, - { - "description": "This scope permits to list all files and folders in the `$DATA`folder.", - "type": "string", - "const": "fs:scope-data-index" - }, - { - "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-data-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", - "type": "string", - "const": "fs:scope-desktop" - }, - { - "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", - "type": "string", - "const": "fs:scope-desktop-index" - }, - { - "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-desktop-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:scope-document" - }, - { - "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", - "type": "string", - "const": "fs:scope-document-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-document-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:scope-download" - }, - { - "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", - "type": "string", - "const": "fs:scope-download-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-download-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", - "type": "string", - "const": "fs:scope-exe" - }, - { - "description": "This scope permits to list all files and folders in the `$EXE`folder.", - "type": "string", - "const": "fs:scope-exe-index" - }, - { - "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-exe-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", - "type": "string", - "const": "fs:scope-font" - }, - { - "description": "This scope permits to list all files and folders in the `$FONT`folder.", - "type": "string", - "const": "fs:scope-font-index" - }, - { - "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-font-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", - "type": "string", - "const": "fs:scope-home" - }, - { - "description": "This scope permits to list all files and folders in the `$HOME`folder.", - "type": "string", - "const": "fs:scope-home-index" - }, - { - "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-home-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:scope-localdata" - }, - { - "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", - "type": "string", - "const": "fs:scope-localdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-localdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", - "type": "string", - "const": "fs:scope-log" - }, - { - "description": "This scope permits to list all files and folders in the `$LOG`folder.", - "type": "string", - "const": "fs:scope-log-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-log-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", - "type": "string", - "const": "fs:scope-picture" - }, - { - "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", - "type": "string", - "const": "fs:scope-picture-index" - }, - { - "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-picture-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", - "type": "string", - "const": "fs:scope-public" - }, - { - "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", - "type": "string", - "const": "fs:scope-public-index" - }, - { - "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-public-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", - "type": "string", - "const": "fs:scope-resource" - }, - { - "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", - "type": "string", - "const": "fs:scope-resource-index" - }, - { - "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-resource-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", - "type": "string", - "const": "fs:scope-runtime" - }, - { - "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", - "type": "string", - "const": "fs:scope-runtime-index" - }, - { - "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-runtime-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", - "type": "string", - "const": "fs:scope-temp" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMP`folder.", - "type": "string", - "const": "fs:scope-temp-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-temp-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:scope-template" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", - "type": "string", - "const": "fs:scope-template-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-template-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", - "type": "string", - "const": "fs:scope-video" - }, - { - "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", - "type": "string", - "const": "fs:scope-video-index" - }, - { - "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-video-recursive" - }, - { - "description": "This enables all write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-all" - }, - { - "description": "This enables all file write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-files" - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "FsScopeEntry", - "description": "FS scope entry.", - "anyOf": [ - { - "description": "FS scope path.", - "type": "string" - }, - { - "type": "object", - "required": [ - "path" - ], - "properties": { - "path": { - "description": "FS scope path.", - "type": "string" - } - } - } - ] - } - }, - "deny": { - "items": { - "title": "FsScopeEntry", - "description": "FS scope entry.", - "anyOf": [ - { - "description": "FS scope path.", - "type": "string" - }, - { - "type": "object", - "required": [ - "path" - ], - "properties": { - "path": { - "description": "FS scope path.", - "type": "string" - } - } - } - ] - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "const": "shell:default" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute" - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open" - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn" - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute" - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open" - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn" - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write" - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "deny": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - } - ], - "required": [ - "identifier" - ] - } - ] - }, - "Identifier": { - "description": "Permission identifier", - "oneOf": [ - { - "description": "Allows reading the CLI matches", - "type": "string", - "const": "cli:default" - }, - { - "description": "Enables the cli_matches command without any pre-configured scope.", - "type": "string", - "const": "cli:allow-cli-matches" - }, - { - "description": "Denies the cli_matches command without any pre-configured scope.", - "type": "string", - "const": "cli:deny-cli-matches" - }, - { - "description": "Default core plugins set which includes:\n- 'core:path:default'\n- 'core:event:default'\n- 'core:window:default'\n- 'core:webview:default'\n- 'core:app:default'\n- 'core:image:default'\n- 'core:resources:default'\n- 'core:menu:default'\n- 'core:tray:default'\n", - "type": "string", - "const": "core:default" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:app:default" - }, - { - "description": "Enables the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-hide" - }, - { - "description": "Enables the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-show" - }, - { - "description": "Enables the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-default-window-icon" - }, - { - "description": "Enables the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-name" - }, - { - "description": "Enables the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-set-app-theme" - }, - { - "description": "Enables the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-tauri-version" - }, - { - "description": "Enables the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-version" - }, - { - "description": "Denies the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-hide" - }, - { - "description": "Denies the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-show" - }, - { - "description": "Denies the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-default-window-icon" - }, - { - "description": "Denies the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-name" - }, - { - "description": "Denies the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-set-app-theme" - }, - { - "description": "Denies the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-tauri-version" - }, - { - "description": "Denies the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-version" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:event:default" - }, - { - "description": "Enables the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit" - }, - { - "description": "Enables the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit-to" - }, - { - "description": "Enables the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-listen" - }, - { - "description": "Enables the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-unlisten" - }, - { - "description": "Denies the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit" - }, - { - "description": "Denies the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit-to" - }, - { - "description": "Denies the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-listen" - }, - { - "description": "Denies the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-unlisten" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:image:default" - }, - { - "description": "Enables the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-bytes" - }, - { - "description": "Enables the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-path" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-new" - }, - { - "description": "Enables the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-rgba" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-size" - }, - { - "description": "Denies the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-bytes" - }, - { - "description": "Denies the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-path" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-new" - }, - { - "description": "Denies the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-rgba" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-size" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:menu:default" - }, - { - "description": "Enables the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-append" - }, - { - "description": "Enables the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-create-default" - }, - { - "description": "Enables the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-get" - }, - { - "description": "Enables the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-insert" - }, - { - "description": "Enables the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-checked" - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-enabled" - }, - { - "description": "Enables the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-items" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-new" - }, - { - "description": "Enables the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-popup" - }, - { - "description": "Enables the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-prepend" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove" - }, - { - "description": "Enables the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove-at" - }, - { - "description": "Enables the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-accelerator" - }, - { - "description": "Enables the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-app-menu" - }, - { - "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-help-menu-for-nsapp" - }, - { - "description": "Enables the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-window-menu" - }, - { - "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-windows-menu-for-nsapp" - }, - { - "description": "Enables the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-checked" - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-enabled" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-icon" - }, - { - "description": "Enables the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-text" - }, - { - "description": "Enables the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-text" - }, - { - "description": "Denies the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-append" - }, - { - "description": "Denies the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-create-default" - }, - { - "description": "Denies the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-get" - }, - { - "description": "Denies the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-insert" - }, - { - "description": "Denies the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-checked" - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-enabled" - }, - { - "description": "Denies the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-items" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-new" - }, - { - "description": "Denies the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-popup" - }, - { - "description": "Denies the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-prepend" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove" - }, - { - "description": "Denies the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove-at" - }, - { - "description": "Denies the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-accelerator" - }, - { - "description": "Denies the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-app-menu" - }, - { - "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-help-menu-for-nsapp" - }, - { - "description": "Denies the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-window-menu" - }, - { - "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-windows-menu-for-nsapp" - }, - { - "description": "Denies the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-checked" - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-enabled" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-icon" - }, - { - "description": "Denies the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-text" - }, - { - "description": "Denies the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-text" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:path:default" - }, - { - "description": "Enables the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-basename" - }, - { - "description": "Enables the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-dirname" - }, - { - "description": "Enables the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-extname" - }, - { - "description": "Enables the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-is-absolute" - }, - { - "description": "Enables the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-join" - }, - { - "description": "Enables the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-normalize" - }, - { - "description": "Enables the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve" - }, - { - "description": "Enables the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve-directory" - }, - { - "description": "Denies the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-basename" - }, - { - "description": "Denies the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-dirname" - }, - { - "description": "Denies the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-extname" - }, - { - "description": "Denies the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-is-absolute" - }, - { - "description": "Denies the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-join" - }, - { - "description": "Denies the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-normalize" - }, - { - "description": "Denies the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve" - }, - { - "description": "Denies the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve-directory" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:resources:default" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:allow-close" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:deny-close" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:tray:default" - }, - { - "description": "Enables the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-get-by-id" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-new" - }, - { - "description": "Enables the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-remove-by-id" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon" - }, - { - "description": "Enables the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon-as-template" - }, - { - "description": "Enables the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-menu" - }, - { - "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-show-menu-on-left-click" - }, - { - "description": "Enables the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-temp-dir-path" - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-title" - }, - { - "description": "Enables the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-tooltip" - }, - { - "description": "Enables the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-visible" - }, - { - "description": "Denies the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-get-by-id" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-new" - }, - { - "description": "Denies the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-remove-by-id" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon" - }, - { - "description": "Denies the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon-as-template" - }, - { - "description": "Denies the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-menu" - }, - { - "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-show-menu-on-left-click" - }, - { - "description": "Denies the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-temp-dir-path" - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-title" - }, - { - "description": "Denies the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-tooltip" - }, - { - "description": "Denies the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-visible" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:webview:default" - }, - { - "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-clear-all-browsing-data" - }, - { - "description": "Enables the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview" - }, - { - "description": "Enables the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview-window" - }, - { - "description": "Enables the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-get-all-webviews" - }, - { - "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-internal-toggle-devtools" - }, - { - "description": "Enables the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-print" - }, - { - "description": "Enables the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-reparent" - }, - { - "description": "Enables the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-focus" - }, - { - "description": "Enables the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-position" - }, - { - "description": "Enables the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-size" - }, - { - "description": "Enables the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-zoom" - }, - { - "description": "Enables the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-close" - }, - { - "description": "Enables the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-hide" - }, - { - "description": "Enables the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-position" - }, - { - "description": "Enables the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-show" - }, - { - "description": "Enables the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-size" - }, - { - "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-clear-all-browsing-data" - }, - { - "description": "Denies the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview" - }, - { - "description": "Denies the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview-window" - }, - { - "description": "Denies the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-get-all-webviews" - }, - { - "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-internal-toggle-devtools" - }, - { - "description": "Denies the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-print" - }, - { - "description": "Denies the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-reparent" - }, - { - "description": "Denies the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-focus" - }, - { - "description": "Denies the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-position" - }, - { - "description": "Denies the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-size" - }, - { - "description": "Denies the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-zoom" - }, - { - "description": "Denies the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-close" - }, - { - "description": "Denies the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-hide" - }, - { - "description": "Denies the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-position" - }, - { - "description": "Denies the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-show" - }, - { - "description": "Denies the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-size" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:window:default" - }, - { - "description": "Enables the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-available-monitors" - }, - { - "description": "Enables the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-center" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-close" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-create" - }, - { - "description": "Enables the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-current-monitor" - }, - { - "description": "Enables the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-cursor-position" - }, - { - "description": "Enables the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-destroy" - }, - { - "description": "Enables the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-get-all-windows" - }, - { - "description": "Enables the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-hide" - }, - { - "description": "Enables the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-position" - }, - { - "description": "Enables the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-size" - }, - { - "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-internal-toggle-maximize" - }, - { - "description": "Enables the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-closable" - }, - { - "description": "Enables the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-decorated" - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-enabled" - }, - { - "description": "Enables the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-focused" - }, - { - "description": "Enables the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-fullscreen" - }, - { - "description": "Enables the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximizable" - }, - { - "description": "Enables the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximized" - }, - { - "description": "Enables the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimizable" - }, - { - "description": "Enables the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimized" - }, - { - "description": "Enables the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-resizable" - }, - { - "description": "Enables the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-visible" - }, - { - "description": "Enables the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-maximize" - }, - { - "description": "Enables the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-minimize" - }, - { - "description": "Enables the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-monitor-from-point" - }, - { - "description": "Enables the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-position" - }, - { - "description": "Enables the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-size" - }, - { - "description": "Enables the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-primary-monitor" - }, - { - "description": "Enables the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-request-user-attention" - }, - { - "description": "Enables the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-scale-factor" - }, - { - "description": "Enables the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-bottom" - }, - { - "description": "Enables the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-top" - }, - { - "description": "Enables the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-closable" - }, - { - "description": "Enables the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-content-protected" - }, - { - "description": "Enables the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-grab" - }, - { - "description": "Enables the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-icon" - }, - { - "description": "Enables the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-position" - }, - { - "description": "Enables the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-visible" - }, - { - "description": "Enables the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-decorations" - }, - { - "description": "Enables the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-effects" - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-enabled" - }, - { - "description": "Enables the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-focus" - }, - { - "description": "Enables the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-fullscreen" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-icon" - }, - { - "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-ignore-cursor-events" - }, - { - "description": "Enables the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-max-size" - }, - { - "description": "Enables the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-maximizable" - }, - { - "description": "Enables the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-min-size" - }, - { - "description": "Enables the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-minimizable" - }, - { - "description": "Enables the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-position" - }, - { - "description": "Enables the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-progress-bar" - }, - { - "description": "Enables the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-resizable" - }, - { - "description": "Enables the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-shadow" - }, - { - "description": "Enables the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size" - }, - { - "description": "Enables the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size-constraints" - }, - { - "description": "Enables the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-skip-taskbar" - }, - { - "description": "Enables the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-theme" - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title" - }, - { - "description": "Enables the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title-bar-style" - }, - { - "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-visible-on-all-workspaces" - }, - { - "description": "Enables the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-show" - }, - { - "description": "Enables the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-dragging" - }, - { - "description": "Enables the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-resize-dragging" - }, - { - "description": "Enables the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-theme" - }, - { - "description": "Enables the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-title" - }, - { - "description": "Enables the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-toggle-maximize" - }, - { - "description": "Enables the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unmaximize" - }, - { - "description": "Enables the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unminimize" - }, - { - "description": "Denies the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-available-monitors" - }, - { - "description": "Denies the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-center" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-close" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-create" - }, - { - "description": "Denies the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-current-monitor" - }, - { - "description": "Denies the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-cursor-position" - }, - { - "description": "Denies the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-destroy" - }, - { - "description": "Denies the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-get-all-windows" - }, - { - "description": "Denies the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-hide" - }, - { - "description": "Denies the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-position" - }, - { - "description": "Denies the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-size" - }, - { - "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-internal-toggle-maximize" - }, - { - "description": "Denies the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-closable" - }, - { - "description": "Denies the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-decorated" - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-enabled" - }, - { - "description": "Denies the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-focused" - }, - { - "description": "Denies the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-fullscreen" - }, - { - "description": "Denies the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximizable" - }, - { - "description": "Denies the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximized" - }, - { - "description": "Denies the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimizable" - }, - { - "description": "Denies the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimized" - }, - { - "description": "Denies the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-resizable" - }, - { - "description": "Denies the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-visible" - }, - { - "description": "Denies the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-maximize" - }, - { - "description": "Denies the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-minimize" - }, - { - "description": "Denies the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-monitor-from-point" - }, - { - "description": "Denies the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-position" - }, - { - "description": "Denies the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-size" - }, - { - "description": "Denies the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-primary-monitor" - }, - { - "description": "Denies the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-request-user-attention" - }, - { - "description": "Denies the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-scale-factor" - }, - { - "description": "Denies the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-bottom" - }, - { - "description": "Denies the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-top" - }, - { - "description": "Denies the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-closable" - }, - { - "description": "Denies the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-content-protected" - }, - { - "description": "Denies the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-grab" - }, - { - "description": "Denies the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-icon" - }, - { - "description": "Denies the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-position" - }, - { - "description": "Denies the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-visible" - }, - { - "description": "Denies the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-decorations" - }, - { - "description": "Denies the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-effects" - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-enabled" - }, - { - "description": "Denies the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-focus" - }, - { - "description": "Denies the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-fullscreen" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-icon" - }, - { - "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-ignore-cursor-events" - }, - { - "description": "Denies the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-max-size" - }, - { - "description": "Denies the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-maximizable" - }, - { - "description": "Denies the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-min-size" - }, - { - "description": "Denies the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-minimizable" - }, - { - "description": "Denies the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-position" - }, - { - "description": "Denies the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-progress-bar" - }, - { - "description": "Denies the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-resizable" - }, - { - "description": "Denies the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-shadow" - }, - { - "description": "Denies the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size" - }, - { - "description": "Denies the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size-constraints" - }, - { - "description": "Denies the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-skip-taskbar" - }, - { - "description": "Denies the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-theme" - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title" - }, - { - "description": "Denies the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title-bar-style" - }, - { - "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-visible-on-all-workspaces" - }, - { - "description": "Denies the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-show" - }, - { - "description": "Denies the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-dragging" - }, - { - "description": "Denies the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-resize-dragging" - }, - { - "description": "Denies the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-theme" - }, - { - "description": "Denies the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-title" - }, - { - "description": "Denies the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-toggle-maximize" - }, - { - "description": "Denies the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unmaximize" - }, - { - "description": "Denies the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unminimize" - }, - { - "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n", - "type": "string", - "const": "dialog:default" - }, - { - "description": "Enables the ask command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-ask" - }, - { - "description": "Enables the confirm command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-confirm" - }, - { - "description": "Enables the message command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-message" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-open" - }, - { - "description": "Enables the save command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-save" - }, - { - "description": "Denies the ask command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-ask" - }, - { - "description": "Denies the confirm command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-confirm" - }, - { - "description": "Denies the message command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-message" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-open" - }, - { - "description": "Denies the save command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-save" - }, - { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n", - "type": "string", - "const": "fs:default" - }, - { - "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta" - }, - { - "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the application folders.", - "type": "string", - "const": "fs:allow-app-read" - }, - { - "description": "This allows full recursive read access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-read-recursive" - }, - { - "description": "This allows non-recursive write access to the application folders.", - "type": "string", - "const": "fs:allow-app-write" - }, - { - "description": "This allows full recursive write access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-read" - }, - { - "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-write" - }, - { - "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-read" - }, - { - "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-write" - }, - { - "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-read" - }, - { - "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-write" - }, - { - "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-read" - }, - { - "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-write" - }, - { - "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-read" - }, - { - "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-write" - }, - { - "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-read" - }, - { - "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-write" - }, - { - "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-read" - }, - { - "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-write" - }, - { - "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-read" - }, - { - "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-write" - }, - { - "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-read" - }, - { - "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-write" - }, - { - "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-read" - }, - { - "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-write" - }, - { - "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-read" - }, - { - "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-write" - }, - { - "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-read" - }, - { - "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-write" - }, - { - "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-read" - }, - { - "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-write" - }, - { - "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-write-recursive" - }, - { - "description": "This denies access to dangerous Tauri relevant files and folders by default.", - "type": "string", - "const": "fs:deny-default" - }, - { - "description": "Enables the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-copy-file" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-create" - }, - { - "description": "Enables the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-exists" - }, - { - "description": "Enables the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-fstat" - }, - { - "description": "Enables the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-ftruncate" - }, - { - "description": "Enables the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-lstat" - }, - { - "description": "Enables the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-mkdir" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-open" - }, - { - "description": "Enables the read command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read" - }, - { - "description": "Enables the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-dir" - }, - { - "description": "Enables the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-file" - }, - { - "description": "Enables the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file" - }, - { - "description": "Enables the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines" - }, - { - "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines-next" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-remove" - }, - { - "description": "Enables the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-rename" - }, - { - "description": "Enables the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-seek" - }, - { - "description": "Enables the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-stat" - }, - { - "description": "Enables the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-truncate" - }, - { - "description": "Enables the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-unwatch" - }, - { - "description": "Enables the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-watch" - }, - { - "description": "Enables the write command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write" - }, - { - "description": "Enables the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-file" - }, - { - "description": "Enables the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-text-file" - }, - { - "description": "This permissions allows to create the application specific directories.\n", - "type": "string", - "const": "fs:create-app-specific-dirs" - }, - { - "description": "Denies the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-copy-file" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-create" - }, - { - "description": "Denies the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-exists" - }, - { - "description": "Denies the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-fstat" - }, - { - "description": "Denies the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-ftruncate" - }, - { - "description": "Denies the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-lstat" - }, - { - "description": "Denies the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-mkdir" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-open" - }, - { - "description": "Denies the read command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read" - }, - { - "description": "Denies the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-dir" - }, - { - "description": "Denies the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-file" - }, - { - "description": "Denies the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file" - }, - { - "description": "Denies the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines" - }, - { - "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines-next" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-remove" - }, - { - "description": "Denies the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-rename" - }, - { - "description": "Denies the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-seek" - }, - { - "description": "Denies the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-stat" - }, - { - "description": "Denies the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-truncate" - }, - { - "description": "Denies the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-unwatch" - }, - { - "description": "Denies the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-watch" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-linux" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-windows" - }, - { - "description": "Denies the write command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write" - }, - { - "description": "Denies the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-file" - }, - { - "description": "Denies the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-text-file" - }, - { - "description": "This enables all read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-all" - }, - { - "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", - "type": "string", - "const": "fs:read-app-specific-dirs-recursive" - }, - { - "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-dirs" - }, - { - "description": "This enables file read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-files" - }, - { - "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-meta" - }, - { - "description": "An empty permission you can use to modify the global scope.", - "type": "string", - "const": "fs:scope" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the application folders.", - "type": "string", - "const": "fs:scope-app" - }, - { - "description": "This scope permits to list all files and folders in the application directories.", - "type": "string", - "const": "fs:scope-app-index" - }, - { - "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", - "type": "string", - "const": "fs:scope-app-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", - "type": "string", - "const": "fs:scope-appcache" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", - "type": "string", - "const": "fs:scope-appcache-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appcache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:scope-appconfig" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", - "type": "string", - "const": "fs:scope-appconfig-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appconfig-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", - "type": "string", - "const": "fs:scope-appdata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", - "type": "string", - "const": "fs:scope-appdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:scope-applocaldata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", - "type": "string", - "const": "fs:scope-applocaldata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applocaldata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", - "type": "string", - "const": "fs:scope-applog" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", - "type": "string", - "const": "fs:scope-applog-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applog-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", - "type": "string", - "const": "fs:scope-audio" - }, - { - "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", - "type": "string", - "const": "fs:scope-audio-index" - }, - { - "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-audio-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", - "type": "string", - "const": "fs:scope-cache" - }, - { - "description": "This scope permits to list all files and folders in the `$CACHE`folder.", - "type": "string", - "const": "fs:scope-cache-index" - }, - { - "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-cache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", - "type": "string", - "const": "fs:scope-config" - }, - { - "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", - "type": "string", - "const": "fs:scope-config-index" - }, - { - "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-config-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", - "type": "string", - "const": "fs:scope-data" - }, - { - "description": "This scope permits to list all files and folders in the `$DATA`folder.", - "type": "string", - "const": "fs:scope-data-index" - }, - { - "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-data-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", - "type": "string", - "const": "fs:scope-desktop" - }, - { - "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", - "type": "string", - "const": "fs:scope-desktop-index" - }, - { - "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-desktop-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:scope-document" - }, - { - "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", - "type": "string", - "const": "fs:scope-document-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-document-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:scope-download" - }, - { - "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", - "type": "string", - "const": "fs:scope-download-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-download-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", - "type": "string", - "const": "fs:scope-exe" - }, - { - "description": "This scope permits to list all files and folders in the `$EXE`folder.", - "type": "string", - "const": "fs:scope-exe-index" - }, - { - "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-exe-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", - "type": "string", - "const": "fs:scope-font" - }, - { - "description": "This scope permits to list all files and folders in the `$FONT`folder.", - "type": "string", - "const": "fs:scope-font-index" - }, - { - "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-font-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", - "type": "string", - "const": "fs:scope-home" - }, - { - "description": "This scope permits to list all files and folders in the `$HOME`folder.", - "type": "string", - "const": "fs:scope-home-index" - }, - { - "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-home-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:scope-localdata" - }, - { - "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", - "type": "string", - "const": "fs:scope-localdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-localdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", - "type": "string", - "const": "fs:scope-log" - }, - { - "description": "This scope permits to list all files and folders in the `$LOG`folder.", - "type": "string", - "const": "fs:scope-log-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-log-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", - "type": "string", - "const": "fs:scope-picture" - }, - { - "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", - "type": "string", - "const": "fs:scope-picture-index" - }, - { - "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-picture-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", - "type": "string", - "const": "fs:scope-public" - }, - { - "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", - "type": "string", - "const": "fs:scope-public-index" - }, - { - "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-public-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", - "type": "string", - "const": "fs:scope-resource" - }, - { - "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", - "type": "string", - "const": "fs:scope-resource-index" - }, - { - "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-resource-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", - "type": "string", - "const": "fs:scope-runtime" - }, - { - "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", - "type": "string", - "const": "fs:scope-runtime-index" - }, - { - "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-runtime-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", - "type": "string", - "const": "fs:scope-temp" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMP`folder.", - "type": "string", - "const": "fs:scope-temp-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-temp-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:scope-template" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", - "type": "string", - "const": "fs:scope-template-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-template-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", - "type": "string", - "const": "fs:scope-video" - }, - { - "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", - "type": "string", - "const": "fs:scope-video-index" - }, - { - "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-video-recursive" - }, - { - "description": "This enables all write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-all" - }, - { - "description": "This enables all file write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-files" - }, - { - "description": "This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n", - "type": "string", - "const": "os:default" - }, - { - "description": "Enables the arch command without any pre-configured scope.", - "type": "string", - "const": "os:allow-arch" - }, - { - "description": "Enables the exe_extension command without any pre-configured scope.", - "type": "string", - "const": "os:allow-exe-extension" - }, - { - "description": "Enables the family command without any pre-configured scope.", - "type": "string", - "const": "os:allow-family" - }, - { - "description": "Enables the hostname command without any pre-configured scope.", - "type": "string", - "const": "os:allow-hostname" - }, - { - "description": "Enables the locale command without any pre-configured scope.", - "type": "string", - "const": "os:allow-locale" - }, - { - "description": "Enables the os_type command without any pre-configured scope.", - "type": "string", - "const": "os:allow-os-type" - }, - { - "description": "Enables the platform command without any pre-configured scope.", - "type": "string", - "const": "os:allow-platform" - }, - { - "description": "Enables the version command without any pre-configured scope.", - "type": "string", - "const": "os:allow-version" - }, - { - "description": "Denies the arch command without any pre-configured scope.", - "type": "string", - "const": "os:deny-arch" - }, - { - "description": "Denies the exe_extension command without any pre-configured scope.", - "type": "string", - "const": "os:deny-exe-extension" - }, - { - "description": "Denies the family command without any pre-configured scope.", - "type": "string", - "const": "os:deny-family" - }, - { - "description": "Denies the hostname command without any pre-configured scope.", - "type": "string", - "const": "os:deny-hostname" - }, - { - "description": "Denies the locale command without any pre-configured scope.", - "type": "string", - "const": "os:deny-locale" - }, - { - "description": "Denies the os_type command without any pre-configured scope.", - "type": "string", - "const": "os:deny-os-type" - }, - { - "description": "Denies the platform command without any pre-configured scope.", - "type": "string", - "const": "os:deny-platform" - }, - { - "description": "Denies the version command without any pre-configured scope.", - "type": "string", - "const": "os:deny-version" - }, - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "const": "shell:default" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute" - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open" - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn" - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute" - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open" - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn" - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write" - }, - { - "description": "### Default Permissions\n\nThis permission set configures what kind of\ndatabase operations are available from the sql plugin.\n\n### Granted Permissions\n\nAll reading related operations are enabled.\nAlso allows to load or close a connection.\n\n", - "type": "string", - "const": "sql:default" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "sql:allow-close" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "sql:allow-execute" - }, - { - "description": "Enables the load command without any pre-configured scope.", - "type": "string", - "const": "sql:allow-load" - }, - { - "description": "Enables the select command without any pre-configured scope.", - "type": "string", - "const": "sql:allow-select" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "sql:deny-close" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "sql:deny-execute" - }, - { - "description": "Denies the load command without any pre-configured scope.", - "type": "string", - "const": "sql:deny-load" - }, - { - "description": "Denies the select command without any pre-configured scope.", - "type": "string", - "const": "sql:deny-select" - } - ] - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - }, - "ShellScopeEntryAllowedArg": { - "description": "A command argument allowed to be executed by the webview API.", - "anyOf": [ - { - "description": "A non-configurable argument that is passed to the command in the order it was specified.", - "type": "string" - }, - { - "description": "A variable that is set while calling the command from the webview API.", - "type": "object", - "required": [ - "validator" - ], - "properties": { - "raw": { - "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", - "default": false, - "type": "boolean" - }, - "validator": { - "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", - "type": "string" - } - }, - "additionalProperties": false - } - ] - }, - "ShellScopeEntryAllowedArgs": { - "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", - "anyOf": [ - { - "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", - "type": "boolean" - }, - { - "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", - "type": "array", - "items": { - "$ref": "#/definitions/ShellScopeEntryAllowedArg" - } - } - ] - } - } -} \ No newline at end of file diff --git a/src-tauri/gen/schemas/macOS-schema.json b/src-tauri/gen/schemas/macOS-schema.json deleted file mode 100644 index fd6f55d9..00000000 --- a/src-tauri/gen/schemas/macOS-schema.json +++ /dev/null @@ -1,5256 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CapabilityFile", - "description": "Capability formats accepted in a capability file.", - "anyOf": [ - { - "description": "A single capability.", - "allOf": [ - { - "$ref": "#/definitions/Capability" - } - ] - }, - { - "description": "A list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - }, - { - "description": "A list of capabilities.", - "type": "object", - "required": [ - "capabilities" - ], - "properties": { - "capabilities": { - "description": "The list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - } - } - } - ], - "definitions": { - "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", - "type": "object", - "required": [ - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", - "type": "string" - }, - "description": { - "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.", - "default": "", - "type": "string" - }, - "remote": { - "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", - "anyOf": [ - { - "$ref": "#/definitions/CapabilityRemote" - }, - { - "type": "null" - } - ] - }, - "local": { - "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", - "default": true, - "type": "boolean" - }, - "windows": { - "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "webviews": { - "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThis is only required when using on multiwebview contexts, by default all child webviews of a window that matches [`Self::windows`] are linked.\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionEntry" - }, - "uniqueItems": true - }, - "platforms": { - "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "CapabilityRemote": { - "description": "Configuration for remote URLs that are associated with the capability.", - "type": "object", - "required": [ - "urls" - ], - "properties": { - "urls": { - "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionEntry": { - "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", - "anyOf": [ - { - "description": "Reference a permission or permission set by identifier.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - { - "description": "Reference a permission or permission set by identifier and extends its scope.", - "type": "object", - "allOf": [ - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n", - "type": "string", - "const": "fs:default" - }, - { - "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta" - }, - { - "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the application folders.", - "type": "string", - "const": "fs:allow-app-read" - }, - { - "description": "This allows full recursive read access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-read-recursive" - }, - { - "description": "This allows non-recursive write access to the application folders.", - "type": "string", - "const": "fs:allow-app-write" - }, - { - "description": "This allows full recursive write access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-read" - }, - { - "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-write" - }, - { - "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-read" - }, - { - "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-write" - }, - { - "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-read" - }, - { - "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-write" - }, - { - "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-read" - }, - { - "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-write" - }, - { - "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-read" - }, - { - "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-write" - }, - { - "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-read" - }, - { - "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-write" - }, - { - "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-read" - }, - { - "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-write" - }, - { - "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-read" - }, - { - "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-write" - }, - { - "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-read" - }, - { - "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-write" - }, - { - "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-read" - }, - { - "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-write" - }, - { - "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-read" - }, - { - "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-write" - }, - { - "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-read" - }, - { - "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-write" - }, - { - "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-read" - }, - { - "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-write" - }, - { - "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-write-recursive" - }, - { - "description": "This denies access to dangerous Tauri relevant files and folders by default.", - "type": "string", - "const": "fs:deny-default" - }, - { - "description": "Enables the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-copy-file" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-create" - }, - { - "description": "Enables the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-exists" - }, - { - "description": "Enables the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-fstat" - }, - { - "description": "Enables the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-ftruncate" - }, - { - "description": "Enables the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-lstat" - }, - { - "description": "Enables the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-mkdir" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-open" - }, - { - "description": "Enables the read command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read" - }, - { - "description": "Enables the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-dir" - }, - { - "description": "Enables the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-file" - }, - { - "description": "Enables the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file" - }, - { - "description": "Enables the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines" - }, - { - "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines-next" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-remove" - }, - { - "description": "Enables the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-rename" - }, - { - "description": "Enables the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-seek" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-size" - }, - { - "description": "Enables the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-stat" - }, - { - "description": "Enables the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-truncate" - }, - { - "description": "Enables the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-unwatch" - }, - { - "description": "Enables the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-watch" - }, - { - "description": "Enables the write command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write" - }, - { - "description": "Enables the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-file" - }, - { - "description": "Enables the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-text-file" - }, - { - "description": "This permissions allows to create the application specific directories.\n", - "type": "string", - "const": "fs:create-app-specific-dirs" - }, - { - "description": "Denies the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-copy-file" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-create" - }, - { - "description": "Denies the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-exists" - }, - { - "description": "Denies the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-fstat" - }, - { - "description": "Denies the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-ftruncate" - }, - { - "description": "Denies the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-lstat" - }, - { - "description": "Denies the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-mkdir" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-open" - }, - { - "description": "Denies the read command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read" - }, - { - "description": "Denies the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-dir" - }, - { - "description": "Denies the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-file" - }, - { - "description": "Denies the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file" - }, - { - "description": "Denies the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines" - }, - { - "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines-next" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-remove" - }, - { - "description": "Denies the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-rename" - }, - { - "description": "Denies the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-seek" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-size" - }, - { - "description": "Denies the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-stat" - }, - { - "description": "Denies the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-truncate" - }, - { - "description": "Denies the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-unwatch" - }, - { - "description": "Denies the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-watch" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-linux" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-windows" - }, - { - "description": "Denies the write command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write" - }, - { - "description": "Denies the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-file" - }, - { - "description": "Denies the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-text-file" - }, - { - "description": "This enables all read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-all" - }, - { - "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", - "type": "string", - "const": "fs:read-app-specific-dirs-recursive" - }, - { - "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-dirs" - }, - { - "description": "This enables file read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-files" - }, - { - "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-meta" - }, - { - "description": "An empty permission you can use to modify the global scope.", - "type": "string", - "const": "fs:scope" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the application folders.", - "type": "string", - "const": "fs:scope-app" - }, - { - "description": "This scope permits to list all files and folders in the application directories.", - "type": "string", - "const": "fs:scope-app-index" - }, - { - "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", - "type": "string", - "const": "fs:scope-app-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", - "type": "string", - "const": "fs:scope-appcache" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", - "type": "string", - "const": "fs:scope-appcache-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appcache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:scope-appconfig" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", - "type": "string", - "const": "fs:scope-appconfig-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appconfig-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", - "type": "string", - "const": "fs:scope-appdata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", - "type": "string", - "const": "fs:scope-appdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:scope-applocaldata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", - "type": "string", - "const": "fs:scope-applocaldata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applocaldata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", - "type": "string", - "const": "fs:scope-applog" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", - "type": "string", - "const": "fs:scope-applog-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applog-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", - "type": "string", - "const": "fs:scope-audio" - }, - { - "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", - "type": "string", - "const": "fs:scope-audio-index" - }, - { - "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-audio-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", - "type": "string", - "const": "fs:scope-cache" - }, - { - "description": "This scope permits to list all files and folders in the `$CACHE`folder.", - "type": "string", - "const": "fs:scope-cache-index" - }, - { - "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-cache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", - "type": "string", - "const": "fs:scope-config" - }, - { - "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", - "type": "string", - "const": "fs:scope-config-index" - }, - { - "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-config-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", - "type": "string", - "const": "fs:scope-data" - }, - { - "description": "This scope permits to list all files and folders in the `$DATA`folder.", - "type": "string", - "const": "fs:scope-data-index" - }, - { - "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-data-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", - "type": "string", - "const": "fs:scope-desktop" - }, - { - "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", - "type": "string", - "const": "fs:scope-desktop-index" - }, - { - "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-desktop-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:scope-document" - }, - { - "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", - "type": "string", - "const": "fs:scope-document-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-document-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:scope-download" - }, - { - "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", - "type": "string", - "const": "fs:scope-download-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-download-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", - "type": "string", - "const": "fs:scope-exe" - }, - { - "description": "This scope permits to list all files and folders in the `$EXE`folder.", - "type": "string", - "const": "fs:scope-exe-index" - }, - { - "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-exe-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", - "type": "string", - "const": "fs:scope-font" - }, - { - "description": "This scope permits to list all files and folders in the `$FONT`folder.", - "type": "string", - "const": "fs:scope-font-index" - }, - { - "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-font-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", - "type": "string", - "const": "fs:scope-home" - }, - { - "description": "This scope permits to list all files and folders in the `$HOME`folder.", - "type": "string", - "const": "fs:scope-home-index" - }, - { - "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-home-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:scope-localdata" - }, - { - "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", - "type": "string", - "const": "fs:scope-localdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-localdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", - "type": "string", - "const": "fs:scope-log" - }, - { - "description": "This scope permits to list all files and folders in the `$LOG`folder.", - "type": "string", - "const": "fs:scope-log-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-log-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", - "type": "string", - "const": "fs:scope-picture" - }, - { - "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", - "type": "string", - "const": "fs:scope-picture-index" - }, - { - "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-picture-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", - "type": "string", - "const": "fs:scope-public" - }, - { - "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", - "type": "string", - "const": "fs:scope-public-index" - }, - { - "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-public-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", - "type": "string", - "const": "fs:scope-resource" - }, - { - "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", - "type": "string", - "const": "fs:scope-resource-index" - }, - { - "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-resource-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", - "type": "string", - "const": "fs:scope-runtime" - }, - { - "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", - "type": "string", - "const": "fs:scope-runtime-index" - }, - { - "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-runtime-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", - "type": "string", - "const": "fs:scope-temp" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMP`folder.", - "type": "string", - "const": "fs:scope-temp-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-temp-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:scope-template" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", - "type": "string", - "const": "fs:scope-template-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-template-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", - "type": "string", - "const": "fs:scope-video" - }, - { - "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", - "type": "string", - "const": "fs:scope-video-index" - }, - { - "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-video-recursive" - }, - { - "description": "This enables all write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-all" - }, - { - "description": "This enables all file write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-files" - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "FsScopeEntry", - "description": "FS scope entry.", - "anyOf": [ - { - "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - { - "type": "object", - "required": [ - "path" - ], - "properties": { - "path": { - "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - } - } - } - ] - } - }, - "deny": { - "items": { - "title": "FsScopeEntry", - "description": "FS scope entry.", - "anyOf": [ - { - "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - { - "type": "object", - "required": [ - "path" - ], - "properties": { - "path": { - "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - } - } - } - ] - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "const": "shell:default" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute" - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open" - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn" - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute" - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open" - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn" - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write" - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "deny": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - } - ], - "required": [ - "identifier" - ] - } - ] - }, - "Identifier": { - "description": "Permission identifier", - "oneOf": [ - { - "description": "Allows reading the CLI matches", - "type": "string", - "const": "cli:default" - }, - { - "description": "Enables the cli_matches command without any pre-configured scope.", - "type": "string", - "const": "cli:allow-cli-matches" - }, - { - "description": "Denies the cli_matches command without any pre-configured scope.", - "type": "string", - "const": "cli:deny-cli-matches" - }, - { - "description": "Default core plugins set which includes:\n- 'core:path:default'\n- 'core:event:default'\n- 'core:window:default'\n- 'core:webview:default'\n- 'core:app:default'\n- 'core:image:default'\n- 'core:resources:default'\n- 'core:menu:default'\n- 'core:tray:default'\n", - "type": "string", - "const": "core:default" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:app:default" - }, - { - "description": "Enables the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-hide" - }, - { - "description": "Enables the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-show" - }, - { - "description": "Enables the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-default-window-icon" - }, - { - "description": "Enables the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-name" - }, - { - "description": "Enables the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-set-app-theme" - }, - { - "description": "Enables the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-tauri-version" - }, - { - "description": "Enables the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-version" - }, - { - "description": "Denies the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-hide" - }, - { - "description": "Denies the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-show" - }, - { - "description": "Denies the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-default-window-icon" - }, - { - "description": "Denies the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-name" - }, - { - "description": "Denies the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-set-app-theme" - }, - { - "description": "Denies the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-tauri-version" - }, - { - "description": "Denies the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-version" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:event:default" - }, - { - "description": "Enables the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit" - }, - { - "description": "Enables the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit-to" - }, - { - "description": "Enables the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-listen" - }, - { - "description": "Enables the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-unlisten" - }, - { - "description": "Denies the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit" - }, - { - "description": "Denies the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit-to" - }, - { - "description": "Denies the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-listen" - }, - { - "description": "Denies the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-unlisten" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:image:default" - }, - { - "description": "Enables the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-bytes" - }, - { - "description": "Enables the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-path" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-new" - }, - { - "description": "Enables the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-rgba" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-size" - }, - { - "description": "Denies the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-bytes" - }, - { - "description": "Denies the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-path" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-new" - }, - { - "description": "Denies the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-rgba" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-size" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:menu:default" - }, - { - "description": "Enables the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-append" - }, - { - "description": "Enables the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-create-default" - }, - { - "description": "Enables the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-get" - }, - { - "description": "Enables the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-insert" - }, - { - "description": "Enables the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-checked" - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-enabled" - }, - { - "description": "Enables the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-items" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-new" - }, - { - "description": "Enables the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-popup" - }, - { - "description": "Enables the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-prepend" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove" - }, - { - "description": "Enables the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove-at" - }, - { - "description": "Enables the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-accelerator" - }, - { - "description": "Enables the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-app-menu" - }, - { - "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-help-menu-for-nsapp" - }, - { - "description": "Enables the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-window-menu" - }, - { - "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-windows-menu-for-nsapp" - }, - { - "description": "Enables the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-checked" - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-enabled" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-icon" - }, - { - "description": "Enables the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-text" - }, - { - "description": "Enables the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-text" - }, - { - "description": "Denies the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-append" - }, - { - "description": "Denies the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-create-default" - }, - { - "description": "Denies the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-get" - }, - { - "description": "Denies the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-insert" - }, - { - "description": "Denies the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-checked" - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-enabled" - }, - { - "description": "Denies the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-items" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-new" - }, - { - "description": "Denies the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-popup" - }, - { - "description": "Denies the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-prepend" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove" - }, - { - "description": "Denies the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove-at" - }, - { - "description": "Denies the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-accelerator" - }, - { - "description": "Denies the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-app-menu" - }, - { - "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-help-menu-for-nsapp" - }, - { - "description": "Denies the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-window-menu" - }, - { - "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-windows-menu-for-nsapp" - }, - { - "description": "Denies the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-checked" - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-enabled" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-icon" - }, - { - "description": "Denies the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-text" - }, - { - "description": "Denies the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-text" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:path:default" - }, - { - "description": "Enables the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-basename" - }, - { - "description": "Enables the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-dirname" - }, - { - "description": "Enables the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-extname" - }, - { - "description": "Enables the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-is-absolute" - }, - { - "description": "Enables the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-join" - }, - { - "description": "Enables the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-normalize" - }, - { - "description": "Enables the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve" - }, - { - "description": "Enables the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve-directory" - }, - { - "description": "Denies the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-basename" - }, - { - "description": "Denies the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-dirname" - }, - { - "description": "Denies the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-extname" - }, - { - "description": "Denies the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-is-absolute" - }, - { - "description": "Denies the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-join" - }, - { - "description": "Denies the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-normalize" - }, - { - "description": "Denies the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve" - }, - { - "description": "Denies the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve-directory" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:resources:default" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:allow-close" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:deny-close" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:tray:default" - }, - { - "description": "Enables the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-get-by-id" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-new" - }, - { - "description": "Enables the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-remove-by-id" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon" - }, - { - "description": "Enables the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon-as-template" - }, - { - "description": "Enables the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-menu" - }, - { - "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-show-menu-on-left-click" - }, - { - "description": "Enables the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-temp-dir-path" - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-title" - }, - { - "description": "Enables the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-tooltip" - }, - { - "description": "Enables the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-visible" - }, - { - "description": "Denies the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-get-by-id" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-new" - }, - { - "description": "Denies the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-remove-by-id" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon" - }, - { - "description": "Denies the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon-as-template" - }, - { - "description": "Denies the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-menu" - }, - { - "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-show-menu-on-left-click" - }, - { - "description": "Denies the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-temp-dir-path" - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-title" - }, - { - "description": "Denies the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-tooltip" - }, - { - "description": "Denies the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-visible" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:webview:default" - }, - { - "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-clear-all-browsing-data" - }, - { - "description": "Enables the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview" - }, - { - "description": "Enables the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview-window" - }, - { - "description": "Enables the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-get-all-webviews" - }, - { - "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-internal-toggle-devtools" - }, - { - "description": "Enables the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-print" - }, - { - "description": "Enables the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-reparent" - }, - { - "description": "Enables the set_webview_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-background-color" - }, - { - "description": "Enables the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-focus" - }, - { - "description": "Enables the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-position" - }, - { - "description": "Enables the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-size" - }, - { - "description": "Enables the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-zoom" - }, - { - "description": "Enables the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-close" - }, - { - "description": "Enables the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-hide" - }, - { - "description": "Enables the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-position" - }, - { - "description": "Enables the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-show" - }, - { - "description": "Enables the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-size" - }, - { - "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-clear-all-browsing-data" - }, - { - "description": "Denies the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview" - }, - { - "description": "Denies the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview-window" - }, - { - "description": "Denies the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-get-all-webviews" - }, - { - "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-internal-toggle-devtools" - }, - { - "description": "Denies the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-print" - }, - { - "description": "Denies the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-reparent" - }, - { - "description": "Denies the set_webview_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-background-color" - }, - { - "description": "Denies the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-focus" - }, - { - "description": "Denies the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-position" - }, - { - "description": "Denies the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-size" - }, - { - "description": "Denies the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-zoom" - }, - { - "description": "Denies the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-close" - }, - { - "description": "Denies the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-hide" - }, - { - "description": "Denies the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-position" - }, - { - "description": "Denies the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-show" - }, - { - "description": "Denies the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-size" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:window:default" - }, - { - "description": "Enables the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-available-monitors" - }, - { - "description": "Enables the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-center" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-close" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-create" - }, - { - "description": "Enables the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-current-monitor" - }, - { - "description": "Enables the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-cursor-position" - }, - { - "description": "Enables the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-destroy" - }, - { - "description": "Enables the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-get-all-windows" - }, - { - "description": "Enables the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-hide" - }, - { - "description": "Enables the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-position" - }, - { - "description": "Enables the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-size" - }, - { - "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-internal-toggle-maximize" - }, - { - "description": "Enables the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-closable" - }, - { - "description": "Enables the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-decorated" - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-enabled" - }, - { - "description": "Enables the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-focused" - }, - { - "description": "Enables the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-fullscreen" - }, - { - "description": "Enables the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximizable" - }, - { - "description": "Enables the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximized" - }, - { - "description": "Enables the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimizable" - }, - { - "description": "Enables the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimized" - }, - { - "description": "Enables the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-resizable" - }, - { - "description": "Enables the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-visible" - }, - { - "description": "Enables the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-maximize" - }, - { - "description": "Enables the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-minimize" - }, - { - "description": "Enables the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-monitor-from-point" - }, - { - "description": "Enables the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-position" - }, - { - "description": "Enables the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-size" - }, - { - "description": "Enables the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-primary-monitor" - }, - { - "description": "Enables the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-request-user-attention" - }, - { - "description": "Enables the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-scale-factor" - }, - { - "description": "Enables the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-bottom" - }, - { - "description": "Enables the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-top" - }, - { - "description": "Enables the set_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-background-color" - }, - { - "description": "Enables the set_badge_count command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-badge-count" - }, - { - "description": "Enables the set_badge_label command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-badge-label" - }, - { - "description": "Enables the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-closable" - }, - { - "description": "Enables the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-content-protected" - }, - { - "description": "Enables the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-grab" - }, - { - "description": "Enables the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-icon" - }, - { - "description": "Enables the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-position" - }, - { - "description": "Enables the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-visible" - }, - { - "description": "Enables the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-decorations" - }, - { - "description": "Enables the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-effects" - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-enabled" - }, - { - "description": "Enables the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-focus" - }, - { - "description": "Enables the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-fullscreen" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-icon" - }, - { - "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-ignore-cursor-events" - }, - { - "description": "Enables the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-max-size" - }, - { - "description": "Enables the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-maximizable" - }, - { - "description": "Enables the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-min-size" - }, - { - "description": "Enables the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-minimizable" - }, - { - "description": "Enables the set_overlay_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-overlay-icon" - }, - { - "description": "Enables the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-position" - }, - { - "description": "Enables the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-progress-bar" - }, - { - "description": "Enables the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-resizable" - }, - { - "description": "Enables the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-shadow" - }, - { - "description": "Enables the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size" - }, - { - "description": "Enables the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size-constraints" - }, - { - "description": "Enables the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-skip-taskbar" - }, - { - "description": "Enables the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-theme" - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title" - }, - { - "description": "Enables the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title-bar-style" - }, - { - "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-visible-on-all-workspaces" - }, - { - "description": "Enables the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-show" - }, - { - "description": "Enables the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-dragging" - }, - { - "description": "Enables the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-resize-dragging" - }, - { - "description": "Enables the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-theme" - }, - { - "description": "Enables the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-title" - }, - { - "description": "Enables the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-toggle-maximize" - }, - { - "description": "Enables the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unmaximize" - }, - { - "description": "Enables the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unminimize" - }, - { - "description": "Denies the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-available-monitors" - }, - { - "description": "Denies the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-center" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-close" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-create" - }, - { - "description": "Denies the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-current-monitor" - }, - { - "description": "Denies the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-cursor-position" - }, - { - "description": "Denies the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-destroy" - }, - { - "description": "Denies the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-get-all-windows" - }, - { - "description": "Denies the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-hide" - }, - { - "description": "Denies the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-position" - }, - { - "description": "Denies the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-size" - }, - { - "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-internal-toggle-maximize" - }, - { - "description": "Denies the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-closable" - }, - { - "description": "Denies the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-decorated" - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-enabled" - }, - { - "description": "Denies the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-focused" - }, - { - "description": "Denies the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-fullscreen" - }, - { - "description": "Denies the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximizable" - }, - { - "description": "Denies the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximized" - }, - { - "description": "Denies the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimizable" - }, - { - "description": "Denies the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimized" - }, - { - "description": "Denies the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-resizable" - }, - { - "description": "Denies the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-visible" - }, - { - "description": "Denies the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-maximize" - }, - { - "description": "Denies the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-minimize" - }, - { - "description": "Denies the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-monitor-from-point" - }, - { - "description": "Denies the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-position" - }, - { - "description": "Denies the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-size" - }, - { - "description": "Denies the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-primary-monitor" - }, - { - "description": "Denies the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-request-user-attention" - }, - { - "description": "Denies the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-scale-factor" - }, - { - "description": "Denies the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-bottom" - }, - { - "description": "Denies the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-top" - }, - { - "description": "Denies the set_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-background-color" - }, - { - "description": "Denies the set_badge_count command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-badge-count" - }, - { - "description": "Denies the set_badge_label command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-badge-label" - }, - { - "description": "Denies the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-closable" - }, - { - "description": "Denies the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-content-protected" - }, - { - "description": "Denies the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-grab" - }, - { - "description": "Denies the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-icon" - }, - { - "description": "Denies the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-position" - }, - { - "description": "Denies the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-visible" - }, - { - "description": "Denies the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-decorations" - }, - { - "description": "Denies the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-effects" - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-enabled" - }, - { - "description": "Denies the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-focus" - }, - { - "description": "Denies the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-fullscreen" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-icon" - }, - { - "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-ignore-cursor-events" - }, - { - "description": "Denies the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-max-size" - }, - { - "description": "Denies the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-maximizable" - }, - { - "description": "Denies the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-min-size" - }, - { - "description": "Denies the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-minimizable" - }, - { - "description": "Denies the set_overlay_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-overlay-icon" - }, - { - "description": "Denies the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-position" - }, - { - "description": "Denies the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-progress-bar" - }, - { - "description": "Denies the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-resizable" - }, - { - "description": "Denies the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-shadow" - }, - { - "description": "Denies the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size" - }, - { - "description": "Denies the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size-constraints" - }, - { - "description": "Denies the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-skip-taskbar" - }, - { - "description": "Denies the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-theme" - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title" - }, - { - "description": "Denies the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title-bar-style" - }, - { - "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-visible-on-all-workspaces" - }, - { - "description": "Denies the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-show" - }, - { - "description": "Denies the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-dragging" - }, - { - "description": "Denies the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-resize-dragging" - }, - { - "description": "Denies the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-theme" - }, - { - "description": "Denies the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-title" - }, - { - "description": "Denies the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-toggle-maximize" - }, - { - "description": "Denies the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unmaximize" - }, - { - "description": "Denies the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unminimize" - }, - { - "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n", - "type": "string", - "const": "dialog:default" - }, - { - "description": "Enables the ask command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-ask" - }, - { - "description": "Enables the confirm command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-confirm" - }, - { - "description": "Enables the message command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-message" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-open" - }, - { - "description": "Enables the save command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-save" - }, - { - "description": "Denies the ask command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-ask" - }, - { - "description": "Denies the confirm command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-confirm" - }, - { - "description": "Denies the message command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-message" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-open" - }, - { - "description": "Denies the save command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-save" - }, - { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n", - "type": "string", - "const": "fs:default" - }, - { - "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta" - }, - { - "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the application folders.", - "type": "string", - "const": "fs:allow-app-read" - }, - { - "description": "This allows full recursive read access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-read-recursive" - }, - { - "description": "This allows non-recursive write access to the application folders.", - "type": "string", - "const": "fs:allow-app-write" - }, - { - "description": "This allows full recursive write access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-read" - }, - { - "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-write" - }, - { - "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-read" - }, - { - "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-write" - }, - { - "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-read" - }, - { - "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-write" - }, - { - "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-read" - }, - { - "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-write" - }, - { - "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-read" - }, - { - "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-write" - }, - { - "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-read" - }, - { - "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-write" - }, - { - "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-read" - }, - { - "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-write" - }, - { - "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-read" - }, - { - "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-write" - }, - { - "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-read" - }, - { - "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-write" - }, - { - "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-read" - }, - { - "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-write" - }, - { - "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-read" - }, - { - "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-write" - }, - { - "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-read" - }, - { - "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-write" - }, - { - "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-read" - }, - { - "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-write" - }, - { - "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-write-recursive" - }, - { - "description": "This denies access to dangerous Tauri relevant files and folders by default.", - "type": "string", - "const": "fs:deny-default" - }, - { - "description": "Enables the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-copy-file" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-create" - }, - { - "description": "Enables the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-exists" - }, - { - "description": "Enables the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-fstat" - }, - { - "description": "Enables the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-ftruncate" - }, - { - "description": "Enables the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-lstat" - }, - { - "description": "Enables the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-mkdir" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-open" - }, - { - "description": "Enables the read command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read" - }, - { - "description": "Enables the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-dir" - }, - { - "description": "Enables the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-file" - }, - { - "description": "Enables the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file" - }, - { - "description": "Enables the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines" - }, - { - "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines-next" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-remove" - }, - { - "description": "Enables the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-rename" - }, - { - "description": "Enables the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-seek" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-size" - }, - { - "description": "Enables the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-stat" - }, - { - "description": "Enables the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-truncate" - }, - { - "description": "Enables the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-unwatch" - }, - { - "description": "Enables the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-watch" - }, - { - "description": "Enables the write command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write" - }, - { - "description": "Enables the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-file" - }, - { - "description": "Enables the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-text-file" - }, - { - "description": "This permissions allows to create the application specific directories.\n", - "type": "string", - "const": "fs:create-app-specific-dirs" - }, - { - "description": "Denies the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-copy-file" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-create" - }, - { - "description": "Denies the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-exists" - }, - { - "description": "Denies the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-fstat" - }, - { - "description": "Denies the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-ftruncate" - }, - { - "description": "Denies the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-lstat" - }, - { - "description": "Denies the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-mkdir" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-open" - }, - { - "description": "Denies the read command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read" - }, - { - "description": "Denies the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-dir" - }, - { - "description": "Denies the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-file" - }, - { - "description": "Denies the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file" - }, - { - "description": "Denies the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines" - }, - { - "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines-next" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-remove" - }, - { - "description": "Denies the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-rename" - }, - { - "description": "Denies the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-seek" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-size" - }, - { - "description": "Denies the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-stat" - }, - { - "description": "Denies the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-truncate" - }, - { - "description": "Denies the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-unwatch" - }, - { - "description": "Denies the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-watch" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-linux" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-windows" - }, - { - "description": "Denies the write command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write" - }, - { - "description": "Denies the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-file" - }, - { - "description": "Denies the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-text-file" - }, - { - "description": "This enables all read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-all" - }, - { - "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", - "type": "string", - "const": "fs:read-app-specific-dirs-recursive" - }, - { - "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-dirs" - }, - { - "description": "This enables file read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-files" - }, - { - "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-meta" - }, - { - "description": "An empty permission you can use to modify the global scope.", - "type": "string", - "const": "fs:scope" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the application folders.", - "type": "string", - "const": "fs:scope-app" - }, - { - "description": "This scope permits to list all files and folders in the application directories.", - "type": "string", - "const": "fs:scope-app-index" - }, - { - "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", - "type": "string", - "const": "fs:scope-app-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", - "type": "string", - "const": "fs:scope-appcache" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", - "type": "string", - "const": "fs:scope-appcache-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appcache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:scope-appconfig" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", - "type": "string", - "const": "fs:scope-appconfig-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appconfig-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", - "type": "string", - "const": "fs:scope-appdata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", - "type": "string", - "const": "fs:scope-appdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:scope-applocaldata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", - "type": "string", - "const": "fs:scope-applocaldata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applocaldata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", - "type": "string", - "const": "fs:scope-applog" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", - "type": "string", - "const": "fs:scope-applog-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applog-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", - "type": "string", - "const": "fs:scope-audio" - }, - { - "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", - "type": "string", - "const": "fs:scope-audio-index" - }, - { - "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-audio-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", - "type": "string", - "const": "fs:scope-cache" - }, - { - "description": "This scope permits to list all files and folders in the `$CACHE`folder.", - "type": "string", - "const": "fs:scope-cache-index" - }, - { - "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-cache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", - "type": "string", - "const": "fs:scope-config" - }, - { - "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", - "type": "string", - "const": "fs:scope-config-index" - }, - { - "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-config-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", - "type": "string", - "const": "fs:scope-data" - }, - { - "description": "This scope permits to list all files and folders in the `$DATA`folder.", - "type": "string", - "const": "fs:scope-data-index" - }, - { - "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-data-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", - "type": "string", - "const": "fs:scope-desktop" - }, - { - "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", - "type": "string", - "const": "fs:scope-desktop-index" - }, - { - "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-desktop-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:scope-document" - }, - { - "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", - "type": "string", - "const": "fs:scope-document-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-document-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:scope-download" - }, - { - "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", - "type": "string", - "const": "fs:scope-download-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-download-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", - "type": "string", - "const": "fs:scope-exe" - }, - { - "description": "This scope permits to list all files and folders in the `$EXE`folder.", - "type": "string", - "const": "fs:scope-exe-index" - }, - { - "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-exe-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", - "type": "string", - "const": "fs:scope-font" - }, - { - "description": "This scope permits to list all files and folders in the `$FONT`folder.", - "type": "string", - "const": "fs:scope-font-index" - }, - { - "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-font-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", - "type": "string", - "const": "fs:scope-home" - }, - { - "description": "This scope permits to list all files and folders in the `$HOME`folder.", - "type": "string", - "const": "fs:scope-home-index" - }, - { - "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-home-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:scope-localdata" - }, - { - "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", - "type": "string", - "const": "fs:scope-localdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-localdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", - "type": "string", - "const": "fs:scope-log" - }, - { - "description": "This scope permits to list all files and folders in the `$LOG`folder.", - "type": "string", - "const": "fs:scope-log-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-log-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", - "type": "string", - "const": "fs:scope-picture" - }, - { - "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", - "type": "string", - "const": "fs:scope-picture-index" - }, - { - "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-picture-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", - "type": "string", - "const": "fs:scope-public" - }, - { - "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", - "type": "string", - "const": "fs:scope-public-index" - }, - { - "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-public-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", - "type": "string", - "const": "fs:scope-resource" - }, - { - "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", - "type": "string", - "const": "fs:scope-resource-index" - }, - { - "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-resource-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", - "type": "string", - "const": "fs:scope-runtime" - }, - { - "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", - "type": "string", - "const": "fs:scope-runtime-index" - }, - { - "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-runtime-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", - "type": "string", - "const": "fs:scope-temp" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMP`folder.", - "type": "string", - "const": "fs:scope-temp-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-temp-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:scope-template" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", - "type": "string", - "const": "fs:scope-template-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-template-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", - "type": "string", - "const": "fs:scope-video" - }, - { - "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", - "type": "string", - "const": "fs:scope-video-index" - }, - { - "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-video-recursive" - }, - { - "description": "This enables all write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-all" - }, - { - "description": "This enables all file write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-files" - }, - { - "description": "This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n", - "type": "string", - "const": "os:default" - }, - { - "description": "Enables the arch command without any pre-configured scope.", - "type": "string", - "const": "os:allow-arch" - }, - { - "description": "Enables the exe_extension command without any pre-configured scope.", - "type": "string", - "const": "os:allow-exe-extension" - }, - { - "description": "Enables the family command without any pre-configured scope.", - "type": "string", - "const": "os:allow-family" - }, - { - "description": "Enables the hostname command without any pre-configured scope.", - "type": "string", - "const": "os:allow-hostname" - }, - { - "description": "Enables the locale command without any pre-configured scope.", - "type": "string", - "const": "os:allow-locale" - }, - { - "description": "Enables the os_type command without any pre-configured scope.", - "type": "string", - "const": "os:allow-os-type" - }, - { - "description": "Enables the platform command without any pre-configured scope.", - "type": "string", - "const": "os:allow-platform" - }, - { - "description": "Enables the version command without any pre-configured scope.", - "type": "string", - "const": "os:allow-version" - }, - { - "description": "Denies the arch command without any pre-configured scope.", - "type": "string", - "const": "os:deny-arch" - }, - { - "description": "Denies the exe_extension command without any pre-configured scope.", - "type": "string", - "const": "os:deny-exe-extension" - }, - { - "description": "Denies the family command without any pre-configured scope.", - "type": "string", - "const": "os:deny-family" - }, - { - "description": "Denies the hostname command without any pre-configured scope.", - "type": "string", - "const": "os:deny-hostname" - }, - { - "description": "Denies the locale command without any pre-configured scope.", - "type": "string", - "const": "os:deny-locale" - }, - { - "description": "Denies the os_type command without any pre-configured scope.", - "type": "string", - "const": "os:deny-os-type" - }, - { - "description": "Denies the platform command without any pre-configured scope.", - "type": "string", - "const": "os:deny-platform" - }, - { - "description": "Denies the version command without any pre-configured scope.", - "type": "string", - "const": "os:deny-version" - }, - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "const": "shell:default" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute" - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open" - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn" - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute" - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open" - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn" - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write" - }, - { - "description": "### Default Permissions\n\nThis permission set configures what kind of\ndatabase operations are available from the sql plugin.\n\n### Granted Permissions\n\nAll reading related operations are enabled.\nAlso allows to load or close a connection.\n\n", - "type": "string", - "const": "sql:default" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "sql:allow-close" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "sql:allow-execute" - }, - { - "description": "Enables the load command without any pre-configured scope.", - "type": "string", - "const": "sql:allow-load" - }, - { - "description": "Enables the select command without any pre-configured scope.", - "type": "string", - "const": "sql:allow-select" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "sql:deny-close" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "sql:deny-execute" - }, - { - "description": "Denies the load command without any pre-configured scope.", - "type": "string", - "const": "sql:deny-load" - }, - { - "description": "Denies the select command without any pre-configured scope.", - "type": "string", - "const": "sql:deny-select" - } - ] - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - }, - "ShellScopeEntryAllowedArg": { - "description": "A command argument allowed to be executed by the webview API.", - "anyOf": [ - { - "description": "A non-configurable argument that is passed to the command in the order it was specified.", - "type": "string" - }, - { - "description": "A variable that is set while calling the command from the webview API.", - "type": "object", - "required": [ - "validator" - ], - "properties": { - "raw": { - "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", - "default": false, - "type": "boolean" - }, - "validator": { - "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", - "type": "string" - } - }, - "additionalProperties": false - } - ] - }, - "ShellScopeEntryAllowedArgs": { - "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", - "anyOf": [ - { - "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", - "type": "boolean" - }, - { - "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", - "type": "array", - "items": { - "$ref": "#/definitions/ShellScopeEntryAllowedArg" - } - } - ] - } - } -} \ No newline at end of file diff --git a/src-tauri/gen/schemas/windows-schema.json b/src-tauri/gen/schemas/windows-schema.json deleted file mode 100644 index 60529716..00000000 --- a/src-tauri/gen/schemas/windows-schema.json +++ /dev/null @@ -1,2089 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CapabilityFile", - "description": "Capability formats accepted in a capability file.", - "anyOf": [ - { - "description": "A single capability.", - "allOf": [ - { - "$ref": "#/definitions/Capability" - } - ] - }, - { - "description": "A list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - }, - { - "description": "A list of capabilities.", - "type": "object", - "required": [ - "capabilities" - ], - "properties": { - "capabilities": { - "description": "The list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - } - } - } - ], - "definitions": { - "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```", - "type": "object", - "required": [ - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", - "type": "string" - }, - "description": { - "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.", - "default": "", - "type": "string" - }, - "remote": { - "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", - "anyOf": [ - { - "$ref": "#/definitions/CapabilityRemote" - }, - { - "type": "null" - } - ] - }, - "local": { - "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", - "default": true, - "type": "boolean" - }, - "windows": { - "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "webviews": { - "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThis is only required when using on multiwebview contexts, by default all child webviews of a window that matches [`Self::windows`] are linked.\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionEntry" - }, - "uniqueItems": true - }, - "platforms": { - "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "CapabilityRemote": { - "description": "Configuration for remote URLs that are associated with the capability.", - "type": "object", - "required": [ - "urls" - ], - "properties": { - "urls": { - "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionEntry": { - "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", - "anyOf": [ - { - "description": "Reference a permission or permission set by identifier.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - { - "description": "Reference a permission or permission set by identifier and extends its scope.", - "type": "object", - "allOf": [ - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "const": "shell:default" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute" - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open" - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn" - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute" - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open" - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn" - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write" - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "Entry", - "description": "A command allowed to be executed by the webview API.", - "type": "object", - "required": [ - "args", - "cmd", - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - } - } - }, - "deny": { - "items": { - "title": "Entry", - "description": "A command allowed to be executed by the webview API.", - "type": "object", - "required": [ - "args", - "cmd", - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - } - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - } - ], - "required": [ - "identifier" - ] - } - ] - }, - "Identifier": { - "description": "Permission identifier", - "oneOf": [ - { - "description": "Allows reading the CLI matches", - "type": "string", - "const": "cli:default" - }, - { - "description": "Enables the cli_matches command without any pre-configured scope.", - "type": "string", - "const": "cli:allow-cli-matches" - }, - { - "description": "Denies the cli_matches command without any pre-configured scope.", - "type": "string", - "const": "cli:deny-cli-matches" - }, - { - "description": "Default core plugins set which includes:\n- 'core:path:default'\n- 'core:event:default'\n- 'core:window:default'\n- 'core:webview:default'\n- 'core:app:default'\n- 'core:image:default'\n- 'core:resources:default'\n- 'core:menu:default'\n- 'core:tray:default'\n", - "type": "string", - "const": "core:default" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:app:default" - }, - { - "description": "Enables the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-hide" - }, - { - "description": "Enables the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-show" - }, - { - "description": "Enables the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-default-window-icon" - }, - { - "description": "Enables the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-name" - }, - { - "description": "Enables the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-tauri-version" - }, - { - "description": "Enables the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-version" - }, - { - "description": "Denies the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-hide" - }, - { - "description": "Denies the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-show" - }, - { - "description": "Denies the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-default-window-icon" - }, - { - "description": "Denies the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-name" - }, - { - "description": "Denies the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-tauri-version" - }, - { - "description": "Denies the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-version" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:event:default" - }, - { - "description": "Enables the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit" - }, - { - "description": "Enables the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit-to" - }, - { - "description": "Enables the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-listen" - }, - { - "description": "Enables the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-unlisten" - }, - { - "description": "Denies the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit" - }, - { - "description": "Denies the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit-to" - }, - { - "description": "Denies the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-listen" - }, - { - "description": "Denies the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-unlisten" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:image:default" - }, - { - "description": "Enables the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-bytes" - }, - { - "description": "Enables the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-path" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-new" - }, - { - "description": "Enables the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-rgba" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-size" - }, - { - "description": "Denies the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-bytes" - }, - { - "description": "Denies the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-path" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-new" - }, - { - "description": "Denies the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-rgba" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-size" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:menu:default" - }, - { - "description": "Enables the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-append" - }, - { - "description": "Enables the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-create-default" - }, - { - "description": "Enables the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-get" - }, - { - "description": "Enables the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-insert" - }, - { - "description": "Enables the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-checked" - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-enabled" - }, - { - "description": "Enables the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-items" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-new" - }, - { - "description": "Enables the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-popup" - }, - { - "description": "Enables the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-prepend" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove" - }, - { - "description": "Enables the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove-at" - }, - { - "description": "Enables the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-accelerator" - }, - { - "description": "Enables the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-app-menu" - }, - { - "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-help-menu-for-nsapp" - }, - { - "description": "Enables the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-window-menu" - }, - { - "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-windows-menu-for-nsapp" - }, - { - "description": "Enables the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-checked" - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-enabled" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-icon" - }, - { - "description": "Enables the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-text" - }, - { - "description": "Enables the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-text" - }, - { - "description": "Denies the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-append" - }, - { - "description": "Denies the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-create-default" - }, - { - "description": "Denies the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-get" - }, - { - "description": "Denies the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-insert" - }, - { - "description": "Denies the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-checked" - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-enabled" - }, - { - "description": "Denies the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-items" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-new" - }, - { - "description": "Denies the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-popup" - }, - { - "description": "Denies the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-prepend" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove" - }, - { - "description": "Denies the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove-at" - }, - { - "description": "Denies the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-accelerator" - }, - { - "description": "Denies the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-app-menu" - }, - { - "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-help-menu-for-nsapp" - }, - { - "description": "Denies the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-window-menu" - }, - { - "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-windows-menu-for-nsapp" - }, - { - "description": "Denies the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-checked" - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-enabled" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-icon" - }, - { - "description": "Denies the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-text" - }, - { - "description": "Denies the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-text" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:path:default" - }, - { - "description": "Enables the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-basename" - }, - { - "description": "Enables the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-dirname" - }, - { - "description": "Enables the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-extname" - }, - { - "description": "Enables the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-is-absolute" - }, - { - "description": "Enables the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-join" - }, - { - "description": "Enables the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-normalize" - }, - { - "description": "Enables the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve" - }, - { - "description": "Enables the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve-directory" - }, - { - "description": "Denies the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-basename" - }, - { - "description": "Denies the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-dirname" - }, - { - "description": "Denies the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-extname" - }, - { - "description": "Denies the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-is-absolute" - }, - { - "description": "Denies the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-join" - }, - { - "description": "Denies the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-normalize" - }, - { - "description": "Denies the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve" - }, - { - "description": "Denies the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve-directory" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:resources:default" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:allow-close" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:deny-close" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:tray:default" - }, - { - "description": "Enables the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-get-by-id" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-new" - }, - { - "description": "Enables the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-remove-by-id" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon" - }, - { - "description": "Enables the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon-as-template" - }, - { - "description": "Enables the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-menu" - }, - { - "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-show-menu-on-left-click" - }, - { - "description": "Enables the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-temp-dir-path" - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-title" - }, - { - "description": "Enables the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-tooltip" - }, - { - "description": "Enables the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-visible" - }, - { - "description": "Denies the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-get-by-id" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-new" - }, - { - "description": "Denies the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-remove-by-id" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon" - }, - { - "description": "Denies the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon-as-template" - }, - { - "description": "Denies the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-menu" - }, - { - "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-show-menu-on-left-click" - }, - { - "description": "Denies the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-temp-dir-path" - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-title" - }, - { - "description": "Denies the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-tooltip" - }, - { - "description": "Denies the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-visible" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:webview:default" - }, - { - "description": "Enables the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview" - }, - { - "description": "Enables the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview-window" - }, - { - "description": "Enables the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-get-all-webviews" - }, - { - "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-internal-toggle-devtools" - }, - { - "description": "Enables the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-print" - }, - { - "description": "Enables the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-reparent" - }, - { - "description": "Enables the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-focus" - }, - { - "description": "Enables the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-position" - }, - { - "description": "Enables the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-size" - }, - { - "description": "Enables the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-zoom" - }, - { - "description": "Enables the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-close" - }, - { - "description": "Enables the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-position" - }, - { - "description": "Enables the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-size" - }, - { - "description": "Denies the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview" - }, - { - "description": "Denies the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview-window" - }, - { - "description": "Denies the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-get-all-webviews" - }, - { - "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-internal-toggle-devtools" - }, - { - "description": "Denies the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-print" - }, - { - "description": "Denies the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-reparent" - }, - { - "description": "Denies the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-focus" - }, - { - "description": "Denies the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-position" - }, - { - "description": "Denies the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-size" - }, - { - "description": "Denies the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-zoom" - }, - { - "description": "Denies the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-close" - }, - { - "description": "Denies the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-position" - }, - { - "description": "Denies the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-size" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:window:default" - }, - { - "description": "Enables the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-available-monitors" - }, - { - "description": "Enables the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-center" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-close" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-create" - }, - { - "description": "Enables the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-current-monitor" - }, - { - "description": "Enables the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-cursor-position" - }, - { - "description": "Enables the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-destroy" - }, - { - "description": "Enables the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-get-all-windows" - }, - { - "description": "Enables the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-hide" - }, - { - "description": "Enables the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-position" - }, - { - "description": "Enables the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-size" - }, - { - "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-internal-toggle-maximize" - }, - { - "description": "Enables the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-closable" - }, - { - "description": "Enables the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-decorated" - }, - { - "description": "Enables the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-focused" - }, - { - "description": "Enables the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-fullscreen" - }, - { - "description": "Enables the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximizable" - }, - { - "description": "Enables the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximized" - }, - { - "description": "Enables the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimizable" - }, - { - "description": "Enables the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimized" - }, - { - "description": "Enables the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-resizable" - }, - { - "description": "Enables the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-visible" - }, - { - "description": "Enables the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-maximize" - }, - { - "description": "Enables the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-minimize" - }, - { - "description": "Enables the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-monitor-from-point" - }, - { - "description": "Enables the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-position" - }, - { - "description": "Enables the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-size" - }, - { - "description": "Enables the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-primary-monitor" - }, - { - "description": "Enables the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-request-user-attention" - }, - { - "description": "Enables the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-scale-factor" - }, - { - "description": "Enables the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-bottom" - }, - { - "description": "Enables the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-top" - }, - { - "description": "Enables the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-closable" - }, - { - "description": "Enables the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-content-protected" - }, - { - "description": "Enables the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-grab" - }, - { - "description": "Enables the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-icon" - }, - { - "description": "Enables the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-position" - }, - { - "description": "Enables the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-visible" - }, - { - "description": "Enables the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-decorations" - }, - { - "description": "Enables the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-effects" - }, - { - "description": "Enables the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-focus" - }, - { - "description": "Enables the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-fullscreen" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-icon" - }, - { - "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-ignore-cursor-events" - }, - { - "description": "Enables the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-max-size" - }, - { - "description": "Enables the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-maximizable" - }, - { - "description": "Enables the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-min-size" - }, - { - "description": "Enables the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-minimizable" - }, - { - "description": "Enables the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-position" - }, - { - "description": "Enables the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-progress-bar" - }, - { - "description": "Enables the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-resizable" - }, - { - "description": "Enables the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-shadow" - }, - { - "description": "Enables the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size" - }, - { - "description": "Enables the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size-constraints" - }, - { - "description": "Enables the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-skip-taskbar" - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title" - }, - { - "description": "Enables the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title-bar-style" - }, - { - "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-visible-on-all-workspaces" - }, - { - "description": "Enables the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-show" - }, - { - "description": "Enables the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-dragging" - }, - { - "description": "Enables the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-resize-dragging" - }, - { - "description": "Enables the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-theme" - }, - { - "description": "Enables the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-title" - }, - { - "description": "Enables the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-toggle-maximize" - }, - { - "description": "Enables the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unmaximize" - }, - { - "description": "Enables the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unminimize" - }, - { - "description": "Denies the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-available-monitors" - }, - { - "description": "Denies the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-center" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-close" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-create" - }, - { - "description": "Denies the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-current-monitor" - }, - { - "description": "Denies the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-cursor-position" - }, - { - "description": "Denies the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-destroy" - }, - { - "description": "Denies the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-get-all-windows" - }, - { - "description": "Denies the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-hide" - }, - { - "description": "Denies the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-position" - }, - { - "description": "Denies the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-size" - }, - { - "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-internal-toggle-maximize" - }, - { - "description": "Denies the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-closable" - }, - { - "description": "Denies the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-decorated" - }, - { - "description": "Denies the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-focused" - }, - { - "description": "Denies the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-fullscreen" - }, - { - "description": "Denies the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximizable" - }, - { - "description": "Denies the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximized" - }, - { - "description": "Denies the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimizable" - }, - { - "description": "Denies the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimized" - }, - { - "description": "Denies the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-resizable" - }, - { - "description": "Denies the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-visible" - }, - { - "description": "Denies the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-maximize" - }, - { - "description": "Denies the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-minimize" - }, - { - "description": "Denies the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-monitor-from-point" - }, - { - "description": "Denies the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-position" - }, - { - "description": "Denies the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-size" - }, - { - "description": "Denies the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-primary-monitor" - }, - { - "description": "Denies the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-request-user-attention" - }, - { - "description": "Denies the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-scale-factor" - }, - { - "description": "Denies the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-bottom" - }, - { - "description": "Denies the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-top" - }, - { - "description": "Denies the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-closable" - }, - { - "description": "Denies the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-content-protected" - }, - { - "description": "Denies the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-grab" - }, - { - "description": "Denies the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-icon" - }, - { - "description": "Denies the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-position" - }, - { - "description": "Denies the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-visible" - }, - { - "description": "Denies the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-decorations" - }, - { - "description": "Denies the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-effects" - }, - { - "description": "Denies the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-focus" - }, - { - "description": "Denies the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-fullscreen" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-icon" - }, - { - "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-ignore-cursor-events" - }, - { - "description": "Denies the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-max-size" - }, - { - "description": "Denies the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-maximizable" - }, - { - "description": "Denies the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-min-size" - }, - { - "description": "Denies the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-minimizable" - }, - { - "description": "Denies the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-position" - }, - { - "description": "Denies the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-progress-bar" - }, - { - "description": "Denies the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-resizable" - }, - { - "description": "Denies the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-shadow" - }, - { - "description": "Denies the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size" - }, - { - "description": "Denies the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size-constraints" - }, - { - "description": "Denies the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-skip-taskbar" - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title" - }, - { - "description": "Denies the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title-bar-style" - }, - { - "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-visible-on-all-workspaces" - }, - { - "description": "Denies the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-show" - }, - { - "description": "Denies the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-dragging" - }, - { - "description": "Denies the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-resize-dragging" - }, - { - "description": "Denies the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-theme" - }, - { - "description": "Denies the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-title" - }, - { - "description": "Denies the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-toggle-maximize" - }, - { - "description": "Denies the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unmaximize" - }, - { - "description": "Denies the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unminimize" - }, - { - "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n", - "type": "string", - "const": "dialog:default" - }, - { - "description": "Enables the ask command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-ask" - }, - { - "description": "Enables the confirm command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-confirm" - }, - { - "description": "Enables the message command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-message" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-open" - }, - { - "description": "Enables the save command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-save" - }, - { - "description": "Denies the ask command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-ask" - }, - { - "description": "Denies the confirm command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-confirm" - }, - { - "description": "Denies the message command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-message" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-open" - }, - { - "description": "Denies the save command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-save" - }, - { - "description": "This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n", - "type": "string", - "const": "os:default" - }, - { - "description": "Enables the arch command without any pre-configured scope.", - "type": "string", - "const": "os:allow-arch" - }, - { - "description": "Enables the exe_extension command without any pre-configured scope.", - "type": "string", - "const": "os:allow-exe-extension" - }, - { - "description": "Enables the family command without any pre-configured scope.", - "type": "string", - "const": "os:allow-family" - }, - { - "description": "Enables the hostname command without any pre-configured scope.", - "type": "string", - "const": "os:allow-hostname" - }, - { - "description": "Enables the locale command without any pre-configured scope.", - "type": "string", - "const": "os:allow-locale" - }, - { - "description": "Enables the os_type command without any pre-configured scope.", - "type": "string", - "const": "os:allow-os-type" - }, - { - "description": "Enables the platform command without any pre-configured scope.", - "type": "string", - "const": "os:allow-platform" - }, - { - "description": "Enables the version command without any pre-configured scope.", - "type": "string", - "const": "os:allow-version" - }, - { - "description": "Denies the arch command without any pre-configured scope.", - "type": "string", - "const": "os:deny-arch" - }, - { - "description": "Denies the exe_extension command without any pre-configured scope.", - "type": "string", - "const": "os:deny-exe-extension" - }, - { - "description": "Denies the family command without any pre-configured scope.", - "type": "string", - "const": "os:deny-family" - }, - { - "description": "Denies the hostname command without any pre-configured scope.", - "type": "string", - "const": "os:deny-hostname" - }, - { - "description": "Denies the locale command without any pre-configured scope.", - "type": "string", - "const": "os:deny-locale" - }, - { - "description": "Denies the os_type command without any pre-configured scope.", - "type": "string", - "const": "os:deny-os-type" - }, - { - "description": "Denies the platform command without any pre-configured scope.", - "type": "string", - "const": "os:deny-platform" - }, - { - "description": "Denies the version command without any pre-configured scope.", - "type": "string", - "const": "os:deny-version" - }, - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "const": "shell:default" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute" - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open" - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn" - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute" - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open" - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn" - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write" - } - ] - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - }, - "ShellAllowedArg": { - "description": "A command argument allowed to be executed by the webview API.", - "anyOf": [ - { - "description": "A non-configurable argument that is passed to the command in the order it was specified.", - "type": "string" - }, - { - "description": "A variable that is set while calling the command from the webview API.", - "type": "object", - "required": [ - "validator" - ], - "properties": { - "raw": { - "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", - "default": false, - "type": "boolean" - }, - "validator": { - "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", - "type": "string" - } - }, - "additionalProperties": false - } - ] - }, - "ShellAllowedArgs": { - "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", - "anyOf": [ - { - "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", - "type": "boolean" - }, - { - "description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.", - "type": "array", - "items": { - "$ref": "#/definitions/ShellAllowedArg" - } - } - ] - } - } -} \ No newline at end of file diff --git a/src-tauri/migrations/20250111160252_create_job_table.up.sql b/src-tauri/migrations/20250111160252_create_job_table.up.sql index 57ac73e8..46880c6c 100644 --- a/src-tauri/migrations/20250111160252_create_job_table.up.sql +++ b/src-tauri/migrations/20250111160252_create_job_table.up.sql @@ -1,8 +1,8 @@ --- Add up migration script here CREATE TABLE IF NOT EXISTS jobs( - id TEXT NOT NULL PRIMARY KEY, - mode BLOB NOT NULL, + id TEXT NOT NULL, + mode TEXT NOT NULL, project_file TEXT NOT NULL, blender_version TEXT NOT NULL, - output_path TEXT NOT NULL + output_path TEXT NOT NULL, + PRIMARY KEY (id) ); \ No newline at end of file diff --git a/src-tauri/migrations/20250111160259_create_task_table.up.sql b/src-tauri/migrations/20250111160259_create_task_table.up.sql deleted file mode 100644 index 08b6c3a6..00000000 --- a/src-tauri/migrations/20250111160259_create_task_table.up.sql +++ /dev/null @@ -1,10 +0,0 @@ --- Add up migration script here -CREATE TABLE IF NOT EXISTS tasks( - id TEXT NOT NULL PRIMARY KEY, - peer_id TEXT NOT NULL, - job_id TEXT NOT NULL, - blender_version TEXT NOT NULL, - blend_file_name TEXT NOT NULL, - start_frame INTEGER NOT NULL, - end_frame INTEGER NOT NULL -); \ No newline at end of file diff --git a/src-tauri/migrations/20250111160259_create_task_table.down.sql b/src-tauri/migrations/20250111160259_create_ticket_table.down.sql similarity index 54% rename from src-tauri/migrations/20250111160259_create_task_table.down.sql rename to src-tauri/migrations/20250111160259_create_ticket_table.down.sql index 1bbc1728..63346eaa 100644 --- a/src-tauri/migrations/20250111160259_create_task_table.down.sql +++ b/src-tauri/migrations/20250111160259_create_ticket_table.down.sql @@ -1,2 +1,2 @@ -- Add down migration script here -DROP TABLE IF EXISTS tasks; \ No newline at end of file +DROP TABLE IF EXISTS ticket; \ No newline at end of file diff --git a/src-tauri/migrations/20250111160259_create_ticket_table.up.sql b/src-tauri/migrations/20250111160259_create_ticket_table.up.sql new file mode 100644 index 00000000..6d883227 --- /dev/null +++ b/src-tauri/migrations/20250111160259_create_ticket_table.up.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS ticket( + id TEXT NOT NULL, + job_id TEXT NOT NULL, + job TEXT NOT NULL, + start INTEGER NOT NULL, + end INTEGER NOT NULL, + PRIMARY KEY (id) +); \ No newline at end of file diff --git a/src-tauri/migrations/20250111160306_create_worker_table.up.sql b/src-tauri/migrations/20250111160306_create_worker_table.up.sql index 26618694..91924323 100644 --- a/src-tauri/migrations/20250111160306_create_worker_table.up.sql +++ b/src-tauri/migrations/20250111160306_create_worker_table.up.sql @@ -1,5 +1,6 @@ --- Add up migration script here CREATE TABLE IF NOT EXISTS workers ( - machine_id TEXT NOT NULL PRIMARY KEY, - spec BLOB NOT NULL + peer_id TEXT NOT NULL, + -- TODO: See how I can use sqlx::json for storage? + spec TEXT NOT NULL, + PRIMARY KEY (peer_id) ); \ No newline at end of file diff --git a/src-tauri/migrations/20250111160855_create_renders_table.up.sql b/src-tauri/migrations/20250111160855_create_renders_table.up.sql index a3e9e672..c8ef8ade 100644 --- a/src-tauri/migrations/20250111160855_create_renders_table.up.sql +++ b/src-tauri/migrations/20250111160855_create_renders_table.up.sql @@ -1,8 +1,8 @@ --- Add up migration script here CREATE TABLE IF NOT EXISTS renders( - -- should be jobs_id + _ + frame number - id TEXT NOT NULL PRIMARY KEY, - jobs_id TEXT NOT NULL, + id TEXT NOT NULL, + job_id TEXT NOT NULL, frame INTEGER NOT NULL, - render_path TEXT NOT NULL + render_path TEXT NOT NULL, + PRIMARY KEY (id) + UNIQUE (job_id, frame) ); \ No newline at end of file diff --git a/src-tauri/migrations/20250612033123_create_advertise_table.down.sql b/src-tauri/migrations/20250612033123_create_advertise_table.down.sql new file mode 100644 index 00000000..2ef8362c --- /dev/null +++ b/src-tauri/migrations/20250612033123_create_advertise_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS advertise; \ No newline at end of file diff --git a/src-tauri/migrations/20250612033123_create_advertise_table.up.sql b/src-tauri/migrations/20250612033123_create_advertise_table.up.sql new file mode 100644 index 00000000..ddcd70e8 --- /dev/null +++ b/src-tauri/migrations/20250612033123_create_advertise_table.up.sql @@ -0,0 +1,7 @@ +-- used to create records of previous advertisement in case of a unexpected shutdown. avoid memory usage as much as you can. +CREATE TABLE IF NOT EXISTS advertise( + id TEXT NOT NULL UNIQUE, -- primary key + -- See if we need anything special, but for now, just name and path both of which is protected keywords. + ad_name TEXT NOT NULL, -- name we broadcast + file_path TEXT NOT NULL -- path to file to respond +) \ No newline at end of file diff --git a/src-tauri/src/constant.rs b/src-tauri/src/constant.rs new file mode 100644 index 00000000..52e34bae --- /dev/null +++ b/src-tauri/src/constant.rs @@ -0,0 +1,7 @@ +pub const DATABASE_FILE_NAME: &str = "blendfarm.db"; +pub const WORKPLACE: &str = "workplace"; +pub const JOB_TOPIC: &str = "/job"; +pub const NODE_TOPIC: &str = "/node"; + +// why does the transfer have number at the trail end? look more into this? +pub const TRANSFER: &str = "/file-transfer/1"; diff --git a/src-tauri/src/domains/advertise_store.rs b/src-tauri/src/domains/advertise_store.rs new file mode 100644 index 00000000..1560f695 --- /dev/null +++ b/src-tauri/src/domains/advertise_store.rs @@ -0,0 +1,20 @@ +use crate::models::advertise::Advertise; +use thiserror::Error; +use uuid::Uuid; + +#[derive(Debug, Error)] +pub enum AdvertiseError { + #[error("Unknown")] + Unknown, + #[error("Received Database errors! {0}")] + DatabaseError(String), +} + +#[async_trait::async_trait] +pub trait AdvertiseStore { + async fn find(&self, id: Uuid) -> Result, AdvertiseError>; + async fn update(&self, advertise: Advertise) -> Result<(), AdvertiseError>; + async fn create(&self, advertise: Advertise) -> Result<(), AdvertiseError>; + async fn kill(&self, id: Uuid) -> Result<(), AdvertiseError>; + async fn all(&self) -> Result>, AdvertiseError>; +} diff --git a/src-tauri/src/domains/job_store.rs b/src-tauri/src/domains/job_store.rs index 49e15055..9467c9e8 100644 --- a/src-tauri/src/domains/job_store.rs +++ b/src-tauri/src/domains/job_store.rs @@ -1,9 +1,12 @@ -use crate::{domains::task_store::TaskError, models::job::Job}; -use serde::{Deserialize, Serialize}; +use crate::{ + domains::ticket_store::TicketError, + models::job::{CreatedJobDto, NewJobDto}, +}; +// use serde::{Deserialize, Serialize}; use thiserror::Error; use uuid::Uuid; -#[derive(Debug, Serialize, Deserialize, Error)] +#[derive(Debug, Error)] // Serialize, Deserialize, pub enum JobError { #[error("Job failed to run: {0}")] FailedToRun(String), @@ -13,14 +16,16 @@ pub enum JobError { #[error("Received Database errors! {0}")] DatabaseError(String), #[error("Task error")] - TaskError(#[from] TaskError), + TaskError(#[from] TicketError), + #[error("Command error: {0}")] + Send(String), } #[async_trait::async_trait] pub trait JobStore { - async fn add_job(&mut self, job: Job) -> Result<(), JobError>; - async fn list_all(&self) -> Result, JobError>; - async fn get_job(&self, job_id: &Uuid) -> Result; - async fn update_job(&mut self, job: Job) -> Result<(), JobError>; + async fn add_job(&mut self, job: NewJobDto) -> Result; + async fn list_all(&self) -> Result, JobError>; + async fn get_job(&self, job_id: &Uuid) -> Result, JobError>; + async fn update_job(&mut self, job: CreatedJobDto) -> Result<(), JobError>; async fn delete_job(&mut self, id: &Uuid) -> Result<(), JobError>; } diff --git a/src-tauri/src/domains/mod.rs b/src-tauri/src/domains/mod.rs index e5c2d65c..c6908032 100644 --- a/src-tauri/src/domains/mod.rs +++ b/src-tauri/src/domains/mod.rs @@ -1,4 +1,6 @@ +pub mod activity_store; +pub mod advertise_store; pub mod job_store; +pub mod render_store; +pub mod ticket_store; pub mod worker_store; -pub mod task_store; -pub mod activity_store; diff --git a/src-tauri/src/domains/render_store.rs b/src-tauri/src/domains/render_store.rs new file mode 100644 index 00000000..acf0f289 --- /dev/null +++ b/src-tauri/src/domains/render_store.rs @@ -0,0 +1,25 @@ +use std::{collections::HashMap, path::PathBuf}; + +use crate::models::{job::JobId, render_info::{CreatedRenderInfoDto, NewRenderInfoDto, RenderInfo}}; +use blender::blender::Frame; +use thiserror::Error; +use uuid::Uuid; + +#[derive(Debug, Error)] +pub enum RenderError { + #[error("Missing file")] + MissingFileAtPath, + #[error("Database Errors")] + DatabaseError(String), +} + +#[async_trait::async_trait] +pub trait RenderStore { + async fn find(&self, filter: Option) -> Result, RenderError>; + async fn update(&mut self, render_info: RenderInfo) -> Result<(), RenderError>; + async fn create( + &self, + render_info: NewRenderInfoDto, + ) -> Result; + async fn kill(&mut self, id: &Uuid) -> Result<(), RenderError>; +} diff --git a/src-tauri/src/domains/task_store.rs b/src-tauri/src/domains/task_store.rs deleted file mode 100644 index 11caa144..00000000 --- a/src-tauri/src/domains/task_store.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::models::task::Task; -use serde::{Deserialize, Serialize}; -use thiserror::Error; -use uuid::Uuid; - -#[derive(Debug, Error, Serialize, Deserialize)] -pub enum TaskError { - #[error("Unknown")] - Unknown, - #[error("Database error: {0}")] - DatabaseError(String), - #[error("Something wring with blender: {0}")] - BlenderError(String), -} - -#[async_trait::async_trait] -pub trait TaskStore { - // append new task to queue - async fn add_task(&mut self, task: Task) -> Result<(), TaskError>; - // Poll task will pop task entry from database - async fn poll_task(&mut self) -> Result; - // delete task by id - async fn delete_task(&mut self, task: Task) -> Result<(), TaskError>; - // delete all task with matching job id - async fn delete_job_task(&mut self, job_id: Uuid) -> Result<(), TaskError>; -} diff --git a/src-tauri/src/domains/ticket_store.rs b/src-tauri/src/domains/ticket_store.rs new file mode 100644 index 00000000..16493b24 --- /dev/null +++ b/src-tauri/src/domains/ticket_store.rs @@ -0,0 +1,32 @@ +use crate::models::ticket::{CreatedTicketDto, Ticket}; +use blender::{blender::BlenderError, manager::ManagerError}; +use thiserror::Error; +use uuid::Uuid; + +#[derive(Debug, Error)] +pub enum TicketError { + #[error("Unknown")] + Unknown, + #[error("Database error: {0}")] + DatabaseError(#[from] sqlx::Error), + #[error("Manager Error: {0}")] + Manager(#[from] ManagerError), + #[error("Something wring with blender: {0}")] + BlenderError(#[from] BlenderError), + #[error("Unable to get temp storage location")] + CacheError, +} + +#[async_trait::async_trait] +pub trait TicketStore { + // append new ticket to queue + async fn add_ticket(&self, ticket: Ticket) -> Result; + // Poll ticket will pop ticket entry from database + async fn poll_ticket(&self) -> Result, TicketError>; + // List pending ticket + async fn list_tickets(&self) -> Result>, TicketError>; + // delete ticket by id + async fn delete_ticket(&self, id: &Uuid) -> Result<(), TicketError>; + // delete all ticket with matching job id + async fn delete_job_ticket(&self, job_id: &Uuid) -> Result<(), TicketError>; +} diff --git a/src-tauri/src/domains/worker_store.rs b/src-tauri/src/domains/worker_store.rs index 74a9633b..df5c8003 100644 --- a/src-tauri/src/domains/worker_store.rs +++ b/src-tauri/src/domains/worker_store.rs @@ -1,9 +1,16 @@ -use crate::models::worker::{Worker, WorkerError}; +use crate::models::worker::Worker; +use libp2p::PeerId; + +#[derive(Debug)] +pub enum WorkerError { + Database(String), +} #[async_trait::async_trait] pub trait WorkerStore { async fn add_worker(&mut self, worker: Worker) -> Result<(), WorkerError>; - async fn get_worker(&self, id: &str) -> Option; + async fn get_worker(&self, id: &PeerId) -> Option; async fn list_worker(&self) -> Result, WorkerError>; - async fn delete_worker(&mut self, machine_id: &str) -> Result<(), WorkerError>; + async fn delete_worker(&mut self, id: &PeerId) -> Result<(), WorkerError>; + async fn clear_worker(&mut self) -> Result<(), WorkerError>; } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c585c4db..dcb585a9 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -2,22 +2,17 @@ Developer blog: - Had a brain fart trying to figure out some ideas allowing me to run this application as either client or server Originally thought of using Clap library to parse in input, but when I run `cargo tauri dev -- test` the application fail to compile due to unknown arguments when running web framework? - This issue has been solved by alllowing certain argument to run. By default it will try to launch the client user interface of the application. - Additionally, I need to check into the argument and see if there's a way we could just allow user to run --server without ui interface? - Interesting thoughts for sure + This issue has been solved by allowing certain argument to run. By default it will launch the manager version of this application. 9/2/24 -- Decided to rely on using Tauri plugin for cli commands and subcommands. Use that instead of clap. Since Tauri already incorporates Clap anyway. - Had an idea that allows user remotely to locally add blender installation without using GUI interface, This would serves two purposes - allow user to expressly select which blender version they can choose from the remote machine and prevent multiple download instances for the node, in case the target machine does not have it pre-installed. - Eventually, I will need to find a way to spin up a virtual machine and run blender farm on that machine to see about getting networking protocol working in place. This will allow me to do two things - I can continue to develop without needing to fire up a remote machine to test this and verify all packet works as intended while I can run the code in parallel to see if there's any issue I need to work overhead. - This might be another big project to work over the summer to understand how network works in Rust. - -- I noticed that some of the function are getting called twice. Check and see what's going on with React UI side of things - Research into profiling front end ui to ensure the app is not invoking the same command twice. - +- Ended up refactoring the program out. each struct have their respective files and folder associated with their group of services. + I still have problem using libp2p. Originally had it working but it was locking up main thread and program from executing in async. + Going to rely on example until I get this program working again. [F] - find a way to allow GUI interface to run as client mode for non cli users. [F] - consider using channel to stream data https://v2.tauri.app/develop/calling-frontend/#channels [F] - Before release - find a way to add updater https://v2.tauri.app/plugin/updater/ @@ -27,32 +22,39 @@ Developer blog: // Need a mapping to explain how blender manager is used and invoked for the job // Prevents additional console window on Windows in release, DO NOT REMOVE!! +// it might be interesting and useful if there's a debug mode enabled? #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use async_std::fs; +use anyhow::Error; use blender::manager::Manager as BlenderManager; +use blender::models::blender_config::BlenderConfig; use clap::{Parser, Subcommand}; -use domains::worker_store::WorkerStore; use dotenvy::dotenv; -use models::network; -use models::{app_state::AppState /* server_setting::ServerSetting */}; -use services::data_store::sqlite_job_store::SqliteJobStore; -use services::data_store::sqlite_task_store::SqliteTaskStore; -use services::data_store::sqlite_worker_store::SqliteWorkerStore; -use services::{blend_farm::BlendFarm, cli_app::CliApp, tauri_app::TauriApp}; -use sqlx::sqlite::SqlitePoolOptions; -use sqlx::SqlitePool; -use std::sync::Arc; -use tokio::{spawn, sync::RwLock}; - +use libp2p::Multiaddr; +use services::{blend_farm::BlendFarm, server::Server, tauri_app::TauriApp}; +use sqlx::{SqlitePool, sqlite::SqliteConnectOptions}; +use std::path::{Path, PathBuf}; +use tokio::spawn; + +use crate::constant::{JOB_TOPIC, NODE_TOPIC}; +use crate::models::server_setting::ServerSetting; +use crate::network::controller::Controller; +use crate::services::app_context::AppContext; + +pub mod constant; pub mod domains; pub mod models; +pub mod network; pub mod routes; pub mod services; #[derive(Parser)] struct Cli { + #[arg(short, long, default_value=None)] + config_path: Option, #[command(subcommand)] command: Option, + #[arg(short, long, default_value=None)] + secret_key: Option } #[derive(Subcommand)] @@ -60,74 +62,106 @@ enum Commands { Client, } -async fn config_sqlite_db() -> Result { - let mut path = BlenderManager::get_config_dir(); - path = path.join("blendfarm.db"); - - // create file if it doesn't exist (.config/BlendFarm/blendfarm.db) - if !path.exists() { - let _ = fs::File::create(&path).await; - } +async fn config_sqlite_db(path: impl AsRef) -> Result { + let options = SqliteConnectOptions::new() + .filename(path) + .create_if_missing(true); + SqlitePool::connect_with(options).await +} - // TODO: Consider thinking about the design behind this. Should we store database connection here or somewhere else? - let url = format!("sqlite://{}", path.as_os_str().to_str().unwrap()); - // macos: "sqlite:///Users/megamind/Library/Application Support/BlendFarm/blendfarm.db" - // dbg!(&url); - let pool = SqlitePoolOptions::new().connect(&url).await?; - sqlx::migrate!().run(&pool).await?; - Ok(pool) +async fn setup_connection(controller: &mut Controller) -> Result<(), Error> { + // Listen on all interfaces and whatever port OS assigns + let tcp: Multiaddr = "/ip4/0.0.0.0/tcp/0".parse().expect("Shouldn't fail"); + let udp: Multiaddr = "/ip4/0.0.0.0/udp/0/quic-v1" + .parse() + .expect("Shouldn't fail"); + + // let's automatically listen to the topics mention above. + // all network interference must subscribe to these topics! + controller.subscribe(JOB_TOPIC).await?; + controller.subscribe(NODE_TOPIC).await?; + + // can we subscribe first before we listen? + controller.start_listening(tcp).await; + controller.start_listening(udp).await; + Ok(()) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub async fn run() { dotenv().ok(); - // to run custom behaviour + // to collect user inputs for custom user preferences let cli = Cli::parse(); - let db = config_sqlite_db() + // If the user overrides a configuration path, then we'll use that, otherwise use default config directory location instead. + let blend_config_path = cli + .config_path + .unwrap_or(BlenderConfig::get_default_config_path()); + + // This program rely on BlenderManager. The user can override this path by providing config_path argument. + let blender_config = BlenderConfig::load(blend_config_path).expect("Must have blender configuration to load!"); + + // TODO: figure out how we can handle database path? + let db_path = BlenderConfig::get_default_config_dir().join(constant::DATABASE_FILE_NAME); + + // initialize database connection (We need a place to store persistent storage) + let db: sqlx::Pool = config_sqlite_db(db_path) .await .expect("Must have database connection!"); - // must have working network services - let (service, controller, receiver) = - network::new().await.expect("Fail to start network service"); + // setup network services + let (mut controller, receiver, server) = network::new(cli.secret_key) + .await + .expect("Fail to start network service"); - // start network service async - spawn(service.run()); + // Run Network service on separate thread. + let network_thread = spawn(async move { + server.run().await; + }); - let _ = match cli.command { - // run as client mode. - Some(Commands::Client) => { - // could this be reconsidered? - let task_store = SqliteTaskStore::new(db.clone()); - let task_store = Arc::new(RwLock::new(task_store)); - CliApp::new(task_store) - .run(controller, receiver) - .await - .map_err(|e| println!("Error running Cli app: {e:?}")) - } + if let Err(e) = setup_connection(&mut controller).await { + eprintln!("Fail to setup connection! {e:?}"); + } + + let manager = BlenderManager::initialize(blender_config).expect("Must have blender configuration to load!"); + // This server settings is different than blender config. + // Server Settings is used for Manager client only, to help organize and arrange file structure for completed render image results. + let server_settings = ServerSetting::load(); + let context = AppContext::new(manager, server_settings); + + // TODO: Restructure this to allow running client from GUI mode. + // TODO: Handle Receiver input here. + let result = match cli.command { + // run as client mode. + Some(Commands::Client) => Server::new(context, &db).run(controller, receiver).await, // run as GUI mode. _ => { - let job_store = SqliteJobStore::new(db.clone()); - let mut worker_store = SqliteWorkerStore::new(db.clone()); - - // Clear worker database before usage! - // TODO: Find a better way to optimize this - if let Ok(old_workers) = worker_store.list_worker().await { - for worker in old_workers { - let _ = &worker_store.delete_worker(&worker.machine_id).await; - } - } - - let job_store = Arc::new(RwLock::new(job_store)); - let worker_store = Arc::new(RwLock::new(worker_store)); - TauriApp::new(worker_store, job_store) + TauriApp::new(context.manager, &db) + .clear_workers_collection() .await .run(controller, receiver) .await - .map_err(|e| eprintln!("Fail to run Tauri app! {e:?}")) } }; + + if let Err(e) = result { + eprintln!("Received Network Error! {e:?}"); + } + + // abort network thread after closing. + network_thread.abort(); +} + +#[cfg(test)] +mod test { + use crate::config_sqlite_db; + + #[tokio::test] + pub async fn validate_creating_database_structure() { + let database_file_name = "blendfarm.db"; + let conn = config_sqlite_db(database_file_name).await; + assert!(conn.is_ok()); + } } diff --git a/src-tauri/src/models/advertise.rs b/src-tauri/src/models/advertise.rs new file mode 100644 index 00000000..4d9ed5fc --- /dev/null +++ b/src-tauri/src/models/advertise.rs @@ -0,0 +1,19 @@ +use std::path::PathBuf; +use uuid::Uuid; + +#[derive(Debug)] +pub struct Advertise { + pub id: Uuid, + pub ad_name: String, + pub file_path: PathBuf, +} + +impl Advertise { + pub fn new(ad_name: String, file_path: PathBuf) -> Self { + Self { + id: Uuid::new_v4(), + ad_name, + file_path, + } + } +} diff --git a/src-tauri/src/models/app_state.rs b/src-tauri/src/models/app_state.rs index 89e84d46..6af2fde2 100644 --- a/src-tauri/src/models/app_state.rs +++ b/src-tauri/src/models/app_state.rs @@ -1,18 +1,25 @@ -use super::server_setting::ServerSetting; -use crate::domains::{job_store::JobStore, worker_store::WorkerStore}; +use crate::models::server_setting::ServerSetting; use crate::services::tauri_app::UiCommand; -use blender::manager::Manager as BlenderManager; -use std::sync::Arc; -use tokio::sync::{RwLock, mpsc::Sender}; +use crate::models::setting_action::SettingsAction; +use futures::{channel::mpsc::{self, Sender, SendError}, SinkExt, StreamExt}; -pub type SafeLock = Arc>; - -// wonder if this is required? -// #[derive(Clone)] +#[derive(Clone)] pub struct AppState { - pub manager: SafeLock, - pub to_network: Sender, - pub setting: SafeLock, - pub job_db: SafeLock<(dyn JobStore + Send + Sync + 'static)>, - pub worker_db: SafeLock<(dyn WorkerStore + Send + Sync + 'static)>, + pub invoke: Sender, +} + +impl AppState { + + pub fn new( invoke: Sender ) -> Self { + Self { + invoke + } + } + + pub async fn get_settings(&mut self) -> Result { + let (sender, mut receiver) = mpsc::channel(1); + let event = UiCommand::Settings(SettingsAction::Get(sender)); + self.invoke.send(event).await?; + Ok(receiver.select_next_some().await) + } } diff --git a/src-tauri/src/models/behaviour.rs b/src-tauri/src/models/behaviour.rs index 49b9833c..e3c44031 100644 --- a/src-tauri/src/models/behaviour.rs +++ b/src-tauri/src/models/behaviour.rs @@ -1,21 +1,32 @@ -use libp2p::{gossipsub, kad, mdns, ping, swarm::NetworkBehaviour}; +use libp2p::{ + gossipsub::{self}, + kad::{self}, + mdns, + swarm::NetworkBehaviour, +}; use libp2p_request_response::cbor; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct FileRequest(pub String); + +// may be changed to use stream? #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct FileResponse(pub Vec); #[derive(NetworkBehaviour)] pub struct BlendFarmBehaviour { - pub ping: ping::Behaviour, // file transfer response protocol pub request_response: cbor::Behaviour, - // Communication between peers to pepers + + // broadcast message to listening node (chat relay) pub gossipsub: gossipsub::Behaviour, + // self discovery network service pub mdns: mdns::tokio::Behaviour, + // used to provide file availability - pub kad: kad::Behaviour, + // TODO: See if we can use sqlite? + pub kademlia: kad::Behaviour, } + diff --git a/src-tauri/src/models/blender_action.rs b/src-tauri/src/models/blender_action.rs new file mode 100644 index 00000000..ba9e9486 --- /dev/null +++ b/src-tauri/src/models/blender_action.rs @@ -0,0 +1,29 @@ +use std::path::PathBuf; + +use blender::blender::Blender; +use futures::channel::mpsc::Sender; +use semver::Version; + +use crate::services::tauri_app::{BlenderQuery, QueryMode}; + +#[derive(Debug)] +pub enum BlenderAction { + Add(PathBuf), + List(Sender>>, QueryMode), + Get(Version, Sender>), + Disconnect(Blender), // detach links associated with file path, but does not delete local installation! + Remove(Blender), // deletes local installation of blender, use it as last resort option. (E.g. force cache clear/reinstall/ corrupted copy) +} + +impl PartialEq for BlenderAction { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Add(l0), Self::Add(r0)) => l0 == r0, + (Self::List(.., l0), Self::List(.., r0)) => l0 == r0, + (Self::Get(l0, ..), Self::Get(r0, ..)) => l0 == r0, + (Self::Disconnect(l0), Self::Disconnect(r0)) => l0 == r0, + (Self::Remove(l0), Self::Remove(r0)) => l0 == r0, + _ => false, + } + } +} diff --git a/src-tauri/src/models/common.rs b/src-tauri/src/models/common.rs index dbf0de81..c5e414b4 100644 --- a/src-tauri/src/models/common.rs +++ b/src-tauri/src/models/common.rs @@ -1,8 +1,11 @@ -use std::path::PathBuf; +// use std::path::PathBuf; -use serde::{Deserialize, Serialize}; +// use ser dde::{Deserialize, Serialize}; + +/* // not sure if I still need this or keep it separated? +#[allow(dead_code)] #[derive(Serialize, Deserialize)] pub enum SenderMsg { FileRequest(String, usize), @@ -10,7 +13,10 @@ pub enum SenderMsg { Render(PathBuf, i32), } +#[allow(dead_code)] #[derive(Serialize, Deserialize)] pub enum ReceiverMsg { CanReceive(bool), } + +*/ diff --git a/src-tauri/src/models/computer_spec.rs b/src-tauri/src/models/computer_spec.rs index b1f09293..e168e71d 100644 --- a/src-tauri/src/models/computer_spec.rs +++ b/src-tauri/src/models/computer_spec.rs @@ -2,9 +2,11 @@ use machine_info::Machine; use serde::{Deserialize, Serialize}; use std::env::consts; +pub type Hostname = String; + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ComputerSpec { - pub host: String, + pub host: Hostname, pub os: String, pub arch: String, pub memory: u64, @@ -14,7 +16,8 @@ pub struct ComputerSpec { } impl ComputerSpec { - pub fn new(machine: &mut Machine) -> Self { + pub fn new() -> Self { + let mut machine = Machine::new(); let sys_info = machine.system_info(); let memory = &sys_info.memory; let host = &sys_info.hostname; diff --git a/src-tauri/src/models/constant.rs b/src-tauri/src/models/constant.rs new file mode 100644 index 00000000..7a38245f --- /dev/null +++ b/src-tauri/src/models/constant.rs @@ -0,0 +1,9 @@ +// TODO: make this user adjustable. +// Ideally, this should be store under BlendFarmUserSettings +// pub const MAX_FRAME_CHUNK_SIZE: i32 = 30; + +#[cfg(test)] +pub mod test { + pub const EXAMPLE_FILE: &str = "./../blender_rs/examples/assets/test.blend"; + pub const EXAMPLE_OUTPUT: &str = "./../blender_rs/examples/assets/"; +} \ No newline at end of file diff --git a/src-tauri/src/models/ffmpeg.rs b/src-tauri/src/models/ffmpeg.rs deleted file mode 100644 index 169b852a..00000000 --- a/src-tauri/src/models/ffmpeg.rs +++ /dev/null @@ -1,2 +0,0 @@ -// use this to invoke FFMpeg to composite frame into short video animation. -// this will be used on the manager side of application, as we want to composit incoming frame jobs into video to preview the final render image result. diff --git a/src-tauri/src/models/job.rs b/src-tauri/src/models/job.rs index 9a486476..b14b66d2 100644 --- a/src-tauri/src/models/job.rs +++ b/src-tauri/src/models/job.rs @@ -3,117 +3,260 @@ - Original idea behind this was to use PhantomData to mitigate the status of the job instead of reading from enum. Need to refresh materials about PhantomData, and how I can translate this data information for front end to update/reflect changes The idea is to change the struct to have state of the job. + I think the limitation for this is serialization/deserialization property. - I need to fetch the handles so that I can maintain and monitor all node activity. + - The Job will have similar state machines. + - First state is to fetch info about this job and related task from all nodes on the network. + - Then Reconciliate with the existing image stored locally. + - Afterward, collect linkage to path, any missing remaining will create a new task and send to pools of pending task to distribute + - This job will routinely checks on all task generated by varioius node. - TODO: See about migrating Sender code into this module? */ -use super::task::Task; +use super::ticket::Ticket; +use super::with_id::WithId; use crate::domains::job_store::JobError; -use blender::models::mode::Mode; +use crate::network::PeerIdString; +use blender::blender::Frame; +use blender::{blend_file::BlendFile, models::mode::RenderMode}; +use futures::channel::mpsc::Sender; use semver::Version; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::{hash::Hash, path::PathBuf}; +use std::{ffi::OsStr, path::Path}; +use std::path::PathBuf; use uuid::Uuid; -#[derive(Debug, Serialize, Deserialize)] +// TODO: I need to refactor this script so it's less code smell. +// I want to make this a better management system. +// The Job struct will contain the work order information. +// We have a require qty count to render to meet the work order quantity. +// This work order can have multiple of Task, as long as the task have the following conditions: +// A) an newly existing node assigned to this task. +// B) an node containing raw completed image for this job +// C) successfully downloaded the image from the node + local reference instead. (End goal) +// This means that if a node was recently assigned to work on this job's task, but was cancel, both job and node should delete the task as no new information is savageable. +// Any information created or stored will persist to local database for persistent storage and quick lookup. This can be handy in the future if we can get ffmpeg included. + +// THIS IS TREATED AS NOTIFICATION UPDATES. DO NOT TAKE THIS AS COMMAND! Acknowledge the package and run behavior tree decision. +#[derive(Debug, Serialize, Deserialize )] pub enum JobEvent { - Render(Task), + Render(PeerIdString, Ticket), Remove(Uuid), - RequestJob, + Failed(String), + RequestTask, ImageCompleted { job_id: Uuid, frame: Frame, - file_name: String, + file_name: String, // Could PathBuf be treated for file_name? + }, + AskForCompletedJobFrameList(JobId), + ImageCompletedList { + job_id: JobId, + files: Vec, }, - JobComplete, - Error(JobError), + + TicketComplete(JobId, Frame), // what's the difference between JobComplete and TaskComplete? + // Error(JobError), + // TODO: for now let's handle this error as string. Find a reason why we want to serialize error enums? + Error(String) } -pub type Frame = i32; +#[derive(Debug)] +pub enum JobAction { + Find(JobId, Sender>), + Update(CreatedJobDto), + Create(NewJobDto, Sender>), + Kill(JobId), + All(Sender>>), + // we will ask all of the node on the network if there's any completed job list. + // The node will advertise their collection of completed job + // the host will be responsible to compare with the current output files and + // see if there's any missing job. If there is missing frame then + // we will ask to fetch for that completed image back + AskForCompletedList(JobId), + Advertise(JobId), +} + +// Used to ignore sender types comparsion. We do not care about sender equality. +impl PartialEq for JobAction { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Find(l0, ..), Self::Find(r0, ..)) => l0 == r0, + (Self::Update(l0), Self::Update(r0)) => l0.id == r0.id, + (Self::Create(l0, ..), Self::Create(r0, ..)) => l0 == r0, + (Self::Kill(l0), Self::Kill(r0)) => l0 == r0, + (Self::All(..), Self::All(..)) => true, + (Self::AskForCompletedList(l0), Self::AskForCompletedList(r0)) => l0 == r0, + (Self::Advertise(l0), Self::Advertise(r0)) => l0 == r0, + _ => false, + } + } +} + +pub type JobId = Uuid; +pub type Output = PathBuf; +pub type NewJobDto = Job; +pub type CreatedJobDto = WithId; // This job is created by the manager and will be used to help determine the individual task created for the workers // we will derive this job into separate task for individual workers to process based on chunk size. -#[derive(Debug, Serialize, Deserialize, Clone, sqlx::FromRow)] +#[derive(Debug, Serialize, Deserialize, Clone, sqlx::FromRow, sqlx::Encode, sqlx::Decode)] pub struct Job { - /// Unique job identifier - pub id: Uuid, /// contains the information to specify the kind of job to render (We could auto fill this from blender peek function?) - pub mode: Mode, + mode: RenderMode, + /// Path to blender files - pub project_file: PathBuf, + blend_file: BlendFile, + // target blender version - pub blender_version: Version, + pub(crate) blender_version: Version, + // target output destination - pub output: PathBuf, - // completed render data. - // TODO: discuss this? Let's map this out and see how we can better utilize this structure? - renders: HashMap, + output: Output, + + // List of task created by the runners This serves as a job history and transaction that perform the job + tasks: Vec, } impl Job { - /// Create a new job entry with provided all information intact. Used for holding database records - pub fn new( - id: Uuid, - mode: Mode, - project_file: PathBuf, - blender_version: Version, - output: PathBuf, - renders: HashMap, + // private - no validation, we trust that the validation is done from public api. + fn new( + mode: RenderMode, + blend_file: BlendFile, + blender_version: Version, // TODO: see if we can validate if this job uses the correct blender version + output: Output, // must be a valid directory ) -> Self { Self { - id, mode, - project_file, + blend_file, blender_version, output, - renders, + tasks: Vec::new(), } } - /// Create a new job entry from the following parameter inputs + /// Create a new job entry with provided all information intact. Used for holding database records pub fn from( - project_file: PathBuf, + mode: RenderMode, + project_file: impl AsRef, + version: Version, output: PathBuf, - blender_version: Version, - mode: Mode, - ) -> Self { - Self { - id: Uuid::new_v4(), - mode, - project_file, - blender_version, - output, - renders: Default::default(), + ) -> Result { + match BlendFile::new(project_file) { + Ok(file) => Ok(Job::new(mode, file, version, output)), + Err(e) => Err(JobError::InvalidFile(e.to_string())), } } - pub fn get_file_name(&self) -> &str { - self.project_file.file_name().unwrap().to_str().unwrap() + // Hmm Risky. Would consider promoting to higher application layer services. + pub fn generate_task(self, job_id: Uuid) -> Option { + // in this case, a job would have break up into pieces for worker client to receive and start a new job + // first thing first, how can I tell if the job is completed or not? + // in the future we will find a better mechanism to partition the frames up and distributed across network nodes. + // If we have node requesting for new task, but we've completed exhaust the query range, we should check other nodes + // and ask the highest queue frame for some of the frame counts instead, until we've reached to a certain water overflow threshold. + let (start, end) = match &self.mode { + RenderMode::Frame(v) => (v.to_owned(), v.to_owned()), + RenderMode::Animation{start, end} => (start.to_owned(), end.to_owned()), + }; + + let job_record = WithId { id: job_id, item: self }; + + match Ticket::from(job_record, start, end) { + Ok(task) => Some(task), + Err(e) => { + println!("Unable to make task? {e:?}"); + None + } + } + } + + pub fn get_file_name_expected(&self) -> &OsStr { + self.blend_file + .to_path() + .file_name() + .expect("Must have valid file name already") + } +} + +impl PartialEq for Job { + fn eq(&self, other: &Self) -> bool { + self.mode == other.mode + && self.blend_file == other.blend_file + && self.blender_version == other.blender_version + && self.output == other.output + // no need to compare job history for partial equal + // && self.tasks == other.tasks } +} - pub fn get_project_path(&self) -> &PathBuf { - &self.project_file +impl AsRef for Job { + fn as_ref(&self) -> &BlendFile { + &self.blend_file } +} - pub fn get_version(&self) -> &Version { +impl AsRef for Job { + fn as_ref(&self) -> &Version { &self.blender_version } } -impl AsRef for Job { - fn as_ref(&self) -> &Uuid { - &self.id +/// return the job output destination (Should be used on the host machine) +impl AsRef for Job { + fn as_ref(&self) -> &Output { + &self.output } } -impl PartialEq for Job { - fn eq(&self, other: &Self) -> bool { - self.id == other.id +impl AsRef for Job { + fn as_ref(&self) -> &RenderMode { + &self.mode } } -impl Hash for Job { - fn hash(&self, state: &mut H) { - self.id.hash(state); +#[cfg(test)] +pub(crate) mod test { + use super::*; + use crate::models::constant::test::{EXAMPLE_FILE, EXAMPLE_OUTPUT}; + use std::path::Path; + + pub fn scaffold_job() -> Job { + let mode = RenderMode::Frame(1); + let file = Path::new(EXAMPLE_FILE); + let project_file = BlendFile::new(file).expect("expect this to work without issue"); + let version = Version::new(4, 4, 0); + let dir = Path::new(EXAMPLE_OUTPUT); + let output = dir.to_path_buf(); + Job::new(mode, project_file, version, output) + } + + // we should at least try to test it against public api + #[test] + fn create_job_successful() { + let file = Path::new(EXAMPLE_FILE); + let mode = RenderMode::Frame(1); + let version = Version::new(1, 1, 1); + let output = Path::new("./test/"); + let job = Job::from(mode.clone(), file, version.clone(), output.to_path_buf()); + + let project_file = BlendFile::new(file).expect("Should be valid project file"); + + assert!(job.is_ok()); + let job = job.unwrap(); + + assert_eq!(job.mode, mode); + assert_eq!(job.output, output); + assert_eq!(AsRef::::as_ref(&job), &project_file); + assert_eq!(AsRef::::as_ref(&job), &version); + assert_eq!( + job.get_file_name_expected(), + file.file_name() + .expect("Should have valid file name") + .to_str() + .expect("Shoudl have valid file name!") + ); } + + #[test] + fn invalid_project_file_path_should_fail() {} } diff --git a/src-tauri/src/models/message.rs b/src-tauri/src/models/message.rs deleted file mode 100644 index 90bdb38a..00000000 --- a/src-tauri/src/models/message.rs +++ /dev/null @@ -1,78 +0,0 @@ -use super::behaviour::FileResponse; -use super::computer_spec::ComputerSpec; -use super::job::JobEvent; -use futures::channel::oneshot; -use libp2p::{Multiaddr, PeerId}; -use libp2p_request_response::ResponseChannel; -use std::{collections::HashSet, error::Error}; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum NetworkError { - #[error("Unable to listen: {0}")] - UnableToListen(String), - #[error("Not Connected")] - NotConnected, - #[error("Invalid connection")] - Invalid, - #[error("Bad Input")] - BadInput, - #[error("Send Error: {0}")] - SendError(String), - #[error("No peers on network have this file available to download!")] - NoPeerProviderFound, - #[error("Unable to save download file: {0}")] - UnableToSave(String), - #[error("Timeout, unable to connect peer")] - Timeout, -} - -// Send commands to network. -#[derive(Debug)] -pub enum NetCommand { - IncomingWorker(PeerId), - Status(String), - SubscribeTopic(String), - UnsubscribeTopic(String), - JobStatus(PeerId, JobEvent), - // use this event to send message to a specific node - StartProviding { - file_name: String, - sender: oneshot::Sender<()>, - }, - GetProviders { - file_name: String, - sender: oneshot::Sender>, - }, - RequestFile { - peer_id: PeerId, - file_name: String, - sender: oneshot::Sender, Box>>, - }, - RespondFile { - file: Vec, - channel: ResponseChannel, - }, - Dial { - peer_id: PeerId, - peer_addr: Multiaddr, - sender: oneshot::Sender>>, - }, -} - -// TODO: Received network events. -#[derive(Debug)] -pub enum NetEvent { - // Share basic computer configuration for sharing Blender compatible executable over the network. (To help speed up the installation over the network.) - Status(PeerId, String), // Receive message status (To GUI?) Could I treat this like Chat messages? - OnConnected(PeerId), - NodeDiscovered(PeerId, ComputerSpec), - // TODO: Future impl. Use this to send computer activity - // Heartbeat() // share hardware statistic monitor heartbeat. (CPU/GPU/RAM activity readings) - NodeDisconnected(PeerId), // On Node disconnected - InboundRequest { - request: String, - channel: ResponseChannel, - }, - JobUpdate(PeerId, JobEvent), -} diff --git a/src-tauri/src/models/mod.rs b/src-tauri/src/models/mod.rs index 107650df..754eeb21 100644 --- a/src-tauri/src/models/mod.rs +++ b/src-tauri/src/models/mod.rs @@ -1,14 +1,15 @@ +pub mod advertise; pub mod app_state; pub mod behaviour; pub(crate) mod common; pub(crate) mod computer_spec; +pub(crate) mod constant; pub mod error; pub(crate) mod job; -pub mod message; -pub mod network; -pub(crate) mod project_file; pub(crate) mod render_info; -pub(crate) mod task; -// pub mod render_queue; +pub(crate) mod ticket; pub(crate) mod server_setting; +pub mod with_id; pub mod worker; +pub(crate) mod blender_action; +pub(crate) mod setting_action; diff --git a/src-tauri/src/models/network.rs b/src-tauri/src/models/network.rs deleted file mode 100644 index 58e01d01..00000000 --- a/src-tauri/src/models/network.rs +++ /dev/null @@ -1,676 +0,0 @@ -use super::behaviour::{BlendFarmBehaviour, FileRequest, FileResponse}; -use super::computer_spec::ComputerSpec; -use super::job::JobEvent; -use super::message::{NetCommand, NetEvent, NetworkError}; -use super::server_setting::ServerSetting; -use crate::models::behaviour::BlendFarmBehaviourEvent; -use core::str; -use futures::{channel::oneshot, prelude::*, StreamExt}; -use libp2p::kad::RecordKey; -use libp2p::multiaddr::Protocol; -use libp2p::{ - gossipsub::{self, IdentTopic}, - kad, mdns, ping, - swarm::{Swarm, SwarmEvent}, - tcp, Multiaddr, PeerId, StreamProtocol, SwarmBuilder, -}; -use libp2p_request_response::{OutboundRequestId, ProtocolSupport, ResponseChannel}; -use machine_info::Machine; -use std::collections::{hash_map, HashMap, HashSet}; -use std::error::Error; -use std::path::PathBuf; -use std::time::Duration; -use std::u64; -use tokio::sync::mpsc::{self, Receiver, Sender}; -use tokio::{io, select}; - -/* -Network Service - Provides simple network interface for peer-to-peer network for BlendFarm. -Includes mDNS () -*/ - -pub const STATUS: &str = "blendfarm/status"; -pub const SPEC: &str = "blendfarm/spec"; -pub const JOB: &str = "blendfarm/job"; -pub const HEARTBEAT: &str = "blendfarm/heartbeat"; -const TRANSFER: &str = "/file-transfer/1"; - -// the tuples return three objects -// the NetworkService holds the network loop operation -// the Network Controller to send command to network service -// the Receiver from network services -pub async fn new() -> Result<(NetworkService, NetworkController, Receiver), NetworkError> -{ - let duration = Duration::from_secs(u64::MAX); - // let id_keys = identity::Keypair::generate_ed25519(); - let tcp_config: tcp::Config = tcp::Config::default(); - - let mut swarm = SwarmBuilder::with_new_identity() - .with_tokio() - .with_tcp( - tcp_config, - libp2p::tls::Config::new, - libp2p::yamux::Config::default, - ) - .expect("Should be able to build with tcp configuration?") - .with_quic() - .with_behaviour(|key| { - let ping_config = ping::Config::default(); - let ping = ping::Behaviour::new(ping_config); - - let gossipsub_config = gossipsub::ConfigBuilder::default() - .heartbeat_interval(Duration::from_secs(10)) - // .validation_mode(gossipsub::ValidationMode::Strict) - // .message_id_fn(message_id_fn) - .build() - .map_err(|msg| io::Error::new(io::ErrorKind::Other, msg))?; - - // p2p communication - let gossipsub = gossipsub::Behaviour::new( - gossipsub::MessageAuthenticity::Signed(key.clone()), - gossipsub_config, - ) - .expect("Fail to create gossipsub behaviour"); - - // network discovery usage - let mdns = - mdns::tokio::Behaviour::new(mdns::Config::default(), key.public().to_peer_id()) - .expect("Fail to create mdns behaviour!"); - - // Used to provide file provision list - let kad = kad::Behaviour::new( - key.public().to_peer_id(), - kad::store::MemoryStore::new(key.public().to_peer_id()), - ); - - let rr_config = libp2p_request_response::Config::default(); - let protocol = [(StreamProtocol::new(TRANSFER), ProtocolSupport::Full)]; - let request_response = libp2p_request_response::Behaviour::new(protocol, rr_config); - - Ok(BlendFarmBehaviour { - ping, - request_response, - gossipsub, - mdns, - kad, - }) - }) - .expect("Expect to build behaviour") - .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(duration)) - .build(); - - let tcp: Multiaddr = "/ip4/0.0.0.0/tcp/0" - .parse() - .map_err(|_| NetworkError::BadInput)?; - - let udp: Multiaddr = "/ip4/0.0.0.0/udp/0/quic-v1" - .parse() - .map_err(|_| NetworkError::BadInput)?; - - // Begin listening on tcp and udp as server - swarm - .listen_on(tcp) - .map_err(|e| NetworkError::UnableToListen(e.to_string()))?; - - swarm - .listen_on(udp) - .map_err(|e| NetworkError::UnableToListen(e.to_string()))?; - - // set the kad as server mode - swarm.behaviour_mut().kad.set_mode(Some(kad::Mode::Server)); - - // the command sender is used for outside method to send message commands to network queue - let (command_sender, command_receiver) = mpsc::channel::(32); - - // the event sender is used to handle incoming network message. E.g. RunJob - let (event_sender, event_receiver) = mpsc::channel::(32); - - let local_peer_id = swarm.local_peer_id().clone(); - - Ok(( - NetworkService { - swarm, - command_receiver, - event_sender, - public_addr: None, - machine: Machine::new(), - pending_dial: Default::default(), - pending_get_providers: Default::default(), - pending_start_providing: Default::default(), - pending_request_file: Default::default(), - // pending_task: Default::default(), - }, - NetworkController { - sender: command_sender, - settings: ServerSetting::load(), - providing_files: Default::default(), - // there could be some other factor this this may not work as intended? Let's find out soon! - public_id: local_peer_id, - }, - event_receiver, - )) -} - -// strange that I don't have the local peer id? -#[derive(Clone)] -pub struct NetworkController { - sender: mpsc::Sender, - pub settings: ServerSetting, - // Use string to defer OS specific path system. This will be treated as a URI instead. /job_id/frame - pub providing_files: HashMap, - // making it public until we can figure out how to mitigate the usage of variable. - pub public_id: PeerId, -} - -impl NetworkController { - pub async fn subscribe_to_topic(&mut self, topic: String) { - self.sender - .send(NetCommand::SubscribeTopic(topic)) - .await - .unwrap(); - } - - pub async fn unsubscribe_from_topic(&mut self, topic: String) { - self.sender - .send(NetCommand::UnsubscribeTopic(topic)) - .await - .unwrap(); - } - - pub async fn send_status(&mut self, status: String) { - println!("[Status]: {status}"); - self.sender - .send(NetCommand::Status(status)) - .await - .expect("Command should not been dropped"); - } - - // How do I get the peers info I want to communicate with? - pub async fn send_job_message(&mut self, target: PeerId, event: JobEvent) { - self.sender - .send(NetCommand::JobStatus(target, event)) - .await - .expect("Command should not be dropped"); - } - - // Share computer info to - pub async fn share_computer_info(&mut self, peer_id: PeerId) { - self.sender - .send(NetCommand::IncomingWorker(peer_id)) - .await - .expect("Command should not have been dropped"); - } - - pub async fn start_providing(&mut self, file_name: String, path: PathBuf) { - let (sender, receiver) = oneshot::channel(); - println!("Start providing file {:?}", file_name); - self.providing_files.insert(file_name.clone(), path); - let cmd = NetCommand::StartProviding { file_name, sender }; - self.sender - .send(cmd) - .await - .expect("Command receiver not to be dropped"); - receiver.await.expect("Sender should not be dropped"); - } - - pub async fn get_providers(&mut self, file_name: &str) -> HashSet { - let (sender, receiver) = oneshot::channel(); - self.sender - .send(NetCommand::GetProviders { - file_name: file_name.to_string(), - sender, - }) - .await - .expect("Command receiver should not be dropped"); - receiver.await.expect("Sender should not be dropped") - } - - pub async fn get_file_from_peers( - &mut self, - file_name: &str, - destination: &PathBuf, - ) -> Result { - let providers = self.get_providers(&file_name).await; - if providers.is_empty() { - return Err(NetworkError::NoPeerProviderFound); - } - - let requests = providers.into_iter().map(|p| { - let mut client = self.clone(); - // should I just request a file from one peer instead? - async move { client.request_file(p, file_name).await }.boxed() - }); - - let content = match futures::future::select_ok(requests).await { - Ok(data) => data.0, - Err(e) => { - // Received a "Timeout" error? What does that mean? Should I try to reconnect? - eprintln!("No peer found? {e:?}"); - return Err(NetworkError::Timeout); - } - }; - - let file_path = destination.join(file_name); - match async_std::fs::write(file_path.clone(), content).await { - Ok(_) => Ok(file_path), - Err(e) => Err(NetworkError::UnableToSave(e.to_string())), - } - } - - pub async fn dial( - &mut self, - peer_id: PeerId, - peer_addr: Multiaddr, - ) -> Result<(), Box> { - let (sender, receiver) = oneshot::channel(); - self.sender - .send(NetCommand::Dial { - peer_id, - peer_addr, - sender, - }) - .await - .expect("Command receiver should not be dropped"); - receiver - .await - .expect("Command receiver should not be dropped") - } - - async fn request_file( - &mut self, - peer_id: PeerId, - file_name: &str, - ) -> Result, Box> { - let (sender, receiver) = oneshot::channel(); - self.sender - .send(NetCommand::RequestFile { - peer_id, - file_name: file_name.into(), - sender, - }) - .await - .expect("Command should not be dropped"); - receiver.await.expect("Sender should not be dropped") - } - - pub(crate) async fn respond_file( - &mut self, - file: Vec, - channel: ResponseChannel, - ) { - self.sender - .send(NetCommand::RespondFile { file, channel }) - .await - .expect("Command should not be dropped"); - } -} - -// this will help launch libp2p network. Should use QUIC whenever possible! -pub struct NetworkService { - // swarm behaviour - interface to the network - swarm: Swarm, - - // receive Network command - pub command_receiver: Receiver, - - // Used to collect computer information to distribute across network. - machine: Machine, - - // Send Network event to subscribers. - event_sender: Sender, - - public_addr: Option, - - // empheral key used to stored and communicate with. - pending_get_providers: HashMap>>, - pending_start_providing: HashMap>, - pending_request_file: - HashMap, Box>>>, - pending_dial: HashMap>>>, - // pending_task: HashMap>>>, -} - -impl NetworkService { - // send command - async fn handle_command(&mut self, cmd: NetCommand) { - match cmd { - NetCommand::Status(msg) => { - let data = msg.as_bytes(); - let topic = IdentTopic::new(STATUS); - if let Err(e) = self.swarm.behaviour_mut().gossipsub.publish(topic, data) { - eprintln!("Fail to send status over network! {e:?}"); - } - } - NetCommand::RequestFile { - peer_id, - file_name, - sender, - } => { - let request_id = self - .swarm - .behaviour_mut() - .request_response - .send_request(&peer_id, FileRequest(file_name.into())); - self.pending_request_file.insert(request_id, sender); - } - NetCommand::RespondFile { file, channel } => { - self.swarm - .behaviour_mut() - .request_response - .send_response(channel, FileResponse(file)) - .expect("Connection to peer may still be open?"); - } - NetCommand::IncomingWorker(peer_id) => { - let spec = ComputerSpec::new(&mut self.machine); - let data = bincode::serialize(&spec).unwrap(); - let topic = IdentTopic::new(SPEC); - let _ = self.swarm.dial(peer_id); - if let Err(e) = self.swarm.behaviour_mut().gossipsub.publish(topic, data) { - eprintln!("Fail to send identity to swarm! {e:?}"); - }; - } - NetCommand::GetProviders { file_name, sender } => { - let key = RecordKey::new(&file_name.as_bytes()); - let query_id = self.swarm.behaviour_mut().kad.get_providers(key.into()); - self.pending_get_providers.insert(query_id, sender); - } - NetCommand::StartProviding { file_name, sender } => { - let provider_key = RecordKey::new(&file_name.as_bytes()); - let query_id = self - .swarm - .behaviour_mut() - .kad - .start_providing(provider_key) - .expect("No store error."); - - self.pending_start_providing.insert(query_id, sender); - } - NetCommand::SubscribeTopic(topic) => { - let ident_topic = IdentTopic::new(topic); - self.swarm - .behaviour_mut() - .gossipsub - .subscribe(&ident_topic) - .unwrap(); - } - NetCommand::UnsubscribeTopic(topic) => { - let ident_topic = IdentTopic::new(topic); - self.swarm - .behaviour_mut() - .gossipsub - .unsubscribe(&ident_topic); - } - // what was I'm suppose to do here? - NetCommand::JobStatus(_peer_id, _event) => { - /* - // convert data into json format. - // let data = bincode::serialize(&status).unwrap(); - - // TODO: Read more about libp2p and how I can just connect to one machine and send that machine job status information. - // let _ = self.swarm.dial(target); - // once we have a tcp/udp/quik connection, we should only send one task over and end the pipe. - - // TODO: Find a way to send JobEvent to specific target machine? - // let topic = IdentTopic::new(JOB); - // if let Err(e) = self.swarm.behaviour_mut().gossipsub.publish(topic, data) { - // eprintln!("Fail to send job! {e:?}"); - // } - */ - - /* - Let's break this down, we receive a worker with peer_id and peer_addr, both of which will be used to establish communication - Once we establish a communication, that target peer will need to receive the pending task we have assigned for them. - For now, we will try to dial the target peer, and append the task to our network service pool of pending task. - */ - // self.pending_task.insert(peer_id, ); - } - NetCommand::Dial { - peer_id, - peer_addr, - sender, - } => { - if let hash_map::Entry::Vacant(e) = self.pending_dial.entry(peer_id) { - self.swarm - .behaviour_mut() - .kad - .add_address(&peer_id, peer_addr.clone()); - match self.swarm.dial(peer_addr.with(Protocol::P2p(peer_id))) { - Ok(()) => { - e.insert(sender); - } - Err(e) => { - let _ = sender.send(Err(Box::new(e))); - } - } - } - } - }; - } - - async fn handle_event(&mut self, event: SwarmEvent) { - match event { - SwarmEvent::Behaviour(BlendFarmBehaviourEvent::Mdns(mdns)) => { - self.handle_mdns(mdns).await - } - SwarmEvent::Behaviour(BlendFarmBehaviourEvent::Gossipsub(gossip)) => { - self.handle_gossip(gossip).await - } - SwarmEvent::Behaviour(BlendFarmBehaviourEvent::Kad(kad)) => { - self.handle_kademila(kad).await - } - SwarmEvent::Behaviour(BlendFarmBehaviourEvent::RequestResponse(rr)) => { - self.handle_response(rr).await - } - // Once the swarm establish connection, we then send the peer_id we connected to. - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - self.event_sender - .send(NetEvent::OnConnected(peer_id)) - .await - .unwrap(); - } - SwarmEvent::ConnectionClosed { peer_id, .. } => { - self.event_sender - .send(NetEvent::NodeDisconnected(peer_id)) - .await - .unwrap(); - } - SwarmEvent::NewListenAddr { address, .. } => { - // hmm.. I need to capture the address here? - // how do I save the address? - if address.protocol_stack().any(|f| f.contains("tcp")) { - dbg!(&address); - self.public_addr = Some(address); - } - } - _ => { - println!("{event:?}"); - } - } - } - - async fn handle_response( - &mut self, - event: libp2p_request_response::Event, - ) { - match event { - libp2p_request_response::Event::Message { message, .. } => match message { - libp2p_request_response::Message::Request { - request, channel, .. - } => { - self.event_sender - .send(NetEvent::InboundRequest { - request: request.0, - channel, - }) - .await - .expect("Event receiver should not be dropped!"); - } - libp2p_request_response::Message::Response { - request_id, - response, - } => { - if let Err(e) = self - .pending_request_file - .remove(&request_id) - .expect("Request is still pending?") - .send(Ok(response.0)) - { - eprintln!("libp2p Response Error: {e:?}"); - } - } - }, - libp2p_request_response::Event::OutboundFailure { - request_id, error, .. - } => { - if let Err(e) = self - .pending_request_file - .remove(&request_id) - .expect("Request is still pending") - .send(Err(Box::new(error))) - { - eprintln!("libp2p outbound fail: {e:?}"); - } - } - libp2p_request_response::Event::ResponseSent { .. } => {} - _ => {} - } - } - - async fn handle_mdns(&mut self, event: mdns::Event) { - match event { - mdns::Event::Discovered(peers) => { - for (peer_id, address) in peers { - self.swarm - .behaviour_mut() - .gossipsub - .add_explicit_peer(&peer_id); - - // add the discover node to kademlia list. - self.swarm - .behaviour_mut() - .kad - .add_address(&peer_id, address); - } - } - mdns::Event::Expired(peers) => { - for (peer_id, ..) in peers { - self.swarm - .behaviour_mut() - .gossipsub - .remove_explicit_peer(&peer_id); - } - } - }; - } - - // TODO: Figure out how I can use the match operator for TopicHash. I'd like to use the TopicHash static variable above. - async fn handle_gossip(&mut self, event: gossipsub::Event) { - match event { - gossipsub::Event::Message { message, .. } => match message.topic.as_str() { - SPEC => { - let source = message.source.expect("Source cannot be empty!"); - let specs = - bincode::deserialize(&message.data).expect("Fail to parse Computer Specs!"); - if let Err(e) = self - .event_sender - .send(NetEvent::NodeDiscovered(source, specs)) - .await - { - eprintln!("Something failed? {e:?}"); - } - } - STATUS => { - let source = message.source.expect("Source cannot be empty!"); - let msg = String::from_utf8(message.data).unwrap(); - if let Err(e) = self.event_sender.send(NetEvent::Status(source, msg)).await { - eprintln!("Something failed? {e:?}"); - } - } - JOB => { - let peer_id = self.swarm.local_peer_id(); - let job_event = - bincode::deserialize(&message.data).expect("Fail to parse Job data!"); - if let Err(e) = self - .event_sender - .send(NetEvent::JobUpdate(peer_id.clone(), job_event)) - .await - { - eprintln!("Something failed? {e:?}"); - } - } - _ => { - let topic = message.topic.as_str(); - let data = String::from_utf8(message.data).unwrap(); - println!("Intercepted signal here? How to approach this? topic:{topic} | data:{data}"); - // TODO: We may intercept signal for other purpose here, how can I do that? - } - }, - _ => {} - } - } - - // Handle kademila events (Used for file sharing) - async fn handle_kademila(&mut self, event: kad::Event) { - match event { - kad::Event::OutboundQueryProgressed { - id, - result: kad::QueryResult::StartProviding(_), - .. - } => { - let sender: oneshot::Sender<()> = self - .pending_start_providing - .remove(&id) - .expect("Completed query to be previously pending."); - let _ = sender.send(()); - } - kad::Event::OutboundQueryProgressed { - id, - result: - kad::QueryResult::GetProviders(Ok(kad::GetProvidersOk::FoundProviders { - providers, - .. - })), - .. - } => { - if let Some(sender) = self.pending_get_providers.remove(&id) { - sender.send(providers).expect("Receiver not to be dropped"); - self.swarm - .behaviour_mut() - .kad - .query_mut(&id) - .unwrap() - .finish(); - } - } - kad::Event::OutboundQueryProgressed { - result: - kad::QueryResult::GetProviders(Ok( - kad::GetProvidersOk::FinishedWithNoAdditionalRecord { .. }, - )), - .. - } => {} - _ => {} - } - } - - pub async fn run(mut self) { - if let Err(e) = tokio::spawn(async move { - loop { - select! { - event = self.swarm.select_next_some() => self.handle_event(event).await, - Some(cmd) = self.command_receiver.recv() => self.handle_command(cmd).await, - } - } - }) - .await - { - println!("fail to start background pool for network run! {e:?}"); - } - } -} - -impl AsRef> for NetworkService { - fn as_ref(&self) -> &Receiver { - &self.command_receiver - } -} diff --git a/src-tauri/src/models/project_file.rs b/src-tauri/src/models/project_file.rs deleted file mode 100644 index 9e10168b..00000000 --- a/src-tauri/src/models/project_file.rs +++ /dev/null @@ -1,59 +0,0 @@ -/* -use blend::Blend; -use semver::Version; -use serde::{Deserialize, Serialize}; -use std::{ - ops::Deref, path::{Path, PathBuf}, str::FromStr -}; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum ProjectFileError { - // #[error("Invalid file type")] - // InvalidFileType, - #[error("Unexpected error - Programmer needs to specify exact error representation")] - UnexpectedError, // should never happen. -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub struct ProjectFile> { - blender_version: Version, - path: T, -} - -impl ProjectFile { - pub fn new(src: PathBuf, version: Version) -> Result { - match Blend::from_path(&src) { - Ok(_data) => { - Ok(Self { - blender_version: version, - path: src, - }) - } - Err(_) => Err(ProjectFileError::InvalidFileType), - } - } -} - -impl AsRef for ProjectFile { - fn as_ref(&self) -> &Version { - &self.blender_version - } -} - -impl FromStr for ProjectFile { - type Err = ProjectFileError; - - fn from_str(s: &str) -> Result { - Ok(serde_json::from_str(s).map_err(|_| ProjectFileError::UnexpectedError)?) - } -} - -impl Deref for ProjectFile { - type Target = Path; - fn deref(&self) -> &Path { - &self.path - } -} - -*/ \ No newline at end of file diff --git a/src-tauri/src/models/render_info.rs b/src-tauri/src/models/render_info.rs index 3ee8d900..567c6836 100644 --- a/src-tauri/src/models/render_info.rs +++ b/src-tauri/src/models/render_info.rs @@ -1,19 +1,29 @@ -/* +use super::with_id::WithId; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use uuid::Uuid; + +pub type CreatedRenderInfoDto = WithId; +pub type NewRenderInfoDto = RenderInfo; #[derive(Debug, Serialize, Deserialize, Clone, Hash, Eq, PartialEq)] pub struct RenderInfo { + // what job this render image belongs to + pub job_id: Uuid, + + // which frame pub frame: i32, - pub path: PathBuf, + + // path to final image + pub render_path: PathBuf, } impl RenderInfo { - pub fn new(frame: i32, path: &PathBuf) -> Self { + pub fn new(job_id: Uuid, frame: i32, path: impl AsRef) -> Self { Self { + job_id, frame, - path: path.clone(), + render_path: path.as_ref().to_path_buf(), } } } -*/ \ No newline at end of file diff --git a/src-tauri/src/models/server_setting.rs b/src-tauri/src/models/server_setting.rs index 810c847d..c28b1843 100644 --- a/src-tauri/src/models/server_setting.rs +++ b/src-tauri/src/models/server_setting.rs @@ -20,7 +20,7 @@ const BLEND_DIR: &str = "BlendFiles/"; /// Server settings information that the user can load and configure for this program to operate. /// It will save the list of blender installation on the machine to avoid duplicate download and installation. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ServerSetting { /// Public directory to store all finished render image. pub render_dir: PathBuf, @@ -57,7 +57,7 @@ impl ServerSetting { fs::create_dir_all(&path).expect("Unable to create directory!"); path } - + fn get_config_path() -> PathBuf { let path = Self::get_config_dir(); path.join(SETTINGS_FILE_NAME) diff --git a/src-tauri/src/models/setting_action.rs b/src-tauri/src/models/setting_action.rs new file mode 100644 index 00000000..8120eed7 --- /dev/null +++ b/src-tauri/src/models/setting_action.rs @@ -0,0 +1,18 @@ +use futures::channel::mpsc::Sender; +use crate::models::server_setting::ServerSetting; + +#[derive(Debug)] +pub enum SettingsAction { + Get(Sender), + Update(ServerSetting), +} + +impl PartialEq for SettingsAction { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Get(..), Self::Get(..)) => true, + (Self::Update(l0), Self::Update(r0)) => l0 == r0, + _ => false, + } + } +} \ No newline at end of file diff --git a/src-tauri/src/models/task.rs b/src-tauri/src/models/task.rs deleted file mode 100644 index 998e25e4..00000000 --- a/src-tauri/src/models/task.rs +++ /dev/null @@ -1,137 +0,0 @@ -use super::job::Job; -use crate::domains::task_store::TaskError; -use blender::{ - blender::{Args, Blender}, - models::status::Status, -}; -use libp2p::PeerId; -use semver::Version; -use serde::{Deserialize, Serialize}; -use std::{ - ops::Range, - path::PathBuf, - sync::{Arc, RwLock}, -}; -use uuid::Uuid; - -/* - Task is used to send Worker individual task to work on - this can be customize to determine what and how many frames to render. - contains information about who requested the job in the first place so that the worker knows how to communicate back notification. -*/ -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Task { - /// Unique id for this task - pub id: Uuid, - - /// peer's id that sent us this task, use this to callback - peer_id: Vec, - - /// reference to the job id - pub job_id: Uuid, - - /// target blender version to use - pub blender_version: Version, - - /// generic blender file name from job's reference. - pub blend_file_name: PathBuf, - - /// Render range frame to perform the task - pub range: Range, -} - -// To better understand Task, this is something that will be save to the database and maintain a record copy for data recovery -// This act as a pending work order to fulfil when resources are available. -impl Task { - pub fn new( - peer_id: PeerId, - job_id: Uuid, - blend_file_name: PathBuf, - blender_version: Version, - range: Range, - ) -> Self { - Self { - id: Uuid::new_v4(), - peer_id: peer_id.to_bytes(), - job_id, - blend_file_name, - blender_version, - range, - } - } - - pub fn from(peer_id: PeerId, job: Job, range: Range) -> Self { - Self { - id: Uuid::new_v4(), - peer_id: peer_id.to_bytes(), - job_id: job.id, - blend_file_name: PathBuf::from(job.project_file.file_name().unwrap()), - blender_version: job.blender_version, - range, - } - } - - // this could be async? we'll see. - - /// The behaviour of this function returns the percentage of the remaining jobs in poll. - /// E.g. 102 (80%) of 120 remaining would return 96 end frames. - /// TODO: Allow other node or host to fetch end frames from this task and distribute to other requesting workers. - pub fn fetch_end_frames(&mut self, percentage: i8) -> Option> { - // Here we'll determine how many franes left, and then pass out percentage of that frames back. - let perc = percentage as f32 / i8::MAX as f32; - let end = self.range.end; - let delta = (end - self.range.start) as f32; - let trunc = (perc * (delta.powf(2.0)).sqrt()).floor() as usize; - - if trunc.le(&2) { - return None; - } - - let start = end - trunc as i32; - let range = Range { start, end }; - self.range.end = start - 1; // Update end value accordingly. - Some(range) - } - - pub fn get_peer_id(&self) -> PeerId { - PeerId::from_bytes(&self.peer_id).expect("Peer Id was posioned!") - } - - fn get_next_frame(&mut self) -> Option { - // we will use this to generate a temporary frame record on database for now. - if self.range.start < self.range.end { - let value = Some(self.range.start); - self.range.start = self.range.start + 1; - value - } else { - None - } - } - - // Invoke blender to run the job - // how do I stop this? Will this be another async container? - pub async fn run( - self, - blend_file: PathBuf, - // output is used to create local path storage to save frame path to - output: PathBuf, - // reference to the blender executable path to run this task. - blender: &Blender, - ) -> Result, TaskError> { - let args = Args::new(blend_file, output); - let arc_task = Arc::new(RwLock::new(self)).clone(); - - // TODO: How can I adjust blender jobs? - // this always puzzle me. Is this still awaited after application closed? - let receiver = blender - .render(args, move || -> Option { - let mut task = match arc_task.write() { - Ok(task) => task, - Err(_) => return None, - }; - task.get_next_frame() - }) - .await; - Ok(receiver) - } -} diff --git a/src-tauri/src/models/ticket.rs b/src-tauri/src/models/ticket.rs new file mode 100644 index 00000000..e3f585ff --- /dev/null +++ b/src-tauri/src/models/ticket.rs @@ -0,0 +1,132 @@ +use super::job::CreatedJobDto; +use crate::{ + domains::ticket_store::TicketError, + models::{job::Job, with_id::WithId}, +}; +use blender::{blend_file::BlendFile, blender::{Args, Blender, Frame}, models::event::BlenderEvent}; +use serde::{Deserialize, Serialize}; +use std::sync::mpsc::Receiver; +use std::{ + collections::HashMap, path::PathBuf +}; +use uuid::Uuid; + +pub type CreatedTicketDto = WithId; + +// pub enum TaskStatus { + // use this to describe what's going on with this task. +// } + +/* + Task is used to send Worker individual task to work on + this can be customize to determine what and how many frames to render. +*/ +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Ticket { + // status: + + /// Id used to identify the job + job_id: Uuid, + + /// This really should expand out to the required info to run the job such as blender file, version, frames, etc. + pub(crate) job: Job, + + // temp output destination - used to hold render image in temp on client machines + // this should not be visible/present for host to obtain. + temp_output: PathBuf, + + /// collection of completed render images + renders: HashMap, + + /// Render range frame to perform the task + pub(crate) start: Frame, + pub(crate) end: Frame, +} + +// To better understand Task, this is something that will be save to the database and maintain a record copy for data recovery +// This act as a pending work order to fulfill when resources are available. +impl Ticket { + // private method, less validation. + fn new(job_id: Uuid, job: Job, temp_output: PathBuf, start: i32, end: i32 ) -> Self { + Self { + job_id, + job, + temp_output, + renders: HashMap::new(), + start, + end + } + } + + pub fn from(job: CreatedJobDto, start: i32, end: i32) -> Result { + match dirs::cache_dir() { + Some(tmp) => Ok(Ticket::new(job.id, job.item, tmp, start, end)), + None => Err(TicketError::CacheError), + } + } + + pub async fn render(&mut self, blender: &Blender) -> Result, TicketError> { + let job = &self.job; + let blend_file = AsRef::::as_ref(&job); + let args = Args::new(blend_file.clone(), self.temp_output.clone(), self.start, self.end); + blender.render(args).await.map_err(TicketError::BlenderError) + } +} + +impl AsRef for Ticket { + fn as_ref(&self) -> &Uuid { + &self.job_id + } +} + +impl AsRef for Ticket { + fn as_ref(&self) -> &Job { + &self.job + } +} + +/* +#[cfg(test)] +mod test { + use super::*; + use crate::models::job::test::scaffold_job; + use uuid::Uuid; + + fn scaffold_task(start: i32, end: i32) -> Task { + let data = WithId { + id: Uuid::new_v4(), + item: scaffold_job(), + }; + Task::from(data, start, end).expect("Should have valid task") + } + + #[test] + fn fetch_end_frame_success() { + // we should run two scenario, one with actual frames, and another with limited or no frames left. + // if we tried to call with enough buffer pending, we should expect Some(value) back + // otherwise if the node is almost done and it was called, None should return. + let mut task = scaffold_task(0, 50); + let data = task.fetch_end_frames(255); + assert!(data.is_some()); + + let data = task.fetch_end_frames(5); + assert!(data.is_none()); + } + + #[test] + fn get_next_frame_success() { + // We should expect two successful result + // one result is that we should have remaining frames, so we should expect to get Some(value) + // otherwise None should return that we've completed the job. + let mut task = scaffold_task(0, 1); + let data = task.get_next_frame(); + assert!(data.is_some()); + + let data = task.get_next_frame(); + assert!(data.is_some()); + + let data = task.get_next_frame(); + assert!(data.is_none()); + } +} +*/ \ No newline at end of file diff --git a/src-tauri/src/models/with_id.rs b/src-tauri/src/models/with_id.rs new file mode 100644 index 00000000..1f397acb --- /dev/null +++ b/src-tauri/src/models/with_id.rs @@ -0,0 +1,27 @@ +use serde::Serialize; +use sqlx::prelude::*; +use uuid::Uuid; + +#[derive(Debug, Serialize, FromRow, Clone)] +pub struct WithId { + pub id: ID, + pub item: T, +} + +impl AsRef for WithId +where + T: Serialize, +{ + fn as_ref(&self) -> &Uuid { + &self.id + } +} + +impl PartialEq for WithId +where + T: Serialize, +{ + fn eq(&self, other: &Uuid) -> bool { + self.id.eq(other) + } +} diff --git a/src-tauri/src/models/worker.rs b/src-tauri/src/models/worker.rs index b768037d..b1768da9 100644 --- a/src-tauri/src/models/worker.rs +++ b/src-tauri/src/models/worker.rs @@ -1,22 +1,22 @@ use super::computer_spec::ComputerSpec; -use serde::{Deserialize, Serialize}; -use thiserror::Error; +// use crate::services::server::ServerEvent; +use libp2p::PeerId; -#[derive(Debug, Error)] -pub enum WorkerError { - #[error("Received error from database: {0}")] - Database(String), -} - -// we will use this to store data into database at some point. -#[derive(Serialize, Deserialize)] +// Treat this struct as server found on network +#[derive(Debug)] pub struct Worker { - pub machine_id: String, + pub peer_id: PeerId, pub spec: ComputerSpec, + // internally, we should at least documented the logs and entry. + // logs: Vec, } impl Worker { - pub fn new(machine_id: String, spec: ComputerSpec) -> Self { - Self { machine_id, spec } + pub fn new(peer_id: PeerId, spec: ComputerSpec) -> Self { + Self { + peer_id, + spec, + /*logs: Vec::new()*/ + } } } diff --git a/src-tauri/src/network/controller.rs b/src-tauri/src/network/controller.rs new file mode 100644 index 00000000..6ba67921 --- /dev/null +++ b/src-tauri/src/network/controller.rs @@ -0,0 +1,196 @@ +use std::{ + collections::HashSet, + path::{Path, PathBuf}, +}; + +use crate::models::behaviour::FileResponse; +use crate::models::job::JobEvent; +use crate::services::server::ServerEvent; +use crate::network::message::{Command, FileCommand, NetworkError}; +use crate::network::provider_rule::ProviderRule; +use futures::channel::oneshot::{self}; +use libp2p::{Multiaddr, PeerId}; +use libp2p_request_response::ResponseChannel; +use tokio::sync::mpsc::Sender; +use tokio::sync::mpsc::error::SendError; + +// Network Controller interfaces network service. +#[derive(Clone)] +pub struct Controller { + sender: Sender, // send net commands + pub public_id: PeerId, + pub hostname: String, +} + +impl Controller { + pub(crate) fn new(sender: Sender, peer_id: PeerId, hostname: String) -> Self { + Self { + sender, + public_id: peer_id, + hostname, + } + } + + pub(crate) async fn start_listening(&mut self, addr: Multiaddr) { + let (sender, receiver) = oneshot::channel(); + self.sender + .send(Command::StartListening { addr, sender }) + .await + .expect("Command receiver should never be dropped"); + if let Err(e) = receiver.await { + eprintln!("Fail to listen? {e:?}"); + } + } + + pub(crate) async fn subscribe(&mut self, topic: &str) -> Result<(), SendError> { + // TODO: find a better way to get around to_owned(), but for now focus on getting this application to work. + let cmd = Command::Subscribe { + topic: topic.to_owned(), + }; + self.sender.send(cmd).await + } + + pub(crate) async fn send_server_status(&self, status: ServerEvent) { + if let Err(e) = self.sender.send(Command::ServerStatus(status)).await { + eprintln!("Failed to send node status to network service: {e:?}"); + } + } + + // Not in used? + /* + pub(crate) async fn dial( + &self, + peer_id: &PeerId, + peer_addr: &Multiaddr, + ) -> Result<(), Box> { + let (sender, receiver) = oneshot::channel(); + // we thread locked here. Awaiting for dial to come back successfully, which means we're establishing connection to exchange information. + self.sender + .send(Command::Dial { + peer_id: peer_id.clone(), + peer_addr: peer_addr.clone(), + sender, + }) + .await + .expect("Should not drop"); + + // so at this point we're waiting for connection established. + if let Err(e) = receiver.await { + eprintln!("Should not error? {e:?}"); + } + Ok(()) + } + */ + + // send job event to all connected node + pub async fn send_job_event(&self, event: JobEvent) { + self.sender + .send(Command::JobStatus(event)) + .await + .expect("Command should not be dropped"); + } + + pub(crate) async fn file_service(&self, command: FileCommand) { + self.sender + .send(Command::FileService(command)) + .await + .expect("Command should not have been dropped!"); + } + + /// file_name are broadcasted with the extensions included, but not the directory it's located in. E.g. "test.blend" + // I need to use some kind of enumeration to help make this process flexible with rules.. + pub(crate) async fn start_providing( + &self, + provider: &ProviderRule, + ) -> Result<(), NetworkError> { + let cmd = match provider { + ProviderRule::Default(path_buf) => { + // TODO: remove .expect(), .to_str(), and .to_owned() + match path_buf.file_name() { + Some(file_name) => { + let keyword = file_name + .to_str() + .expect("Must be able to convert OsStr to Str!"); + + FileCommand::StartProviding(keyword.into(), path_buf.into()) + } + None => return Err(NetworkError::BadInput), + } + } + ProviderRule::Custom(keyword, path_buf) => { + FileCommand::StartProviding(keyword.to_owned(), path_buf.to_owned()) + } + }; + + if let Err(e) = self.sender.send(Command::FileService(cmd)).await { + eprintln!("How did this happen? {e:?}"); + } + Ok(()) + } + + pub async fn get_providers(&mut self, file_name: &str) -> HashSet { + let (sender, receiver) = oneshot::channel(); + let cmd = Command::FileService(FileCommand::GetProviders { + file_name: file_name.to_string(), + sender, + }); + self.sender + .send(cmd) + .await + .expect("Command receiver should not be dropped"); + receiver.await.unwrap_or(HashSet::new()) + } + + // client request file from peers. + // I feel like we should make this as fetching data from network? Some sort of stream? + pub async fn get_file_from_peers>( + &mut self, + file_name: &str, + destination: T, + ) -> Result { + let providers = self.get_providers(&file_name).await; + match providers.iter().next() { + Some(peer_id) => { + self.request_file(peer_id, file_name, destination.as_ref()) + .await + } + None => Err(NetworkError::NoPeerProviderFound), + } + } + + async fn request_file( + &mut self, + peer_id: &PeerId, + file_name: &str, + destination: &Path, + ) -> Result { + let (sender, receiver) = oneshot::channel(); + let cmd = Command::FileService(FileCommand::RequestFile { + peer_id: *peer_id, + file_name: file_name.into(), + sender, + }); + self.sender + .send(cmd) + .await + .expect("Command should not be dropped"); + let content = receiver + .await + .expect("Should not be closed?") + .or_else(|e| Err(NetworkError::UnableToSave(e.to_string())))?; + + let file_path = destination.join(file_name); + match async_std::fs::write(file_path.clone(), content).await { + Ok(_) => Ok(file_path), + Err(e) => Err(NetworkError::UnableToSave(e.to_string())), + } + } + + // TODO: Come back to this one and see how this one gets invoked. + pub(crate) async fn respond_file(&self, file: Vec, channel: ResponseChannel) { + let cmd = Command::FileService(FileCommand::RespondFile { file, channel }); + if let Err(e) = self.sender.send(cmd).await { + println!("Command should not be dropped: {e:?}"); + } + } +} diff --git a/src-tauri/src/network/message.rs b/src-tauri/src/network/message.rs new file mode 100644 index 00000000..f4e5fd49 --- /dev/null +++ b/src-tauri/src/network/message.rs @@ -0,0 +1,118 @@ +use futures::channel::oneshot::{self}; +use libp2p::{Multiaddr, PeerId}; +use libp2p_request_response::{OutboundRequestId, ResponseChannel}; +use std::path::PathBuf; +use std::{collections::HashSet, error::Error}; +use thiserror::Error; + +use crate::models::behaviour::FileResponse; +use crate::models::job::JobEvent; +use crate::services::server::ServerEvent; + +#[derive(Debug, Error)] +pub enum NetworkError { + #[error("Unable to listen: {0}")] + UnableToListen(String), + #[error("Not Connected")] + NotConnected, + #[error("Invalid connection")] + Invalid, + #[error("Bad Input")] + BadInput, + #[error("Send Error: {0}")] + SendError(String), + #[error("No peers on network have this file available to download!")] + NoPeerProviderFound, + #[error("Unable to save download file: {0}")] + UnableToSave(String), + #[error("Timeout, unable to connect peer")] + Timeout, +} + +pub type KeywordSearch = String; + +// to make things simple, we'll create a file service command to handle file service. +#[derive(Debug)] +pub enum FileCommand { + StartProviding(KeywordSearch, PathBuf), // update kademlia service to provide a new file. Must have a file name and a extension! Cannot be a directory! + StopProviding(KeywordSearch), // update kademlia service to stop providing the file. + GetProviders { + file_name: String, + sender: oneshot::Sender>, + }, + RequestFile { + peer_id: PeerId, + file_name: String, + sender: oneshot::Sender, Box>>, + }, + RespondFile { + file: Vec, + channel: ResponseChannel, + }, + RequestFilePath { + keyword: KeywordSearch, + sender: oneshot::Sender>, + }, +} + +// Send commands to network. +#[derive(Debug)] +pub enum Command { + /* + Dial { + peer_id: PeerId, + peer_addr: Multiaddr, + sender: oneshot::Sender>>, + }, + */ + Subscribe { + topic: String, + }, + // TODO: figure out a way to get around the Box traits! + StartListening { + addr: Multiaddr, + sender: oneshot::Sender>>, + }, + // TODO: Find a way to get around the string type! This expects a copy! + StartProviding { + file_name: String, + sender: oneshot::Sender<()>, + }, + + GetProviders { + file_name: String, + sender: oneshot::Sender>, + }, + RequestFile { + file_name: String, + peer: PeerId, + sender: oneshot::Sender, Box>>, + }, + RespondFile { + // what is file? + file: Vec, + channel: ResponseChannel, + }, + + // TODO: More documentation to explain below + // These are signal to use to send out message and forget. + // May expect a respoonse back potentially requesting this node to work new jobs. + ServerStatus(ServerEvent), // broadcast node activity changed + // don't think I need this anymore? + // JobStatus(JobEvent), + FileService(FileCommand), +} + +// Received network events. +#[derive(Debug)] +pub enum Event { + Discovered(PeerId, Multiaddr), + InboundRequest { + request: String, + channel: ResponseChannel, + }, + + ServerStatus(ServerEvent), + JobUpdate(JobEvent), + ReceivedFileData(OutboundRequestId, Vec), +} diff --git a/src-tauri/src/network/mod.rs b/src-tauri/src/network/mod.rs new file mode 100644 index 00000000..a125cade --- /dev/null +++ b/src-tauri/src/network/mod.rs @@ -0,0 +1,127 @@ +use crate::{ + constant::TRANSFER, + models::behaviour::BlendFarmBehaviour, + network::{ + controller::Controller, + message::{Command, Event, NetworkError}, + service::Service, + }, +}; +use libp2p::{StreamProtocol, SwarmBuilder, gossipsub, identity, kad, mdns, noise, tcp, yamux}; +use libp2p_request_response::ProtocolSupport; +use machine_info::Machine; +use std::{/*hash::DefaultHasher,*/ time::Duration}; +use tokio::io; +use tokio::sync::mpsc::{self, Receiver}; +pub mod controller; +pub mod message; +pub(crate) mod provider_rule; +pub mod service; + +// type is locally contained +pub type PeerIdString = String; + +// the tuples return two objects +// Network Controller to interface network service +// Receiver receive network events +pub async fn new( + secret_key_seed: Option, +) -> Result<(Controller, Receiver, Service), NetworkError> { + // wonder why we have a connection timeout of 60 seconds? Why not uint::MAX? + + let duration = Duration::from_secs(60); + // is there a reason for the secret key seed? + let id_keys = match secret_key_seed { + Some(seed) => { + let mut bytes = [0u8; 32]; + bytes[0] = seed; + identity::Keypair::ed25519_from_bytes(bytes).unwrap() + } + None => identity::Keypair::generate_ed25519(), + }; + + let mut swarm = SwarmBuilder::with_existing_identity(id_keys) + // let mut swarm = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + ) + .expect("Should be able to build with tcp configuration?") + .with_quic() + .with_behaviour(|key| { + // seems like we need to content-address message. We'll use the hash of the message as the ID. + // let message_id_fn = |message: &gossipsub::Message| { + // let mut s = DefaultHasher::new(); + // message.data.hash(&mut s); + // gossipsub::MessageId::from(s.finish().to_string()) + // }; + + let gossipsub_config = gossipsub::ConfigBuilder::default() + .heartbeat_interval(Duration::from_secs(10)) + .validation_mode(gossipsub::ValidationMode::Strict) + // .message_id_fn(message_id_fn) + .build() + .map_err(|msg| io::Error::new(io::ErrorKind::Other, msg))?; + + // p2p communication + let gossipsub = gossipsub::Behaviour::new( + gossipsub::MessageAuthenticity::Signed(key.clone()), + gossipsub_config, + ) + .expect("Fail to create gossipsub behaviour"); + + // network discovery usage + // TODO: replace expect with error handling + let mdns = + mdns::tokio::Behaviour::new(mdns::Config::default(), key.public().to_peer_id()) + .expect("Fail to create mdns behaviour!"); + + // Used to provide file provision list + let kad = kad::Behaviour::new( + key.public().to_peer_id(), + kad::store::MemoryStore::new(key.public().to_peer_id()), + ); + + let rr_config = libp2p_request_response::Config::default(); + // Learn more about this and see if we need the transfer keyword of some sort? + let protocol = [(StreamProtocol::new(TRANSFER), ProtocolSupport::Full)]; + let request_response = libp2p_request_response::Behaviour::new(protocol, rr_config); + + Ok(BlendFarmBehaviour { + request_response, + gossipsub, + mdns, + kademlia: kad, + }) + }) + // TODO remove/handle expect() + .expect("Expect to build behaviour") + .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(duration)) + .build(); + + // set the kad as server mode + swarm + .behaviour_mut() + .kademlia + .set_mode(Some(kad::Mode::Server)); + + // the command sender is used for outside method to send message commands to network queue + let (sender, receiver) = mpsc::channel::(32); + + // the event sender is used to handle incoming network message. E.g. RunJob + let (event_sender, event_receiver) = mpsc::channel::(32); + + let public_id = swarm.local_peer_id().clone(); + + let controller = Controller::new(sender, public_id, Machine::new().system_info().hostname); + + let service = Service::new( + swarm, + receiver, + event_sender, // Here is where network service communicates out. + ); + + Ok((controller, event_receiver, service)) +} diff --git a/src-tauri/src/network/provider_rule.rs b/src-tauri/src/network/provider_rule.rs new file mode 100644 index 00000000..deb2fe7b --- /dev/null +++ b/src-tauri/src/network/provider_rule.rs @@ -0,0 +1,19 @@ +use crate::network::message::KeywordSearch; +use std::{ffi::OsStr, path::PathBuf}; + +#[allow(dead_code)] +pub enum ProviderRule { + // Use "file name.ext", Extracted from PathBuf. + Default(PathBuf), + // Custom keyword search for specific PathBuf. + Custom(KeywordSearch, PathBuf), +} + +impl ProviderRule { + pub fn get_file_name(&self) -> Option<&OsStr> { + match self { + ProviderRule::Default(path) => path.file_name(), + ProviderRule::Custom(_, path_buf) => path_buf.file_name(), + } + } +} diff --git a/src-tauri/src/network/service.rs b/src-tauri/src/network/service.rs new file mode 100644 index 00000000..5b4776a6 --- /dev/null +++ b/src-tauri/src/network/service.rs @@ -0,0 +1,629 @@ +use crate::constant::{JOB_TOPIC, NODE_TOPIC}; +use crate::models::behaviour::{BlendFarmBehaviourEvent, FileRequest, FileResponse}; +use crate::models::job::JobEvent; +use crate::network::message::FileCommand; +use crate::services::server::ServerEvent; +use crate::{ + models::behaviour::BlendFarmBehaviour, + network::message::{Command, Event}, +}; +use futures::StreamExt; +use futures::channel::oneshot; +use libp2p::gossipsub::{self, IdentTopic}; +use libp2p::kad::RecordKey; +use libp2p::mdns; +use libp2p::multiaddr::Protocol; +use libp2p::swarm::SwarmEvent; +use libp2p::{ + Multiaddr, PeerId, Swarm, + kad::{self, QueryId}, +}; +use libp2p_request_response::OutboundRequestId; +use std::collections::{HashMap, HashSet}; +use std::error::Error; +use std::path::PathBuf; +use tokio::select; +use tokio::sync::mpsc::{Receiver, Sender}; + +// Network service module to handle invocation commands to send to network service, +// as well as handling network event from other peers +pub struct Service { + // swarm behaviour - interface to the network + swarm: Swarm, + + // receive Network command + receiver: Receiver, + + // Send Network event to subscribers. + sender: Sender, + + // connection established + dialers: HashMap, + + pending_start_providing: HashMap>, + pending_dial: HashMap>>>, + providing_files: HashMap, + pending_get_providers: HashMap>>, + pending_request_file: + HashMap, Box>>>, + + pending_job_event: Vec, +} + +// network service will be used to handle and receive network signal. It will also transmit network package over lan +impl Service { + pub fn new( + swarm: Swarm, + receiver: Receiver, + sender: Sender, + ) -> Self { + Self { + swarm, + receiver, + sender, + dialers: Default::default(), + pending_start_providing: Default::default(), + pending_dial: Default::default(), + providing_files: Default::default(), + pending_get_providers: Default::default(), + pending_request_file: Default::default(), + pending_job_event: Default::default(), + } + } + + /* + From my understanding about this method implementation: broadcast all potential files and sponsor what's available. + This methodology will change: The host will ask the client for task information that matches Job ID. + This client will reply back to the host with list of matching task(s) information. + + I need to setup a network diagram to make this network layer protocol clear and understand, + as well as easy to debug, test, and identify potential issues. + + From the host side. the host will broadcast asking for job updates. + This update will include job id. + + On the client side, the client will receive the notification from the host, + and check the database to see if the job id exist. + + if it does exist, then the client will broadcast list of completed images. + The host will receive this list and compare to the host machine to see if they have the image + + If the host does not have the image, it will initiate a file transfer between the host and the client machine + In this case, we should not have to make all of the files available, but instead make the target image + available for the host to transfer over the network protocol. + + This is recognized as a tcp handshake connection, asking for the image from the node + and the node will send the image via channel request. + */ + + // here we will deviate handling the file service command. + async fn process_file_service(&mut self, cmd: FileCommand) { + match cmd { + FileCommand::RequestFile { + peer_id, + file_name, + sender, + } => { + let request_id = self + .swarm + .behaviour_mut() + .request_response + .send_request(&peer_id, FileRequest(file_name.into())); + self.pending_request_file.insert(request_id, sender); + } + FileCommand::RespondFile { file, channel } => { + // somehow the send_response errored out? How come? + // Seems like this function got timed out? + if let Err(e) = self + .swarm + .behaviour_mut() + .request_response + .send_response(channel, FileResponse(file)) + { + // why am I'm getting error message here? + eprintln!("Error received on sending response! {e:?}"); + } + } + FileCommand::GetProviders { file_name, sender } => { + let key = file_name.into_bytes().into(); + let query_id = self.swarm.behaviour_mut().kademlia.get_providers(key); + self.pending_get_providers.insert(query_id, sender); + } + FileCommand::StartProviding(keyword, file_path) => { + let key = keyword.clone().into_bytes().into(); + // could we make use of this query ID? + let _query_id = self + .swarm + .behaviour_mut() + .kademlia + .start_providing(key) + .expect("No store error."); + self.providing_files.insert(keyword, file_path); + } + FileCommand::StopProviding(keyword) => { + let key = RecordKey::new(&keyword.as_bytes()); + self.swarm.behaviour_mut().kademlia.stop_providing(&key); + self.providing_files.remove(&keyword); + } + FileCommand::RequestFilePath { keyword, sender } => { + let result = self + .providing_files + .get(&keyword) + .and_then(|f| Some(f.to_owned())); + println!("{keyword:?} | {result:?}"); + sender.send(result).expect("Receiver should not be dropped"); + } + }; + } + + // TODO: Will need to return Result... For now let's keep it as-is. + async fn send_job_status(&mut self, event: &JobEvent) { + let data = serde_json::to_string(&event).unwrap(); + let topic = IdentTopic::new(JOB_TOPIC); + // we should wait until we successfully subscribed to the various topics filter. + // The only reason why I'm getting failed to send job message is because we are not subscribed to the topic yet. + match self.swarm.behaviour_mut().gossipsub.publish(topic, data) { + // TODO: Print log verbosity + Ok(_) => println!("Job Status Sent!\n{event:?}"), + Err(e) => eprintln!("Fail to send job message! {e:?}"), + }; + } + + // send command + // Receive commands from foreign invocation. + async fn handle_command(&mut self, cmd: Command) { + // handle the commands via the services implementation given limited power for the network services. + match cmd { + Command::Subscribe { topic } => { + let identity = IdentTopic::new(topic); + if let Err(e) = self.swarm.behaviour_mut().gossipsub.subscribe(&identity) { + eprintln!("Fail to subscribe! {e:}"); + } + } + Command::StartListening { addr, sender } => { + let _result = match self.swarm.listen_on(addr) { + Err(e) => match e.source() { + Some(err) => Err(Box::new(err.to_string())), + None => Ok(()), + }, + _ => Ok(()), + }; + // TODO, figure out how to get this situation straighten? Why + // sender.send(result); + if let Err(e) = sender.send(Ok(())) { + eprintln!("Fail to send! {e:?}"); + } + } + + /* + Command::Dial { + peer_id, + peer_addr, + sender, + } => { + if let hash_map::Entry::Vacant(e) = self.pending_dial.entry(peer_id) { + self.swarm + .behaviour_mut() + .kademlia + .add_address(&peer_id, peer_addr.clone()); + + // TODO: give me a reason why we need to dial? + match self.swarm.dial(peer_addr.with(Protocol::P2p(peer_id))) { + Ok(()) => { + e.insert(sender); + } + Err(e) => { + sender.send(Err(Box::new(e))).expect("Should not drop"); + } + } + } else { + eprintln!("Already dialing the peer!"); + } + } + */ + // use this to advertise files. On app startup we should broadcast blender apps as well. + Command::StartProviding { file_name, sender } => { + // TODO: Find a way to get around expect()! + let query_id = self + .swarm + .behaviour_mut() + .kademlia + .start_providing(file_name.into_bytes().into()) + .expect("No store value"); + self.pending_start_providing.insert(query_id, sender); + } + + Command::GetProviders { file_name, sender } => { + let query_id = self + .swarm + .behaviour_mut() + .kademlia + .get_providers(file_name.into_bytes().into()); + self.pending_get_providers.insert(query_id, sender); + } + Command::RequestFile { + file_name, + peer, + sender, + } => { + let request_id = self + .swarm + .behaviour_mut() + .request_response + .send_request(&peer, FileRequest(file_name)); + self.pending_request_file.insert(request_id, sender); + } + Command::RespondFile { file, channel } => { + self.swarm + .behaviour_mut() + .request_response + .send_response(channel, FileResponse(file)) + .expect("Connection to peer should be still open"); + } + Command::FileService(service) => self.process_file_service(service).await, + + // received job status. invoke commands + // TODO: we should only send command if we are subscribed. + Command::JobStatus(event) => { + // I will have to make a queue until we have subscribers. + // I want to send a message only if we have active subscribers. + // which means I need to create my own list of peers I think may be listening on the network + // convert data into json format. + // The foreign request is asking for the Job Status -> Reply back to the user directly. + if self.dialers.capacity().gt(&0) { + &self.send_job_status(&event); + } else { + // TODO: impl Arc>> + &self.pending_job_event.push(event); + } + } + Command::ServerStatus(status) => { + // we want to send this info across broadcast network. We do not care who is listening the network. Only the fact that we want our hosts to keep notify for availability. + let data = serde_json::to_string(&status).unwrap(); + let topic = IdentTopic::new(NODE_TOPIC); + if let Err(e) = self.swarm.behaviour_mut().gossipsub.publish(topic, data) { + eprintln!("Fail to publish gossip message: {e:?}"); + } + } + } + } + + // This method is invoked by network event. + // This is under RequestResponse + async fn process_response_event( + &mut self, + event: libp2p_request_response::Event, + ) { + match event { + libp2p_request_response::Event::Message { message, .. } => match message { + libp2p_request_response::Message::Request { + request, channel, .. + } => { + self.sender + .send(Event::InboundRequest { + request: request.0, + channel, + }) + .await + .expect("Event receiver should not be dropped!"); + } + libp2p_request_response::Message::Response { + request_id, + response, + } => { + let _ = self + .pending_request_file + .remove(&request_id) + .expect("Request to still be pending") + .send(Ok(response.0)); + } + }, + libp2p_request_response::Event::OutboundFailure { + request_id, error, .. + } => { + let _ = self + .pending_request_file + .remove(&request_id) + .expect("Request to still be pending") + .send(Err(Box::new(error))); + } + libp2p_request_response::Event::ResponseSent { .. } => {} + _ => {} + } + } + + async fn process_mdns_event(&mut self, event: mdns::Event) { + match event { + mdns::Event::Discovered(peers) => { + for (peer_id, address) in peers { + // println!("Discovered [{peer_id:?}] {address:?}"); + + // create a discovery notification to the subscribers + let event = Event::Discovered(peer_id, address.clone()); + // if this errors out, we should gracefully hang up? + if let Err(e) = self.sender.send(event).await { + eprintln!("sender should not drop! {e:?}"); + } + + // if I have already discovered this address, then I need to skip it. Otherwise I will produce garbage log input for duplicated peer id already exist. + // it seems that I do need to explicitly add the peers to the list. + // self.swarm + // .behaviour_mut() + // .gossipsub + // .add_explicit_peer(&peer_id); + + // // add the discover node to kademlia list. + // why would I want to do this? + self.swarm + .behaviour_mut() + .kademlia + .add_address(&peer_id, address.clone()); + } + } + mdns::Event::Expired(..) => { + // for (peer_id, ..) in peers { + // self.swarm + // .behaviour_mut() + // .gossipsub + // .remove_explicit_peer(&peer_id); + // } + } + }; + } + + async fn process_gossip_event(&mut self, event: gossipsub::Event) { + match event { + // what is propagation source? can we use this somehow? + gossipsub::Event::Message { message, .. } => match message.topic.as_str() { + // if the topic is JOB related, assume data as JobEvent + JOB_TOPIC => match serde_json::from_slice::(&message.data) { + Ok(job_event) => { + if let Err(e) = self.sender.send(Event::JobUpdate(job_event)).await { + eprintln!("Something failed? {e:?}"); + } + } + Err(e) => { + eprintln!("Fail to parse Job topic data! {e:?}"); + } + }, + // Node based event awareness + NODE_TOPIC => match serde_json::from_slice::(&message.data) { + Ok(node_event) => { + if let Err(e) = self.sender.send(Event::ServerStatus(node_event)).await { + eprintln!("Something failed? {e:?}"); + } + } + Err(e) => eprintln!("fail to parse Node topic data! {e:?}"), + }, + + // Garbage collector - Treat this as a grain of salt. Do not execute any data from this scope + // should only be used to display logs and info, things for us to identify unusual activity going on outside our domain specification. + _ => { + // I received Mac.lan from message.topic? + let topic = message.topic.as_str(); + eprintln!("Intercepted unhandled signal here: {topic}"); + } + }, + // TODO: Don't think I need this yet? suppressing this for now + gossipsub::Event::Subscribed { .. /*peer_id, topic*/ } => { + // what are the peer_id and topic? + // Maybe it's the user who joined the network, we can send a RequestTask if we're idle? + + // let event = Event::JobUpdate(()); + // if let Err(e) = self.sender.send(event).await { + // eprintln!("Fail to send subscribed notification! {e:?}"); + // } + } + // I should be logging info from other event from gossip... wonder what they got to say? + // TODO: Log and verify if we need to handle other gossip events. + any => { + println!("[Unhandled Gossipsub]{any:?}"); + } + } + } + + // async fn process_outbound_query(&mut ) + + // Handle kademila events (Used for file sharing) + // can we use this same DHT to make node spec publicly available? + async fn process_kademlia_event(&mut self, kad_event: kad::Event) { + match kad_event { + kad::Event::OutboundQueryProgressed { + id: query_id, + result: query_result, + .. + } => { + match query_result { + kad::QueryResult::StartProviding(..) => { + let sender: oneshot::Sender<()> = self + .pending_start_providing + .remove(&query_id) + .expect("Completed query to be previously pending."); + let _ = sender.send(()); + } + kad::QueryResult::GetProviders(Ok(kad::GetProvidersOk::FoundProviders { + providers, + .. + })) => { + if let Some(sender) = self.pending_get_providers.remove(&query_id) { + sender.send(providers).expect("Receiver not to be dropped"); + + if let Some(mut node) = + self.swarm.behaviour_mut().kademlia.query_mut(&query_id) + { + node.finish(); + } + } + } + kad::QueryResult::GetProviders(Ok( + kad::GetProvidersOk::FinishedWithNoAdditionalRecord { .. }, + )) => { + // yeah this looks wrong? + if let Some(sender) = self.pending_get_providers.remove(&query_id) { + sender + .send(HashSet::new()) + .expect("Sender not to be dropped"); + } + + if let Some(mut node) = + self.swarm.behaviour_mut().kademlia.query_mut(&query_id) + { + node.finish(); + } + // This piece of code means that there's nobody advertising this on the network? + // what was suppose to happen here? + // TODO: I am once again stopped here. This message appeared from the CLI side. Not the host. + + // let outbound_request_id = id; + // let event = Event::PendingRequestFiled(outbound_request_id, None); + // self.sender.send(event).await; + } + kad::QueryResult::PutRecord(result) => match result { + Ok(value) => println!("Successfully append the record! {value:?}"), + Err(e) => eprintln!("Error putting record in! {e:?}"), + }, + // suppressed + _ => {} + } + } + + kad::Event::InboundRequest { .. } => {} // suppressed + kad::Event::RoutingUpdated { .. } => {} // suppressed + // TODO: Find out what cause this to happen and see if we need to handle anything for this invocation exception + kad::Event::UnroutablePeer { peer } => { + eprintln!("Unroutable Peer? {peer}"); + } // suppressed + _ => { + // oh mah gawd. What am I'm suppose to do here? + eprintln!("Unhandled Kademila event: {kad_event:?}"); + } + } + } + + async fn handle_swarm_event(&mut self, event: SwarmEvent) { + match event { + SwarmEvent::Behaviour(behaviour) => match behaviour { + // RequestResponse? + BlendFarmBehaviourEvent::RequestResponse(event) => { + self.process_response_event(event).await; + } + // Gossipsub used to spread message across + BlendFarmBehaviourEvent::Gossipsub(event) => { + self.process_gossip_event(event).await; + } + // mdns used to identify other computer on the network + BlendFarmBehaviourEvent::Mdns(event) => { + self.process_mdns_event(event).await; + } + // Kademlia for DHT services + BlendFarmBehaviourEvent::Kademlia(event) => { + self.process_kademlia_event(event).await; + } + }, + // Another swarm established to you. + SwarmEvent::ConnectionEstablished { + peer_id, endpoint, .. + } => { + // TODO: Could we stream io? + // TODO: Toggle verbosity mode? + // println!("Connection Established: {peer_id:?}\n{endpoint:?}"); + if endpoint.is_dialer() { + if let Some(sender) = self.pending_dial.remove(&peer_id) { + // Eventually we can remove this. I don't think this is necessary anymore? + // register active session. peer_id and remote address stored. + self.dialers + .entry(peer_id) + .and_modify(|f| *f = endpoint.get_remote_address().clone()); + + // TODO: Where are we sending Ok(()) to? + let _ = sender.send(Ok(())); + } + } + } + // why does it report I/O error? What does it mean closed by peer? + // This was called when client starts while manager is running. "Connection error: I/O error: closed by peer: 0" + // Lost connection to peer_id + SwarmEvent::ConnectionClosed { peer_id, cause, .. } => { + let reason = cause.and_then(|f| Some(f.to_string())); + + // Are we using ServerEvent correctly? + let node = ServerEvent::Disconnected { + peer_id: peer_id.to_base58(), + reason, + }; + let event = Event::ServerStatus(node); + if let Err(e) = self.sender.send(event).await { + eprintln!("Fail to send event on connection closed! {e:?}"); + } + } + SwarmEvent::OutgoingConnectionError { + peer_id: Some(peer_id), + error, + .. + } => { + if let Some(sender) = self.pending_dial.remove(&peer_id) { + let _ = sender.send(Err(Box::new(error))); + } + } + // TODO: Figure out what these events are, and see if they're any use for us to play with or delete them. Unnecessary comment codeblocks + // SwarmEvent::ListenerClosed { .. } => todo!(), + // SwarmEvent::ListenerError { listener_id, error } => todo!(), + + // FEATURE: Display verbose info using argument switch + /* #region vv verbose events vv */ + SwarmEvent::OutgoingConnectionError { peer_id: None, .. } => {} + + SwarmEvent::NewListenAddr { address, .. } => { + // println!("[New Listener Address]: {address}"); + let local_peer_id = *self.swarm.local_peer_id(); + eprintln!( + "Listening @ {:?}", + address.with(Protocol::P2p(local_peer_id)) + ); + } + + SwarmEvent::Dialing { .. } => {} + + SwarmEvent::IncomingConnection { + connection_id, + local_addr, + send_back_addr, + } => { + // Incoming connection? How do I accept? + eprintln!("Incoming connection: {connection_id} | {local_addr} | {send_back_addr}"); + + // I'm assuming this is reply from dial? + // what does it mean to have incoming connection here? + // self.dialers.entry() + } // Suppressing logs + + // Suppressing logs + SwarmEvent::NewExternalAddrOfPeer { .. } => {} + SwarmEvent::IncomingConnectionError { .. } => {} // I recognize this and do want to display result below. + SwarmEvent::ExpiredListenAddr { .. } => {} + + /* #endregion ^^eof ignore^^ */ + // Must fully exhaust all condition types as possible! + // Add to the ignore list with description why we're suppressing logs. They must be visible under verbose mode. + e => panic!("{e:?}"), + }; + } + + // run the network loops + pub(crate) async fn run(mut self) { + loop { + select! { + event = self.swarm.select_next_some() => self.handle_swarm_event(event).await, + pending_command = self.receiver.recv() => match pending_command { + Some(command) => self.handle_command(command).await, + None => return, + }, + } + } + } +} + +#[cfg(test)] +pub mod test { + // TODO: perform some service test. How can I get the service up and running for this? +} diff --git a/src-tauri/src/routes/index.rs b/src-tauri/src/routes/index.rs new file mode 100644 index 00000000..2f214a9a --- /dev/null +++ b/src-tauri/src/routes/index.rs @@ -0,0 +1,66 @@ +use maud::{PreEscaped, html}; +use tauri::{State, command}; +use tokio::sync::Mutex; +use crate::constant::WORKPLACE; +use crate::models::app_state::AppState; +use crate::routes::job::{cmd_list_jobs, cmd_fetch_job, render_list_job, render_job_detail_page}; + +// separate this? +#[command(async)] +pub async fn index(state: State<'_,Mutex>) -> Result { + // Design to load content and page for the index. + let mut app_state = state.lock().await; + let jobs = cmd_list_jobs(&mut app_state).await; + let list_job_render = render_list_job(&jobs); + + let job_detail = match &jobs { + Some(job_list) => { + match job_list.first() { + Some(job) => cmd_fetch_job(&mut app_state, job.id.clone() ).await, + None => None + } + }, + None => None + }; + let front_page_render = render_job_detail_page(&job_detail); + + Ok(html! ( + div { + div class="sidebar" { + nav { + ul class="nav-menu-items" { + + // li key="manager" class="nav-bar" tauri-invoke="remote_render_page" hx-target=(format!("#{WORKPLACE}")) { + // span { "Remote Render" } + // }; + + li key="setting" class="nav-bar" tauri-invoke="setting_page" hx-target=(format!("#{WORKPLACE}")) { + span { "Setting" } + }; + }; + }; + div { + h3 { "Jobs" } + + button tauri-invoke="open_dialog_for_blend_file" hx-target="body" hx-swap="beforeend" { + "Import" + }; + + div class="group" id="joblist" { + (PreEscaped(list_job_render)); + }; + } + + // div { + // h2 { "Computer Nodes" }; + // // hx-trigger="every 10s" - omitting this as this was spamming console log + // div class="group" id="workers" tauri-invoke="list_workers" hx-target="this" {}; + // }; + }; + + } + main id=(WORKPLACE) { + (PreEscaped(front_page_render)) + }; + ).0) +} \ No newline at end of file diff --git a/src-tauri/src/routes/job.rs b/src-tauri/src/routes/job.rs index a0392107..c45975b8 100644 --- a/src-tauri/src/routes/job.rs +++ b/src-tauri/src/routes/job.rs @@ -1,19 +1,141 @@ -use blender::models::mode::Mode; -use maud::html; +use crate::constant::WORKPLACE; +use crate::domains::job_store::JobError; +use crate::models::job::{CreatedJobDto, Output}; +use crate::models::{app_state::AppState, job::{Job, JobAction}}; +use crate::services::tauri_app::UiCommand; +use blender::blend_file::BlendFile; +use blender::models::mode::RenderMode; +use futures::channel::mpsc; +use futures::{SinkExt, StreamExt}; +use maud::{html, PreEscaped}; use semver::Version; use serde_json::json; -use std::path::PathBuf; -use std::{ops::Range, str::FromStr}; -use tauri::{command, State}; +use std::{path::PathBuf, str::FromStr}; +use tauri::{State, command}; use tokio::sync::Mutex; use uuid::Uuid; -use crate::{ - models::{app_state::AppState, job::Job}, - services::tauri_app::UiCommand, -}; +/* + private method to call the function and return the objects, but not the actual renders. +*/ +async fn cmd_create_job(state: &mut AppState, job: Job) -> Result { + let (sender, mut receiver) = mpsc::channel(1); + let add = UiCommand::Job(JobAction::Create(job, sender)); + state + .invoke + .send(add) + .await.map_err(|e| JobError::Send(e.to_string()))?; -use super::remote_render::remote_render_page; + receiver.select_next_some().await +} + +/// used to send command to backend service to fetch for the job list. +pub(crate) async fn cmd_list_jobs(state: &mut AppState) -> Option> { + let (sender, mut receiver) = mpsc::channel(0); + let cmd = UiCommand::Job(JobAction::All(sender)); + if let Err(e) = state.invoke.send(cmd).await { + eprintln!("Fail to send command to server! {e:?}"); + return None; + } + receiver.select_next_some().await +} + +/// command to fetch the job from backend service. +pub(crate) async fn cmd_fetch_job(state: &mut AppState, job_id: Uuid) -> Option { + let (sender, mut receiver) = mpsc::channel(0); + let cmd = UiCommand::Job(JobAction::Find(job_id, sender)); + if let Err(e) = state.invoke.send(cmd).await { + eprintln!("Fail to send job action: {e:?}"); + return None + }; + receiver.select_next_some().await +} + +/// Used to render the job list on teh side of the app. +pub(crate) fn render_list_job(collection: &Option>) -> String { + match collection { + Some(list) => { + html! { + @for job in list { + div { + table { + tbody { + tr tauri-invoke="get_job_detail" hx-vals=(json!({"jobId":job.id.to_string()})) hx-target={"#" (WORKPLACE) } { + td style="width:100%" { + (job.item.get_file_name_expected().to_string_lossy()) + }; + }; + }; + }; + }; + }; + } + } + None => { + html! { + div { + "No job found!" + } + } + } + }.0 +} + +/// Render the full job description and detail page. +pub(crate) fn render_job_detail_page(job: &Option) -> String { + match job { + Some(job) => { + let result = fetch_img_result(&job.item.as_ref()); + + // TODO: it would be nice to provide ffmpeg gif result of the completed render image. + // Something to add for immediate preview and feedback from render result + // this is to fetch the render collection + // if let Some(imgs) = result { + // let preview = fetch_img_preview(&job.item.output, &imgs); + // } + + let project_file = AsRef::::as_ref(&job.item).to_path(); + let output = AsRef::::as_ref(&job.item); + let version = AsRef::::as_ref(&job.item); + + html!( + div class="content" { + h2 { "Job Detail" }; + + button tauri-invoke="open_dir" hx-vals=(json!({"path": project_file.to_string_lossy()})) { ( project_file.to_string_lossy() ) }; + + div { ( output.to_str().unwrap() ) }; + + div { ( version.to_string() ) }; + + button tauri-invoke="delete_job" hx-vals=(json!({"jobId":job.id})) hx-target="#workplace" { "Delete Job" }; + + p; + + @if let Some(list) = result { + @for img in list { + tr { + td { + img width="120px" src=(convert_file_src(&img)); + } + } + } + } + @else { + div { + "No image found in output directory..." + } + } + }; + ) + } + None => html!( + div { + p { "Job do not exist.. How did you get here?" }; + }; + ), + }.0 +} // input values are always string type. I need to validate input on backend instead of front end. // return invalidation if the value are not accepted. @@ -26,107 +148,247 @@ pub async fn create_job( path: PathBuf, output: PathBuf, ) -> Result { - // first thing first, parse the string into number - let start = start.parse::().map_err(|e| e.to_string())?; - let end = end.parse::().map_err(|e| e.to_string())?; - // stop if the parse fail to parse. - - let mode = Mode::Animation(Range { start, end }); - let job = Job::from(path, output, version, mode); - let app_state = state.lock().await; - let mut jobs = app_state.job_db.write().await; - - // use this to send the job over to database instead of command to network directly. - // We're splitting this apart to rely on database collection instead of forcing to send command over. - if let Err(e) = jobs.add_job(job.clone()).await { - eprintln!("{:?}", e); - } - - // send job to server - if let Err(e) = app_state - .to_network - .send(UiCommand::StartJob(job.clone())) - .await - { - eprintln!("Fail to send command to the server! \n{e:?}"); - } + let mode = RenderMode::try_new(&start, &end).map_err(|e| e.to_string())?; + let job = Job::from(mode, path, version, output).map_err(|e| e.to_string())?; + let mut app_state = state.lock().await; + let job_created = cmd_create_job(&mut app_state, job).await.map_err(|e| e.to_string())?; + let list = cmd_list_jobs(&mut app_state).await; - remote_render_page().await + // TODO: Utilize hx-swap-oob to update the list, then we'll update the portal to display selected job. + let list = render_list_job(&list); + let detail = render_job_detail_page(&Some(job_created)); + + Ok(html!( + div hx-target={ "#" (WORKPLACE) }{ + (PreEscaped(detail)) + } + div id="joblist" hx-swap-oob="true" { + (PreEscaped(list)) + } + ) + .0) } #[command(async)] -pub async fn list_jobs(state: State<'_, Mutex>) -> Result { - let server = state.lock().await; - let jobs = server.job_db.read().await; - let job_list = jobs.list_all().await.unwrap(); - - Ok(html! { - @for job in job_list { - div { - table { - tbody { - tr tauri-invoke="get_job" hx-vals=(json!({"jobId":job.id.to_string()})) hx-target="#detail" { - td style="width:100%" { - (job.get_file_name()) - }; - }; - }; - }; - }; - }; +pub async fn list_jobs(state: State<'_, Mutex>) -> Result { + let mut server = state.lock().await; + let content = cmd_list_jobs(&mut server).await; + Ok(render_list_job(&content)) +} + +fn fetch_img_result(path: &PathBuf) -> Option> { + match path.read_dir() { + // read the directory content + Ok(dir) => { + let mut list = dir + .filter_map(|res| res.ok()) // collect valid result + .map(|ent| ent.path()) // collect path from Directory entry result + .filter(|path| path.extension().map_or(false, |ext| ext == "png")) + .collect::>(); // collect the result into array list + list.sort(); // the list is not organzied, sort the list after collecting data + Some(list) + } + Err(e) => { + eprintln!("Unable to find any image stored in the directory:\nPath:{path:?}\nError:{e:?}"); + None + } } - .0) +} + +/* +fn fetch_img_preview(path: &PathBuf, imgs: &Vec) -> PathBuf { + // ffmpeg command usage + // ffmpeg -y -framerate 10 -i %02d.png -s 426x240 preview.gif + + let output = Command::new("ffmpeg").arg("-y -framerate 10 -i 02d.png -s 426x240 preview.gif").output(); + + + PathBuf::new() +} +*/ + +fn convert_file_src(path: &PathBuf) -> String { + #[cfg(any(windows, target_os = "android"))] + let base = "http://asset.localhost/"; + #[cfg(not(any(windows, target_os = "android")))] + let base = "asset://localhost/"; + // Consider about removing dunce lib for less dependencies involve for this case? + let path = dunce::canonicalize(path).expect("Should be able to canonicalize path!"); + let binding = path.to_string_lossy(); + let encoded = urlencoding::encode(&binding); + + format!("{base}{encoded}") } #[command(async)] -pub async fn get_job(state: State<'_, Mutex>, job_id: &str) -> Result { - // TODO: ask for the key to fetch the job details. - let job_id = Uuid::from_str(job_id).map_err(|e| { - eprintln!("Unable to parse uuid? \n{e:?}"); - () - })?; - let app_state = state.lock().await; - let jobs = app_state.job_db.read().await; - - match jobs.get_job(&job_id).await { - Ok(job) => Ok(html!( - div { - p { "Job Detail" }; - div { ( job.project_file.to_str().unwrap() ) }; - div { ( job.output.to_str().unwrap() ) }; - div { ( job.blender_version.to_string() ) }; - button tauri-invoke="delete_job" hx-vals=(json!({"jobId":job_id})) hx-target="#workplace" { "Delete Job" }; - }; - ) - .0), - Err(e) => Ok(html!( - div { - p { "Job do not exist.. How did you get here?" }; - input type="hidden" value=(e.to_string()); - }; - ) - .0), - } +pub async fn get_job_detail( + state: State<'_, Mutex>, + job_id: &str, +) -> Result { + let job_id = Uuid::from_str(job_id).map_err(|e| format!("Unable to parse uuid? \n{e:?}"))?; + let mut app_state = state.lock().await; + let result = cmd_fetch_job(&mut app_state, job_id).await; + Ok(render_job_detail_page(&result)) } // we'll need to figure out more about this? How exactly are we going to update the job? -// #[command(async)] -// pub fn update_job() +#[command(async)] +pub async fn update_job(state: State<'_, Mutex>, job_id: Uuid) -> Result<(), String> { + let mut app_state = state.lock().await; + if let Err(e) = app_state.invoke.send(UiCommand::Job(JobAction::Kill(job_id))).await { + return Err(format!("Fail to send command to host! Are you sure this app is responsive? {e:?}").into()); + } + + // TODO: call list_jobs and perform hx-swap-oob here to trigger job list refresh. + Ok(()) +} /// just delete the job from database. Notify peers to abandon task matches job_id #[command(async)] pub async fn delete_job(state: State<'_, Mutex>, job_id: &str) -> Result { + // here we're deleting it from the database { - let id = Uuid::from_str(job_id).unwrap(); - let server = state.lock().await; - let mut jobs = server.job_db.write().await; - let _ = jobs.delete_job(&id).await; - // TODO: Figure out what suppose to be done and handle here? - // let msg = UiCommand::StopJob(id); - // if let Err(e) = server.to_network.send(msg).await { - // eprintln!("Fail to send stop job command! {e:?}"); - // } + let mut app_state = state.lock().await; + let id = Uuid::from_str(job_id).map_err(|e| format!("{e:?}"))?; + let cmd = UiCommand::Job(JobAction::Kill(id)); + if let Err(e) = app_state.invoke.send(cmd).await { + eprintln!("{e:?}"); + } + } + + // now here we need to refresh the list + let list = list_jobs(state).await?; + + // TODO: do not send back Ok() response if there's an error, consider handling this separately. + // use a match condition to avoid sending error to the list + Ok(html!( + div class="group" id="joblist" hx-swap-oob="true" { + (PreEscaped(list)); + } + ) + .0) +} + +#[cfg(test)] +mod test { + /* + In this test suite, we are going to simply invoke all of the api function that are exposed to the UI. + Each API should have at least a minimum 1 passing test and 4 expect failures on certain edge cases + (malform input entry, wrong json syntax, incomplete form, etc) + + TODO: See about how we can get test coverage that handle all possible cases + */ + + use super::*; + use crate::{services::tauri_app::TauriApp}; + // use crate::models::constant::test::{EXAMPLE_FILE, EXAMPLE_OUTPUT}; + use anyhow::Error; + use futures::channel::mpsc::Receiver; + use ntest::timeout; + use tauri::{ + test::{mock_builder, MockRuntime}, + // webview::InvokeRequest + }; + + // TODO: Fix this so that I can get unit test working again + #[allow(dead_code)] + async fn scaffold_app() -> Result<(tauri::App, Receiver), Error> { + let (_invoke, receiver) = mpsc::channel(1); + // let conn = config_sqlite_db().await?; + // let app = TauriApp::new(&conn).await; + // TODO: Find a better way to get around this approach. Seems like I may not need to have an actual tauri app builder? + // error: symbol `_EMBED_INFO_PLIST` is already defined + let context = tauri::generate_context!("tauri.conf.json"); + let app = TauriApp::init_tauri_plugins(mock_builder()).build(context).expect("Should be able to build"); + Ok((app, receiver)) + } + + #[tokio::test] + #[timeout(5000)] + async fn create_job_successfully() { + // For now I'm going to let this pass, until I figure out how/why mockup tauri app dead-lock on initialization. + /* + let (app, mut receiver) = scaffold_app().await.unwrap(); + let webview = tauri::WebviewWindowBuilder::new(&app, "main", Default::default()) + .build() + .unwrap(); + let start = "1".to_owned(); + let end = "2".to_owned(); + let blender_version = Version::new(4, 1, 0); + let project_file = PathBuf::from(EXAMPLE_FILE); + let output = PathBuf::from(EXAMPLE_OUTPUT); + + let body = json!({ + "start": start, + "end": end, + "version": blender_version, + "path": project_file, + "output": output, + }); + + let res = tauri::test::get_ipc_response( + &webview, + InvokeRequest { + cmd: "create_job".into(), + callback: tauri::ipc::CallbackFn(0), + error: tauri::ipc::CallbackFn(1), + url: "tauri://localhost".parse().unwrap(), + body: tauri::ipc::InvokeBody::Json(body), + headers: Default::default(), + invoke_key: tauri::test::INVOKE_KEY.to_string(), + }, + ) + .map(|b| b.deserialize::().unwrap()); + + assert!(res.is_ok()); + + let expected_mode = RenderMode::Frame(1); + let job = Job::from(expected_mode, project_file, blender_version, output).expect("Should not fail"); + + let event = receiver.select_next_some().await; + let (mock_sender, _) = mpsc::channel(0); + assert_eq!(event, UiCommand::Job(JobAction::Create(job, mock_sender))); + */ + + assert!(true); + } + + #[tokio::test] + #[timeout(5000)] + async fn create_job_malform_fail() { + // For now I'm going to let this pass, until I figure out how/why mockup tauri app dead-lock on initialization. + // let (app, _) = scaffold_app().await.unwrap(); + // let webview = tauri::WebviewWindowBuilder::new(&app, "main", Default::default()); + // let start = "1".to_owned(); + // let end = "2".to_owned(); + // let project_file = PathBuf::from("./blender_rs/examples/assets/test.blend".to_owned()); + // let output = PathBuf::from("./blender_rs/examples/assets/".to_owned()); + + // let body = json!({ + // "start": start, + // "end": end, + // "version": "1a2b3c", + // "path": project_file, + // "output": output, + // }); + + // let res = tauri::test::get_ipc_response( + // &webview, + // InvokeRequest { + // cmd: "create_job".into(), + // callback: tauri::ipc::CallbackFn(0), + // error: tauri::ipc::CallbackFn(1), + // url: "tauri://localhost".parse().unwrap(), + // body: tauri::ipc::InvokeBody::Json(body), + // headers: Default::default(), + // invoke_key: tauri::test::INVOKE_KEY.to_string(), + // }, + // ) + // .map(|b| b.deserialize::().unwrap()); + + // assert!(res.is_err()); + assert!(true); } - remote_render_page().await + //#endregion } diff --git a/src-tauri/src/routes/live_view.rs b/src-tauri/src/routes/live_view.rs deleted file mode 100644 index 6c92ae09..00000000 --- a/src-tauri/src/routes/live_view.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::fs::File; -use tauri::{command, AppHandle}; - -/* - The idea behind this is to allow a scene you're working on to refresh and render from remote computer parts of the viewport render. - Almost like linescan rendering. - - TODO: Find a way to pipe render preview from Blender's .so/.a/.dll? - TODO: Find a way to receive and send data across network -*/ - -#[allow(dead_code)] -pub struct LiveView { - file: File, -} - -#[allow(dead_code)] -#[command] -pub fn load_file(_app: AppHandle) { - // load the project file - // spin up render_node to send the files over - // then have it prepare to render section of it - // and return the result to this view - todo!("impl this later!"); -} diff --git a/src-tauri/src/routes/mod.rs b/src-tauri/src/routes/mod.rs index 9ed25657..b525e528 100644 --- a/src-tauri/src/routes/mod.rs +++ b/src-tauri/src/routes/mod.rs @@ -1,7 +1,7 @@ pub mod job; -pub(crate) mod live_view; pub(crate) mod remote_render; pub mod server_settings; pub(crate) mod settings; pub(crate) mod util; pub(crate) mod worker; +pub(crate) mod index; diff --git a/src-tauri/src/routes/remote_render.rs b/src-tauri/src/routes/remote_render.rs index 683047b8..b437be78 100644 --- a/src-tauri/src/routes/remote_render.rs +++ b/src-tauri/src/routes/remote_render.rs @@ -1,51 +1,84 @@ /* Dev blog: - I really need to draw things out and make sense of the workflow for using this application. -I wonder why initially I thought of importing the files over and then selecting the files again to begin the render job? - -For now - Let's go ahead and save the changes we have so far. -Next update - Remove Project list, and instead just allow user to create a new job. -when you create a new job, it immediately sends a new job to the server farm for future features impl: Get a preview window that show the user current job progress - this includes last frame render, node status, (and time duration?) */ -use crate::AppState; -use blender::blender::Blender; +use super::util::select_directory; +use crate::models::blender_action::BlenderAction; +use crate::{ + models::app_state::AppState, + services::tauri_app::{QueryMode, UiCommand}, +}; +use blender::blend_file::BlendFile; +use futures::{SinkExt, StreamExt, channel::mpsc}; use maud::html; use semver::Version; use std::path::PathBuf; -use tauri::{command, AppHandle, State}; +use tauri::{AppHandle, State, command}; use tauri_plugin_dialog::DialogExt; use tauri_plugin_fs::FilePath; use tokio::sync::Mutex; -// todo break commands apart, find a way to get the list of versions -async fn list_versions(app_state: &AppState) -> Vec { - let manager = app_state.manager.read().await; - let mut versions = Vec::new(); - - let _ = manager.home.as_ref().iter().for_each(|b| { - let version = match b.fetch_latest() { - Ok(download_link) => download_link.get_version().clone(), - Err(_) => Version::new(b.major, b.minor, 0), - }; - versions.push(version); - }); - - // let manager = server.manager.read().await; - let _ = manager - .get_blenders() - .iter() - .for_each(|b| versions.push(b.get_version().clone())); - - versions +// TODO: where is this function called? +async fn list_versions(app_state: &mut AppState) -> Vec { + // TODO: see if there's a better way to get around this problematic function + /* + Issues: I'm noticing a significant delay of behaviour event happening here when connected online. + When connected online, BlenderManager seems to hold up to approximately 2-3 seconds before the remaining content fills in. + Offline loads instant, which is exactly the kind of behaviour I expect to see from this application. + */ + let (sender, mut receiver) = mpsc::channel(1); + let event = UiCommand::Blender(BlenderAction::List( + sender, + QueryMode::ONLINE | QueryMode::LOCAL, + )); + if let Err(e) = app_state.invoke.send(event).await { + eprintln!("Fail to send event! {e:?}"); + return Vec::new(); + } + + let res = receiver.select_next_some().await; + match res { + // Clone operation used here. might be expensive? See if there's another way to get aorund this. + Some(list) => list + .iter() + .map(|f| f.version.clone()) + .collect::>(), + None => Vec::new(), + } + + // let mut versions = Vec::new(); + + // // fetch local installation first. + // let mut local = manager + // .get_blenders() + // .iter() + // .map(|b| b.get_version().clone()) + // .collect::>(); + + // if !local.is_empty() { + // versions.append(&mut local); + // } + + // // then display the rest of the download list + // if let Some(downloads) = manager.fetch_download_list() { + // let mut item = downloads + // .iter() + // .map(|d| d.get_version().clone()) + // .collect::>(); + // versions.append(&mut item); + // }; + + // versions } /// List all of the available blender version. +// TODO: not used in the function yet? #[command(async)] pub async fn available_versions(state: State<'_, Mutex>) -> Result { - let server = state.lock().await; - let versions = list_versions(&server).await; + let mut server = state.lock().await; + let versions = list_versions(&mut server).await; Ok(html!( div { @@ -59,47 +92,52 @@ pub async fn available_versions(state: State<'_, Mutex>) -> Result>, +// This function must be async to avoid ui thread lock. Without async, no dialog will appear and app will freeze +/// Display dialog and return file path to blender. +/// This function will read the file and display another dialog prompt for additional detail before continue to display the result from import_blend() +#[command] +pub async fn open_dialog_for_blend_file( app: AppHandle, + state: State<'_, Mutex>, ) -> Result { - // tell tauri to open file dialog - // with that file path we will run import_blend function. - // else return nothing. - let result = match app + let given_path = app .dialog() .file() .add_filter("Blender", &["blend"]) .blocking_pick_file() - { - Some(file_path) => match file_path { - FilePath::Path(path) => import_blend(state, path).await.unwrap(), - FilePath::Url(uri) => import_blend(state, uri.as_str().into()).await.unwrap(), - }, - None => "".to_owned(), - }; - - Ok(result) + .and_then(|f| match f { + // TODO - see about converting PathBuf into &str, to reduce .into() for Url + FilePath::Path(f) => Some(f), + FilePath::Url(u) => Some(u.as_str().into()), + }); + + if let Some(path) = given_path { + return import_blend(&state, path).await; + } + Err("No file selected!".into()) } -// change this to return HTML content of the info back. -#[command(async)] -pub async fn import_blend( - state: State<'_, Mutex>, - path: PathBuf, -) -> Result { - let server = state.lock().await; - let versions = list_versions(&server).await; - - if path.file_name() == None { - return Err("Should be a valid file!".to_owned()); +#[command] +pub async fn update_output_field(app: AppHandle) -> Result { + match select_directory(app).await { + Ok(path) => Ok(html!( + input type="text" class="form-input" placeholder="Output Path" name="output" value=(path) readonly={true}; + ).0), + Err(_) => Err(()), } +} - let data = match Blender::peek(&path).await { - Ok(data) => data, - Err(e) => return Err(e.to_string()), - }; +// TODO: Rename this function to "read_blend_file_content" - return info about this file. +// we can multi-purpose this for drag and drop feature +pub async fn import_blend(state: &Mutex, path: PathBuf) -> Result { + // for some reason this function takes longer online than it does offline? + // TODO: set unit test to make sure this function doesn't repetitively call blender.org everytime it's called. + let mut app_state = state.lock().await; + let versions = list_versions(&mut app_state).await; + + // validate file path. + let blend_file = BlendFile::new(&path).map_err(|e| e.to_string())?; + let data = blend_file.peek_response(None); let content = html! { div id="modal" _="on closeModal add .closing then wait for animationend then remove me" { @@ -108,12 +146,15 @@ pub async fn import_blend( form method="dialog" tauri-invoke="create_job" hx-target="#workplace" _="on submit trigger closeModal" { h1 { "Create new Render Job" }; label { "Project File Path:" }; - input type="text" class="form-input" name="path" value=(path.to_str().unwrap()) placeholder="Project path" readonly={true}; - // add a button here to let the user search by directory path. Let them edit the form. - br; + // TODO: Figure out what this value was suppose to be? What method did this invoke to? + // input type="text" class="form-input" name="path" value=(blend_file.to_str().unwrap()) placeholder="Project path" readonly={true}; + p { "Need to update this method. Please see the source code" } + br; label { "Output destination:" }; - input type="text" tauri-invoke="select_directory" hx-target="this" class="form-input" placeholder="Output Path" name="output" value=(data.output.to_str().unwrap()) readonly={true}; + div tauri-invoke="update_output_field" hx-target="this" { + input type="text" class="form-input" placeholder="Output Path" name="output" value=(data.current.render_setting.get_output().to_str().unwrap()) readonly={true}; + } br; div name="mode" { @@ -159,24 +200,7 @@ pub async fn import_blend( Ok(content.into_string()) } -#[command(async)] -pub async fn remote_render_page() -> Result { - let content = html! { - div class="content" { - h1 { "Remote Jobs" }; - - button tauri-invoke="create_new_job" hx-target="body" hx-indicator="#spinner" hx-swap="beforeend" { - "Import" - }; - - img id="spinner" class="htmx-indicator" src="/assets/svg-loaders/tail-spin.svg"; - - div class="group" id="joblist" tauri-invoke="list_jobs" hx-trigger="load" hx-target="this" { - }; - - div id="detail"; - }; - }; - - Ok(content.0) +#[cfg(test)] +mod test { + // TODO: fill testing suite for this route } diff --git a/src-tauri/src/routes/server_settings.rs b/src-tauri/src/routes/server_settings.rs index 935ed6e3..72623f57 100644 --- a/src-tauri/src/routes/server_settings.rs +++ b/src-tauri/src/routes/server_settings.rs @@ -1,13 +1,12 @@ -use tauri::{command, State}; -// TODO: Double verify that this is the correct Mutex usage throughout the application +use crate::models::{app_state::AppState, server_setting::ServerSetting, setting_action::SettingsAction}; +use crate::services::tauri_app::UiCommand; +use futures::SinkExt; +use tauri::{State, command}; use tokio::sync::Mutex; -use crate::models::{app_state::AppState, server_setting::ServerSetting}; - - #[command(async)] pub async fn get_server_settings() -> Result { - Ok( "".to_owned() ) + Ok("".to_owned()) } #[command(async)] @@ -15,11 +14,10 @@ pub async fn set_server_settings( state: State<'_, Mutex>, new_settings: ServerSetting, ) -> Result<(), String> { - // maybe I'm a bit confused here? - let app_state = state.lock().await; - let mut old_setting = app_state.setting.write().await; - new_settings.save(); - *old_setting = new_settings; - + let mut app_state = state.lock().await; + let event = UiCommand::Settings(SettingsAction::Update(new_settings)); + if let Err(e) = app_state.invoke.send(event).await { + return Err(e.to_string()); + } Ok(()) -} \ No newline at end of file +} diff --git a/src-tauri/src/routes/settings.rs b/src-tauri/src/routes/settings.rs index 7922d75c..1015cb5b 100644 --- a/src-tauri/src/routes/settings.rs +++ b/src-tauri/src/routes/settings.rs @@ -1,41 +1,69 @@ -use std::{path::PathBuf, sync::Arc}; - -// this is the settings controller section that will handle input from the setting page. use crate::models::{app_state::AppState, server_setting::ServerSetting}; +use crate::models::blender_action::BlenderAction; +use crate::models::setting_action::SettingsAction; +use crate::services::tauri_app::{QueryMode, UiCommand}; +use std::{env, path::PathBuf, str::FromStr, process::Command}; use blender::blender::Blender; +use futures::{channel::mpsc, SinkExt, StreamExt}; use maud::html; use semver::Version; use serde_json::json; -use tauri::{command, AppHandle, Error, State}; +use tauri::{command, AppHandle, State}; use tauri_plugin_dialog::DialogExt; use tauri_plugin_fs::FilePath; -use tokio::{ - join, - sync::{Mutex, RwLock}, -}; +use tokio::sync::Mutex; const SETTING: &str= "settings"; -/* - Because blender installation path is not store in server setting, it is infact store under blender manager, - we will need to create a new custom response message to provide all of the information needed to display on screen properly -*/ +#[command] +pub fn open_dir(path: &str) -> Result<(),()> { + // macos is special, the path link inside app bundle, but cannot access via file explore/finder + let path = PathBuf::from_str(path).unwrap(); + let result = match env::consts::OS { + "windows" => Ok("explorer"), + "macos" => Ok("open"), + "linux" => Ok("xdg-open"), + _ => Err(()) + }; + if let Ok(program) = result { + Command::new(program) + .arg(path) + .spawn() + .unwrap(); + } + Ok(()) +} #[command(async)] pub async fn list_blender_installed(state: State<'_, Mutex>) -> Result { - let app_state = state.lock().await; - let manager = app_state.manager.read().await; - let localblenders = manager.get_blenders(); + let (sender, mut receiver) = mpsc::channel(0); + let mut app_state = state.lock().await; + + let event = UiCommand::Blender(BlenderAction::List(sender, QueryMode::LOCAL)); + if let Err(e) = app_state.invoke.send(event).await { + eprintln!("fail to send mpsc to event! {e:?}"); + return Err(()) + } + + let list = receiver.select_next_some().await.expect("Should expect data back!"); Ok(html! { - @for blend in localblenders { + @for blend in list { tr { td { - (blend.get_version().to_string()) + label title=(blend.link()) { + (blend.version.to_string()) + } }; td { - (blend.get_executable().to_str().unwrap()) - }; + button tauri-invoke="open_dir" hx-vals=(json!({"path":blend.link()})) { + r"📁" + } + button tauri-invoke="delete_blender" hx-vals=(json!({"path":blend.link() })) + { + r"🗑︎" + } + } }; }; } @@ -45,11 +73,10 @@ pub async fn list_blender_installed(state: State<'_, Mutex>) -> Result /// Add a new blender entry to the system, but validate it first! #[command(async)] pub async fn add_blender_installation( - app: AppHandle, - state: State<'_, Mutex>, // TODO: Need to change this to string, string? -) -> Result { - // TODO: include behaviour to search for file that contains blender. - // so here's where + handle: State<'_, Mutex>, + state: State<'_, Mutex>, +) -> Result<(), ()> { // TODO: Need to change this to string, string? + let app = handle.lock().await; let path = match app.dialog().file().blocking_pick_file() { Some(file_path) => match file_path { FilePath::Path(path) => path, @@ -58,15 +85,22 @@ pub async fn add_blender_installation( None => return Err(()), }; - let app_state = state.lock().await; - let mut manager = app_state.manager.write().await; - match manager.add_blender_path(&path) { - Ok(_blender) => Ok(html! { - // HX-trigger="newBlender" - } - .0), - Err(_) => Err(()), + let mut app_state = state.lock().await; + if let Err(e) = app_state.invoke.send(UiCommand::Blender(BlenderAction::Add(path))).await { + eprintln!("Fail to send data back! {e:?}"); } + Ok(()) +} + +// Somehow I was missing this function where it was used in this class? +#[command(async)] +pub async fn install_from_internet( + _handle: State<'_, Mutex>, + _state: State<'_, Mutex> +) -> Result<(), ()>{ + print!("Show me what the internet still have?"); + // in this case, I need to return a maud layout of the dialog pop up using htmx + Err(()) } // So this can no longer be a valid api call? @@ -75,88 +109,120 @@ pub async fn add_blender_installation( pub async fn fetch_blender_installation( state: State<'_, Mutex>, version: &str, -) -> Result { - let app_state = state.lock().await; - let mut manager = app_state.manager.write().await; - let version = Version::parse(version).map_err(|e| e.to_string())?; - let blender = manager.fetch_blender(&version).map_err(|e| match e { - blender::manager::ManagerError::DownloadNotFound { arch, os, url } => { - format!("Download link not found! {arch} {os} {url}") - } - blender::manager::ManagerError::RequestError(request) => { - format!("Request error: {request}") - } - blender::manager::ManagerError::FetchError(fetch) => format!("Fetch error: {fetch}"), - blender::manager::ManagerError::IoError(io) => format!("IoError: {io}"), - blender::manager::ManagerError::UnsupportedOS(os) => format!("Unsupported OS {os}"), - blender::manager::ManagerError::UnsupportedArch(arch) => { - format!("Unsupported architecture! {arch}") - } - blender::manager::ManagerError::UnableToExtract(ctx) => { - format!("Unable to extract content! {ctx}") - } - blender::manager::ManagerError::UrlParseError(url) => format!("Url parse error: {url}"), - blender::manager::ManagerError::PageCacheError(cache) => { - format!("Page cache error! {cache}") - } - blender::manager::ManagerError::BlenderError { source } => { - format!("Blender error: {source}") - } - })?; - Ok(blender) +) -> Result { + let version = Version::parse(version).map_err(|_| ())?; + let (sender, mut receiver) = mpsc::channel(1); + let event = UiCommand::Blender(BlenderAction::Get(version, sender)); + let mut app_state = state.lock().await; + app_state.invoke.send(event).await.unwrap(); + let result = receiver.select_next_some().await; + + // let blender = manager.fetch_blender(&version).map_err(|e| match e { + // blender::manager::ManagerError::DownloadNotFound { arch, os, url } => { + // format!("Download link not found! {arch} {os} {url}") + // } + // blender::manager::ManagerError::RequestError(request) => { + // format!("Request error: {request}") + // } + // blender::manager::ManagerError::FetchError(fetch) => format!("Fetch error: {fetch}"), + // blender::manager::ManagerError::IoError(io) => format!("IoError: {io}"), + // blender::manager::ManagerError::UnsupportedOS(os) => format!("Unsupported OS {os}"), + // blender::manager::ManagerError::UnsupportedArch(arch) => { + // format!("Unsupported architecture! {arch}") + // } + // blender::manager::ManagerError::UnableToExtract(ctx) => { + // format!("Unable to extract content! {ctx}") + // } + // blender::manager::ManagerError::UrlParseError(url) => format!("Url parse error: {url}"), + // blender::manager::ManagerError::PageCacheError(cache) => { + // format!("Page cache error! {cache}") + // } + // blender::manager::ManagerError::BlenderError { source } => { + // format!("Blender error: {source}") + // } + // })?; + + match result { + Some(blend) => Ok(blend), + None => Err(()) + } +} + +/// Permanently delete blender from the system using the file path given +#[command(async)] +pub async fn delete_blender(state: State<'_, Mutex>, path: &str) -> Result<(), String> { + let mut app_state = state.lock().await; + let blender = match Blender::from_executable(path) { + Ok(blend) => blend, + Err(e) => return Err(e.to_string()) + }; + + let event = UiCommand::Blender(BlenderAction::Remove(blender)); + if let Err(e) = app_state.invoke.send(event).await { + eprintln!("Fail to send blender action event! {e:?}"); + return Err(e.to_string()) + } + + Ok(()) } -// TODO: Ambiguous name - Change this so that we have two methods, -// - Severe local path to blender from registry (Orphan on disk/not touched) -// - Delete blender content completely (erasing from disk) +/// - Severe local path to blender from registry (Orphan on disk/not touched) #[command(async)] -pub async fn remove_blender_installation( +pub async fn disconnect_blender_installation( state: State<'_, Mutex>, blender: Blender, -) -> Result<(), Error> { - let app_state = state.lock().await; - let mut manager = app_state.manager.write().await; - manager.remove_blender(&blender); +) -> Result<(), String> { + let mut app_state = state.lock().await; + + let event = UiCommand::Blender(BlenderAction::Disconnect(blender)); + if let Err(e) = app_state.invoke.send(event).await { + eprintln!("Fail to send blender action event! {e:?}"); + return Err(e.to_string()) + } + Ok(()) } +// I am a little confused about this function. #[command(async)] pub async fn update_settings( state: State<'_, Mutex>, install_path: String, cache_path: String, render_path: String, -) -> Result { - let install_path = PathBuf::from(install_path); +) -> Result<(), ()> { + let _install_path = PathBuf::from(install_path); let blend_dir = PathBuf::from(cache_path); let render_dir = PathBuf::from(render_path); - { - let mut server = state.lock().await; - server.setting = Arc::new(RwLock::new(ServerSetting { - blend_dir, - render_dir, - })); - let mut manager = server.manager.write().await; - manager.set_install_path(&install_path); + let mut state = state.lock().await; + let new_setting = ServerSetting { + blend_dir, + render_dir, + }; + + let command = UiCommand::Settings(SettingsAction::Update(new_setting)); + if let Err(e) = state.invoke.send(command).await { + eprintln!("{e:?}"); } - Ok(get_settings(state).await.unwrap()) + Ok(()) } // change this so that this is returning the html layout to let the client edit the settings. #[command(async)] pub async fn edit_settings(state: State<'_, Mutex>) -> Result { - let app_state = state.lock().await; - let (settings, manager) = join!(app_state.setting.read(), app_state.manager.read()); - let install_path = manager.get_install_path(); + let mut app_state = state.lock().await; + let settings = app_state.get_settings().await.map_err(|e| e.to_string())?; + + // let install_path = manager.get_install_path(); let cache_path = &settings.blend_dir; let render_path = &settings.render_dir; Ok(html!( form tauri-invoke="update_settings" hx-target="this" hx-swap="outerHTML" { - h3 { "Blender Installation Path:" }; - input name="installPath" class="form-input" readonly="true" tauri-invoke="select_directory" hx-trigger="click" hx-target="this" value=(install_path.to_str().unwrap() ); + // h3 { "Blender Installation Path:" }; + // input name="installPath" class="form-input" readonly="true" tauri-invoke="select_directory" hx-trigger="click" hx-target="this" value=(install_path.to_str().unwrap() ); h3 { "Blender File Cache Path:" }; input name="cachePath" class="form-input" readonly="true" tauri-invoke="select_directory" hx-trigger="click" hx-target="this" value=(cache_path.to_str().unwrap()); @@ -174,23 +240,26 @@ pub async fn edit_settings(state: State<'_, Mutex>) -> Result>) -> Result { - let app_state = state.lock().await; - let (settings, manager) = join!(app_state.setting.read(), app_state.manager.read()); + let mut app_state = state.lock().await; + let settings = app_state.get_settings().await.map_err(|e| e.to_string())?; - let install_path = manager.get_install_path().to_str().unwrap(); let cache_path = &settings.blend_dir.to_str().unwrap(); let render_path = &settings.render_dir.to_str().unwrap(); Ok(html!( div tauri-invoke="open_path" hx-target="this" hx-swap="outerHTML" { - h3 { "Blender Installation Path:" }; - label hx-info=(json!( { "path": install_path } )) { (install_path) }; - + // TODO: Could we make a factory to build buttons for this? h3 { "Blender File Cache Path:" }; - label hx-info=(json!( { "path": cache_path } )) { (cache_path) }; + button tauri-invoke="open_dir" hx-vals=(json!({"path":cache_path})) { + r"📁" + } + label word-wrap="break-word" hx-info=(json!( { "path": cache_path } )) { (cache_path) }; h3 { "Render cache directory:" }; - label hx-info=(json!( { "path": render_path } )) { (render_path) }; + button tauri-invoke="open_dir" hx-vals=(json!({"path":render_path})) { + r"📁" + } + label word-wrap="break-word" hx-info=(json!( { "path": render_path } )) { (render_path) }; br; button tauri-invoke="edit_settings" { "Edit" }; diff --git a/src-tauri/src/routes/worker.rs b/src-tauri/src/routes/worker.rs index f4c18a51..3f168db7 100644 --- a/src-tauri/src/routes/worker.rs +++ b/src-tauri/src/routes/worker.rs @@ -1,37 +1,53 @@ +use std::str::FromStr; + +use futures::channel::mpsc; +use futures::{SinkExt, StreamExt}; +use libp2p::PeerId; use maud::html; use serde_json::json; -use tauri::{command, State}; +use tauri::{State, command}; use tokio::sync::Mutex; +use crate::constant::WORKPLACE; use crate::models::app_state::AppState; -use crate::services::tauri_app::WORKPLACE; +use crate::services::tauri_app::{UiCommand, WorkerAction}; #[command(async)] pub async fn list_workers(state: State<'_, Mutex>) -> Result { - let server = state.lock().await; - let workers = server.worker_db.read().await; - match &workers.list_worker().await { - Ok(data) => Ok(html! { - @for worker in data { - div tauri-invoke="get_worker" hx-vals=(json!({ "machineId": worker.machine_id })) hx-target=(format!("#{WORKPLACE}")) { - table { - tbody { - tr { - td style="width:100%" { - div { (worker.spec.host) } - div { (worker.spec.os) " | " (worker.spec.arch) } + let mut server = state.lock().await; + let (sender, mut receiver) = mpsc::channel(1); + let cmd = UiCommand::Worker(WorkerAction::List(sender)); + if let Err(e) = server.invoke.send(cmd).await { + eprintln!("Fail to send command to fetch workers{e:?}"); + } + + match receiver.select_next_some().await { + Some(data) => { + let content = match data.len() { + 0 => html! { div { } }, + _ => html! { + @for worker in data { + div { + table tauri-invoke="get_worker" hx-vals=(json!({ "machineId": worker.peer_id.to_base58() })) hx-target=(format!("#{WORKPLACE}")) { + tbody { + tr { + td style="width:100%" { + div { (worker.spec.host) } + div { (worker.spec.os) " | " (worker.spec.arch) } + } + } } } } } - } - } + }, + }; + Ok(content.0) } - .0), - Err(e) => { - eprintln!("Received error on list workers: \n{e:?}"); + None => { + eprintln!("No workers found"); Ok(html!( div { }; ).0) - }, + } } } @@ -56,21 +72,78 @@ pub async fn list_workers(state: State<'_, Mutex>) -> Result>, machine_id: &str) -> Result { - let app_state = state.lock().await; - let workers = app_state.worker_db.read().await; - match workers.get_worker(machine_id).await { + let mut app_state = state.lock().await; + let (mut sender, mut receiver) = mpsc::channel(0); + match PeerId::from_str(machine_id) { + Ok(peer_id) => { + let cmd = UiCommand::Worker(WorkerAction::Get(peer_id, sender)); + if let Err(e) = app_state.invoke.send(cmd).await { + eprintln!("{e:?}"); + } + } + Err(e) => { + eprintln!("Fail to parse machine id from input! {e:?}"); + sender + .send(None) + .await + .expect("Sender/Receiver should not be closed"); + } + }; + + match receiver.select_next_some().await { Some(worker) => Ok(html! { - div { - h1 { (format!("Computer: {}", worker.machine_id)) }; + div class="content" { + h1 { (format!("Computer: {}", &worker.spec.host)) }; h3 { "Hardware Info:" }; - p { (format!("System: {} | {}", worker.spec.os, worker.spec.arch))} - p { (format!("CPU: {} | ({} threads)", worker.spec.cpu, worker.spec.cores)) }; - p { (format!("Ram: {} GB", worker.spec.memory / ( 1024 * 1024 )))} - @if let Some(gpu) = worker.spec.gpu { - p { (format!("GPU: {gpu}")) }; - } @else { - p { "GPU: N/A" }; - }; + table { + tr { + th { + "System" + } + th { + "CPU" + } + th { + "Memory" + } + th { + "GPU" + } + } + tr { + td { + p { (worker.spec.os) } + span { (worker.spec.arch) } + } + td { + p { (worker.spec.cpu) } + span { (format!("({} cores)",worker.spec.cores)) } + } + td { + (format!("{}GB", worker.spec.memory / ( 1024 * 1024 * 1024 ))) + } + td { + @if let Some(gpu) = &worker.spec.gpu { + label { (gpu) }; + } @else { + label { "N/A" }; + }; + } + } + } + + h3 { "Task List" } + table { + tr { + th { + "Project Name" + } + th { + "Progresss" + } + } + // TODO: Fill in the info from the worker machine here. + } }; } .0), diff --git a/src-tauri/src/services/app_context.rs b/src-tauri/src/services/app_context.rs new file mode 100644 index 00000000..44ea22ac --- /dev/null +++ b/src-tauri/src/services/app_context.rs @@ -0,0 +1,18 @@ + + +// Used to help organize dependency injections +use crate::{BlenderManager, models::server_setting::ServerSetting}; + +pub(crate) struct AppContext { + pub manager: BlenderManager, + pub settings: ServerSetting, // default ::load() +} + +impl AppContext { + pub fn new(manager: BlenderManager, settings: ServerSetting ) -> Self { + Self { + manager, + settings + } + } +} \ No newline at end of file diff --git a/src-tauri/src/services/blend_farm.rs b/src-tauri/src/services/blend_farm.rs index 10fa4e1b..dc7dd9d3 100644 --- a/src-tauri/src/services/blend_farm.rs +++ b/src-tauri/src/services/blend_farm.rs @@ -1,15 +1,41 @@ -use crate::models::{ - message::{NetEvent, NetworkError}, - network::NetworkController, - }; +use crate::models::behaviour::FileResponse; +use crate::network::controller::Controller as NetworkController; +use crate::network::message::{Event, FileCommand, NetworkError}; use async_trait::async_trait; +use futures::channel::oneshot; +use libp2p_request_response::ResponseChannel; use tokio::sync::mpsc::Receiver; #[async_trait] pub trait BlendFarm { + // TODO: Return mpsc stream for event notifications and system relays. async fn run( mut self, client: NetworkController, - event_receiver: Receiver, + event_receiver: Receiver, ) -> Result<(), NetworkError>; + + // could we use this inside the blendfarm as a base class? + async fn handle_inbound_request( + client: &NetworkController, + request: String, + channel: ResponseChannel, + ) { + let (sender, receiver) = oneshot::channel(); + let cmd = FileCommand::RequestFilePath { + keyword: request, + sender, + }; + client.file_service(cmd).await; + + // once we received the data signal - process the remaining with the information obtained. + if let Some(path) = receiver.await.expect("Sender should not be dropped") { + let file = async_std::fs::read(path).await.unwrap(); + client.respond_file(file, channel).await; + } else { + eprintln!( + "This local service does not have any matching request providing! Do something about the ResponseChannel?" + ); + } + } } diff --git a/src-tauri/src/services/cli_app.rs b/src-tauri/src/services/cli_app.rs deleted file mode 100644 index 54ef9843..00000000 --- a/src-tauri/src/services/cli_app.rs +++ /dev/null @@ -1,244 +0,0 @@ -use std::sync::Arc; - -/* -Have a look into TUI for CLI status display window to show user entertainment on screen -https://docs.rs/tui/latest/tui/ - -Feature request: - - See how we can treat this application process as service mode so that it can be initialize and start on machine reboot? - - receive command to properly reboot computer when possible? -*/ -use super::blend_farm::BlendFarm; -use crate::{ - domains::{job_store::JobError, task_store::TaskStore}, - models::{ - job::JobEvent, - message::{NetEvent, NetworkError}, - network::{NetworkController, JOB}, - task::Task, - }, -}; -use blender::blender::Manager as BlenderManager; -use blender::models::status::Status; -use libp2p::PeerId; -use tokio::{ - select, - sync::{mpsc::Receiver, RwLock}, - // task::JoinHandle, -}; - -pub struct CliApp { - manager: BlenderManager, - task_store: Arc>, - // Hmm not sure if I need this but we'll see! - // task_handle: Option>, // isntead of this, we should hold task_handler. That way, we can abort it when we receive the invocation to do so. -} - -impl CliApp { - pub fn new(task_store: Arc>) -> Self { - let manager = BlenderManager::load(); - Self { - manager, - task_store, - // task_handle: None, - } - } -} - -impl CliApp { - // TODO: May have to refactor this to take consideration of Job Storage - // How do I abort the job? - // Invokes the render job. The task needs to be mutable for frame deque. - async fn render_task( - &mut self, - request_id: PeerId, - client: &mut NetworkController, - task: &mut Task, - ) { - let status = format!("Receive task from peer [{:?}]", task); - client.send_status(status).await; - let id = task.job_id; - - // create a path link where we think the file should be - let blend_dir = client.settings.blend_dir.join(id.to_string()); - if let Err(e) = async_std::fs::create_dir_all(&blend_dir).await { - eprintln!("Error creating blend directory! {e:?}"); - } - - // assume project file is located inside this directory. - let project_file = blend_dir.join(&task.blend_file_name); // append the file name here instead. - - client - .send_status(format!("Checking for project file {:?}", &project_file)) - .await; - - // Fetch the project from peer if we don't have it. - if !project_file.exists() { - println!( - "Project file do not exist, asking to download from host: {:?}", - &task.blend_file_name - ); - - let file_name = task.blend_file_name.to_str().unwrap(); - // TODO: To receive the path or not to modify existing project_file value? I expect both would have the same value? - match client.get_file_from_peers(&file_name, &blend_dir).await { - Ok(path) => println!("File successfully download from peers! path: {path:?}"), - Err(e) => match e { - NetworkError::UnableToListen(_) => todo!(), - NetworkError::NotConnected => todo!(), - NetworkError::SendError(_) => {} - NetworkError::NoPeerProviderFound => { - // I was timed out here? - client - .send_status("No peer provider found on the network?".to_owned()) - .await - } - NetworkError::UnableToSave(e) => { - client - .send_status(format!("Fail to save file to disk: {e}")) - .await - } - NetworkError::Timeout => { - // somehow we lost connection, try to establish connection again? - // client.dial(request_id, client.public_addr).await; - dbg!("Timed out?"); - } - _ => println!("Unhandle error received {e:?}"), // shouldn't be covered? - }, - } - } - - // here we'll ask if we have blender installed before usage - let blender = self - .manager - .fetch_blender(&task.blender_version) - .expect("Fail to download blender"); - - // TODO: Call other network on specific topics to see if there's a version available. - // match manager.have_blender(job.as_ref()) { - // Some(exe) => exe.clone(), - // None => { - // // try to fetch from other peers with matching os / arch. - // // question is, how do I make them publicly available with the right blender version? or do I just find it by the executable name instead? - // } - // } - - // create a output destination for the render image - let output = client.settings.render_dir.join(id.to_string()); - if let Err(e) = async_std::fs::create_dir_all(&output).await { - eprintln!("Error creating render directory: {e:?}"); - } - - // run the job! - match task.clone().run(project_file, output, &blender).await { - Ok(rx) => loop { - if let Ok(status) = rx.recv() { - match status { - Status::Idle => client.send_status("[Idle]".to_owned()).await, - Status::Running { status } => { - client.send_status(format!("[Running] {status}")).await - } - Status::Log { status } => { - client.send_status(format!("[Log] {status}")).await - } - Status::Warning { message } => { - client.send_status(format!("[Warning] {message}")).await - } - Status::Error(blender_error) => { - client.send_status(format!("[ERR] {blender_error:?}")).await - } - Status::Completed { frame, result, .. } => { - // Use PathBuf as this helps enforce type intention of using OsString - // Why don't I create it like a directory instead? = - let file_name = result.file_name().unwrap().to_string_lossy(); - let file_name = format!("/{}/{}", id, file_name); - let event = JobEvent::ImageCompleted { - job_id: id, - frame, - file_name: file_name.clone(), - }; - client.start_providing(file_name, result).await; - client.send_job_message(request_id, event).await; - } - Status::Exit => { - client - .send_job_message(request_id, JobEvent::JobComplete) - .await; - break; - } - }; - } - }, - Err(e) => { - let err = JobError::TaskError(e); - client - .send_job_message(request_id, JobEvent::Error(err)) - .await; - } - }; - } - - async fn handle_message(&mut self, client: &mut NetworkController, event: NetEvent) { - match event { - NetEvent::OnConnected(peer_id) => client.share_computer_info(peer_id).await, - NetEvent::NodeDiscovered(..) => {} // Ignored - NetEvent::NodeDisconnected(_) => {} // ignored - NetEvent::JobUpdate(peer_id, job_event) => match job_event { - // on render task received, we should store this in the database. - JobEvent::Render(mut task) => self.render_task(peer_id, client, &mut task).await, - JobEvent::ImageCompleted { .. } => {} // ignored since we do not want to capture image? - // For future impl. we can take advantage about how we can allieve existing job load. E.g. if I'm still rendering 50%, try to send this node the remaining parts? - JobEvent::JobComplete => {} // Ignored, we're treated as a client node, waiting for new job request. - JobEvent::Remove(id) => { - let mut db = self.task_store.write().await; - let _ = db.delete_job_task(id).await; - // let mut db = self.job_store.write().await; - // if let Err(e) = db.delete_job(id).await { - // eprintln!("Fail to remove job from database! {e:?}"); - // } else { - // println!("Successfully remove job from database!"); - // } - } - _ => println!("Unhandle Job Event: {job_event:?}"), - }, - // maybe move this inside Network code? Seems repeative in both cli and Tauri side of application here. - NetEvent::InboundRequest { request, channel } => { - if let Some(path) = client.providing_files.get(&request) { - println!("Sending file {path:?}"); - client - .respond_file(std::fs::read(path).unwrap(), channel) - .await; - } - } - _ => println!("[CLI] Unhandled event from network: {event:?}"), - } - } -} - -#[async_trait::async_trait] -impl BlendFarm for CliApp { - async fn run( - mut self, - mut client: NetworkController, - mut event_receiver: Receiver, - ) -> Result<(), NetworkError> { - // Future Impl. Make this machine available to other peers that share the same operating system and arch - // - so that we can distribute blender across network rather than download blender per each peers. - // let system = self.machine.system_info(); - // let system_info = format!("blendfarm/{}{}", consts::OS, &system.processor.brand); - // client.subscribe_to_topic(system_info).await; - client.subscribe_to_topic(JOB.to_string()).await; - - // let current_job: Option = None; - loop { - select! { - // here we can insert job_db here to receive event invocation from Tauri_app - Some(event) = event_receiver.recv() => self.handle_message(&mut client, event).await, - // how do I poll database here? - // Some(task) = db.poll_task().await => self.handle_poll(&) - - // how do I poll the machine specs in certain intervals? - } - } - } -} diff --git a/src-tauri/src/services/data_store/mod.rs b/src-tauri/src/services/data_store/mod.rs index 35aab06e..c963c503 100644 --- a/src-tauri/src/services/data_store/mod.rs +++ b/src-tauri/src/services/data_store/mod.rs @@ -1,3 +1,5 @@ +pub mod sqlite_advertise_store; pub mod sqlite_job_store; -pub mod sqlite_task_store; +pub mod sqlite_renders_store; +pub mod sqlite_ticket_store; pub mod sqlite_worker_store; diff --git a/src-tauri/src/services/data_store/sqlite_advertise_store.rs b/src-tauri/src/services/data_store/sqlite_advertise_store.rs new file mode 100644 index 00000000..aafc4af6 --- /dev/null +++ b/src-tauri/src/services/data_store/sqlite_advertise_store.rs @@ -0,0 +1,99 @@ +use std::{path::PathBuf, str::FromStr}; + +use serde::{Deserialize, Serialize}; +use sqlx::{query, query_as, FromRow, SqlitePool}; +use uuid::Uuid; + +use crate::{ + domains::advertise_store::{AdvertiseError, AdvertiseStore}, + models::advertise::Advertise, +}; + +pub struct SqliteAdvertiseStore { + conn: SqlitePool, +} + +#[derive(Debug, FromRow, Serialize, Deserialize)] +struct AdvertiseDAO { + id: String, + ad_name: String, + file_path: String, +} + +impl AdvertiseDAO { + pub fn dto_to_obj(self) -> Advertise { + let id = Uuid::from_str(&self.id).expect("ID was mutated!"); + let file_path = PathBuf::from_str(&self.file_path).expect("File path was mutated!"); + Advertise { + id, + ad_name: self.ad_name, + file_path, + } + } +} + +#[async_trait::async_trait] +impl AdvertiseStore for SqliteAdvertiseStore { + async fn find(&self, id: Uuid) -> Result, AdvertiseError> { + let id = id.to_string(); + match query_as!( + AdvertiseDAO, + r"SELECT id, ad_name, file_path FROM advertise WHERE id=$1", + id + ) + .fetch_optional(&self.conn) + .await + { + Ok(dto) => Ok(dto.map(|d| d.dto_to_obj())), + Err(e) => Err(AdvertiseError::DatabaseError(e.to_string())), + } + } + + async fn update(&self, advertise: Advertise) -> Result<(), AdvertiseError> { + let id = advertise.id.to_string(); + let file_path = advertise.file_path.to_str(); + query!( + "UPDATE advertise SET ad_name=$2, file_path=$3 WHERE id=$1", + id, + advertise.ad_name, + file_path + ) + .execute(&self.conn) + .await + .map_err(|e| AdvertiseError::DatabaseError(e.to_string()))?; + Ok(()) + } + + async fn create(&self, advertise: Advertise) -> Result<(), AdvertiseError> { + let id = advertise.id.to_string(); + let file_path = advertise.file_path.to_str(); + if let Err(e) = query!( + r" + INSERT INTO advertise (id, ad_name, file_path) + VALUES($1, $2, $3); + ", + id, + advertise.ad_name, + file_path + ) + .execute(&self.conn) + .await + { + return Err(AdvertiseError::DatabaseError(e.to_string())); + } + + Ok(()) + } + + async fn kill(&self, id: Uuid) -> Result<(), AdvertiseError> { + let id = id.to_string(); + let _ = query!(r"DELETE FROM advertise WHERE id=$1", id) + .execute(&self.conn) + .await + .map_err(|e| AdvertiseError::DatabaseError(e.to_string()))?; + Ok(()) + } + async fn all(&self) -> Result>, AdvertiseError> { + Ok(None) + } +} diff --git a/src-tauri/src/services/data_store/sqlite_job_store.rs b/src-tauri/src/services/data_store/sqlite_job_store.rs index bf938871..30dd8a62 100644 --- a/src-tauri/src/services/data_store/sqlite_job_store.rs +++ b/src-tauri/src/services/data_store/sqlite_job_store.rs @@ -2,11 +2,15 @@ use std::{path::PathBuf, str::FromStr}; use crate::{ domains::job_store::{JobError, JobStore}, - models::job::Job, + models::{ + job::{CreatedJobDto, Job, NewJobDto, Output}, + with_id::WithId, + }, }; -use blender::models::mode::Mode; +use blender::blend_file::BlendFile; +use blender::models::mode::RenderMode; use semver::Version; -use sqlx::{FromRow, SqlitePool}; +use sqlx::{FromRow, SqlitePool, query_as}; use uuid::Uuid; pub struct SqliteJobStore { @@ -19,8 +23,9 @@ impl SqliteJobStore { } } -#[derive(FromRow)] -struct JobDb { +// this information is used to help transpose data into database format. +#[derive(Debug, Clone, FromRow)] +struct JobDAO { id: String, mode: String, project_file: String, @@ -28,76 +33,128 @@ struct JobDb { output_path: String, } +impl JobDAO { + pub fn dto_to_obj(self) -> Result, JobError> { + let id = Uuid::from_str(&self.id).expect("id malformed"); + let mode = serde_json::from_str(&self.mode).expect("mode malformed"); + let project_file = PathBuf::from_str(&self.project_file).expect("Project path malformed"); + let blender_version = + Version::from_str(&self.blender_version).expect("Blender version malformed"); + let output = PathBuf::from_str(&self.output_path).expect("Output path malformed"); + match Job::from(mode, &project_file, blender_version, output) { + Ok(item) => Ok(WithId { id, item }), + Err(e) => Err(JobError::InvalidFile(e.to_string())), + } + } +} + #[async_trait::async_trait] impl JobStore for SqliteJobStore { - async fn add_job(&mut self, job: Job) -> Result<(), JobError> { - let id = job.id.to_string(); - let mode = serde_json::to_string(&job.mode).unwrap(); - let project_file = job.project_file.to_str().unwrap().to_owned(); - let blender_version = job.blender_version.to_string(); - let output = job.output.to_str().unwrap().to_owned(); + async fn add_job(&mut self, job: NewJobDto) -> Result { + let id = Uuid::new_v4(); + let id_str = id.to_string(); + let mode = serde_json::to_string::(job.as_ref()).unwrap(); + let blend_file = AsRef::::as_ref(&job).to_path().to_string_lossy(); + let blender_version = AsRef::::as_ref(&job).to_string(); + let output = AsRef::::as_ref(&job).to_str().unwrap().to_owned(); - sqlx::query( + sqlx::query!( r" INSERT INTO jobs (id, mode, project_file, blender_version, output_path) VALUES($1, $2, $3, $4, $5); ", + id_str, + mode, + blend_file, + blender_version, + output ) - .bind(id) - .bind(mode) - .bind(project_file) - .bind(blender_version) - .bind(output) .execute(&self.conn) .await .map_err(|e| JobError::DatabaseError(e.to_string()))?; - Ok(()) + Ok(CreatedJobDto { id, item: job }) } - async fn get_job(&self, job_id: &Uuid) -> Result { - let sql = - "SELECT id, mode, project_file, blender_version, output_path FROM Jobs WHERE id=$1"; - match sqlx::query_as::<_, JobDb>(sql) - .bind(job_id.to_string()) - .fetch_one(&self.conn) - .await + async fn get_job(&self, job_id: &Uuid) -> Result, JobError> { + let id_str = job_id.to_string(); + match sqlx::query_as!( + JobDAO, + r"SELECT id, mode, project_file, blender_version, output_path FROM Jobs WHERE id=$1", + id_str + ) + .fetch_optional(&self.conn) + .await { - Ok(r) => { - let id = Uuid::parse_str(&r.id).unwrap(); - let mode: Mode = serde_json::from_str(&r.mode).unwrap(); - let project = PathBuf::from(r.project_file); - let version = Version::from_str(&r.blender_version).unwrap(); - let output = PathBuf::from(r.output_path); - let job = Job::new(id, mode, project, version, output, Default::default()); - Ok(job) - } + Ok(record) => match record { + Some(r) => { + let id = Uuid::parse_str(&r.id).unwrap(); + let mode: RenderMode = serde_json::from_str(&r.mode).unwrap(); + let project = PathBuf::from(r.project_file); + let version = Version::from_str(&r.blender_version).unwrap(); + let output = PathBuf::from(r.output_path); + match Job::from(mode, &project, version, output) { + Ok(job) => Ok(Some(WithId { id, item: job })), + Err(e) => Err(JobError::InvalidFile(e.to_string())), + } + } + None => Ok(None), + }, Err(e) => Err(JobError::DatabaseError(e.to_string())), } } - async fn update_job(&mut self, _job: Job) -> Result<(), JobError> { - todo!("Update job to database"); + async fn update_job(&mut self, job: CreatedJobDto) -> Result<(), JobError> { + let id = job.id.to_string(); + let item = &job.item; + let mode = serde_json::to_string(item.into()).unwrap(); + let project = AsRef::::as_ref(&item) + .to_path() + .to_string_lossy(); + let version = AsRef::::as_ref(&item).to_string(); + let output = AsRef::::as_ref(&item) + .to_str() + .expect("Must have valid path!"); + + match sqlx::query!( + r"UPDATE Jobs SET mode=$2, project_file=$3, blender_version=$4, output_path=$5 + WHERE id=$1", + id, + mode, + project, + version, + output + ) + .execute(&self.conn) + .await + { + Ok(record) => match record.rows_affected() { + 0 => Err(JobError::DatabaseError( + "Unable to find record! No record was affected!".into(), + )), + 1 => Ok(()), + _ => Err(JobError::DatabaseError(format!( + "More than one records was affected! {}", + record.rows_affected() + ))), + }, + Err(e) => Err(JobError::DatabaseError(e.to_string())), + } } - async fn list_all(&self) -> Result, JobError> { - let sql = r"SELECT id, mode, project_file, blender_version, output_path FROM jobs"; - let mut data: Vec = Vec::new(); - let results = sqlx::query_as::<_, JobDb>(sql).fetch_all(&self.conn).await; - match results { - Ok(records) => { - for r in records { - let id = Uuid::parse_str(&r.id).unwrap(); - let mode: Mode = serde_json::from_str(&r.mode).unwrap(); - let project = PathBuf::from(r.project_file); - let version = Version::from_str(&r.blender_version).unwrap(); - let output = PathBuf::from(r.output_path); - let job = Job::new(id, mode, project, version, output, Default::default()); - data.push(job); - } - } - Err(e) => return Err(JobError::DatabaseError(e.to_string())), + async fn list_all(&self) -> Result, JobError> { + let query = query_as!( + JobDAO, + r"SELECT id, mode, project_file, blender_version, output_path FROM jobs LIMIT 20" + ); + + let result = query.fetch_all(&self.conn).await; + match result { + Ok(records) => Ok(records + .iter() + .map(|r| r.clone().dto_to_obj().expect("Must have valid job")) + .collect()), + Err(e) => Err(JobError::DatabaseError(e.to_string())), } - Ok(data) } async fn delete_job(&mut self, id: &Uuid) -> Result<(), JobError> { @@ -111,3 +168,61 @@ impl JobStore for SqliteJobStore { Ok(()) } } + +#[cfg(test)] +mod tests { + use crate::{config_sqlite_db, constant::DATABASE_FILE_NAME, models::job::test::scaffold_job}; + + use super::*; + + async fn get_sqlite_pool() -> SqlitePool { + let pool = config_sqlite_db(DATABASE_FILE_NAME).await; + assert!(pool.is_ok()); + pool.expect("Should be ok") + } + + async fn scaffold_job_store() -> SqliteJobStore { + let conn = get_sqlite_pool().await; + SqliteJobStore::new(conn) + } + + #[tokio::test] + async fn can_create_worker_success() { + let mut job_store = scaffold_job_store().await; + let job = scaffold_job(); + + let result = job_store.add_job(job).await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn fetch_job_success() { + let mut job_store = scaffold_job_store().await; + let job = scaffold_job(); + + // append a job to the database first + let result = job_store.add_job(job).await; + assert!(result.is_ok()); + + // retrieve the ID from the created job we inserted + let id = result.expect("Should be safe").id; + + // test and see if we can fetch it. + let fetch_result = job_store.get_job(&id).await; + assert!(fetch_result.is_ok()); + } + + #[tokio::test] + async fn fetch_job_fail_no_record_found() { + let job_store = scaffold_job_store().await; + + // generate random uuid that doesn't exist in the databset yet + let fake_id = Uuid::new_v4(); + + // query the result + let result = job_store.get_job(&fake_id).await; + + // Query should be successful, but should return none + assert!(result.is_ok_and(|e| e.is_none())); + } +} diff --git a/src-tauri/src/services/data_store/sqlite_renders_store.rs b/src-tauri/src/services/data_store/sqlite_renders_store.rs new file mode 100644 index 00000000..de860db2 --- /dev/null +++ b/src-tauri/src/services/data_store/sqlite_renders_store.rs @@ -0,0 +1,111 @@ +use std::{collections::HashMap, path::PathBuf}; + +use crate::{ + domains::render_store::{RenderError, RenderStore}, + models::{job::JobId, render_info::{CreatedRenderInfoDto, NewRenderInfoDto, RenderInfo}, with_id::WithId}, +}; +use blender::blender::Frame; +use sqlx::{SqlitePool, query_as}; +use uuid::Uuid; + +pub struct SqliteRenderStore { + conn: SqlitePool, +} + +impl SqliteRenderStore { + pub fn new(conn: SqlitePool) -> Self { + Self { conn } + } +} + +#[derive(Clone)] +struct RenderDAO { + id: String, + job_id: String, + frame: i64, + render_path: String, +} + +impl RenderDAO { + pub fn to_record(&self) -> Result, RenderError> { + let id = Uuid::parse_str(&self.id).map_err(|e| RenderError::DatabaseError(e.to_string()))?; + let job_id = Uuid::parse_str(&self.job_id).map_err(|e| RenderError::DatabaseError(e.to_string()))?; + let render_path = PathBuf::from(&self.render_path); + + let render_info = RenderInfo::new(job_id, self.frame as i32, render_path); + Ok( WithId { id, item: render_info }) + } +} + +#[async_trait::async_trait] +impl RenderStore for SqliteRenderStore { + async fn find(&self, filter: Option) -> Result, RenderError> { + // query all and list the renders + + let col = match filter { + Some(job_id) => { + query_as!( + RenderDAO, + r"SELECT id, job_id, frame, render_path FROM renders WHERE job_id=$1", + job_id + ) + .fetch_all(&self.conn) + .await + .map_err(|e| RenderError::DatabaseError(e.to_string()))? + } + None => + query_as!( + RenderDAO, + "SELECT id, job_id, frame, render_path FROM renders", + ) + .fetch_all(&self.conn) + .await + .map_err(|e| RenderError::DatabaseError(e.to_string()))? + }.iter().fold(HashMap::new(),|mut map, item| { + if let Ok( record ) = &item.to_record() { + map.insert(record.item.frame, record.item.render_path.clone()); + } + + map + }); + + // TODO: For future impl, Consider looking into Stream and see how we can take advantage of streaming realtime data? + + Ok(col) + } + + async fn create( + &self, + render_info: NewRenderInfoDto, + ) -> Result { + let sql = + r#"INSERT INTO renders (id, job_id, frame, render_path) VALUES( $1, $2, $3, $4, $5);"#; + let id = Uuid::new_v4(); + + if let Err(e) = sqlx::query(sql) + .bind(id.to_string()) + .bind(render_info.job_id.to_string()) + .bind(render_info.frame.to_string()) + .bind(render_info.render_path.to_str()) + .execute(&self.conn) + .await + { + eprintln!("Fail to save data to database! {e:?}"); + } + + Ok(CreatedRenderInfoDto { + id, + item: render_info, + }) + } + + async fn update(&mut self, render_info: RenderInfo) -> Result<(), RenderError> { + dbg!(render_info); + todo!("Impl. missing implementations here") + } + + async fn kill(&mut self, id: &Uuid) -> Result<(), RenderError> { + dbg!(id); + Ok(()) + } +} diff --git a/src-tauri/src/services/data_store/sqlite_task_store.rs b/src-tauri/src/services/data_store/sqlite_task_store.rs deleted file mode 100644 index 74f8493b..00000000 --- a/src-tauri/src/services/data_store/sqlite_task_store.rs +++ /dev/null @@ -1,56 +0,0 @@ -use sqlx::SqlitePool; -use uuid::Uuid; - -use crate::{domains::task_store::{TaskError, TaskStore}, models::task::Task}; - - -pub struct SqliteTaskStore { - conn: SqlitePool -} - -impl SqliteTaskStore { - pub fn new(conn: SqlitePool) -> Self { - Self { conn } - } -} - -#[async_trait::async_trait] -impl TaskStore for SqliteTaskStore { - async fn add_task(&mut self, task: Task) -> Result<(), TaskError> { - let id = task.id.to_string(); - let peer_id = task.get_peer_id().to_base58(); - let job_id = task.job_id.to_string(); - let blend_file_name = task.blend_file_name.to_str().unwrap().to_string(); - let blender_version = task.blender_version.to_string(); - let range = serde_json::to_string(&task.range).unwrap(); - let _ = sqlx::query(r"INSERT INTO tasks(id, peer_id, job_id, blend_file_name, blender_version, range) - VALUES($1, $2, $3, $4, $5, $6)") - .bind(id) - .bind(peer_id) - .bind(job_id) - .bind(blend_file_name) - .bind(blender_version) - .bind(range) - .execute(&self.conn); - Ok(()) - } - - async fn poll_task(&mut self) -> Result { - todo!("poll pending task?"); - } - - async fn delete_task(&mut self, task: Task) -> Result<(), TaskError> { - let id = task.id.to_string(); - let _ = sqlx::query(r"DELETE * FROM tasks WHERE id = $1") - .bind(id) - .execute(&self.conn).await; - Ok(()) - } - - async fn delete_job_task(&mut self, job_id: Uuid) -> Result<(), TaskError> { - let _ = sqlx::query(r"DELETE * FROM tasks WHERE job_id = $1") - .bind(job_id.to_string()) - .execute(&self.conn).await; - Ok(()) - } -} \ No newline at end of file diff --git a/src-tauri/src/services/data_store/sqlite_ticket_store.rs b/src-tauri/src/services/data_store/sqlite_ticket_store.rs new file mode 100644 index 00000000..dbcce922 --- /dev/null +++ b/src-tauri/src/services/data_store/sqlite_ticket_store.rs @@ -0,0 +1,128 @@ +use crate::{ + domains::ticket_store::{TicketError, TicketStore}, + models::{ + job::Job, + ticket::{CreatedTicketDto, Ticket}, + with_id::WithId, + }, +}; +use sqlx::{FromRow, SqlitePool, query, query_as, types::Uuid}; +use std::str::FromStr; + +// Is this how we can make this connection arc across threads? +#[derive(Debug)] +pub struct SqliteTicketStore { + conn: SqlitePool, +} + +impl SqliteTicketStore { + pub fn new(conn: SqlitePool) -> Self { + Self { conn } + } +} + +#[derive(Debug, Clone, FromRow)] +struct TicketDAO { + id: String, + job_id: String, + job: String, + // TODO: See why we can't use Frame (i32). Sqlite impose using i64? + start: i64, + end: i64, +} + +impl TicketDAO { + fn dto_to_task(self) -> WithId { + let id = Uuid::from_str(&self.id).expect("id was mutated"); + let job_id = Uuid::from_str(&self.job_id).expect("job_id was mutated"); + let job = serde_json::from_str::(&self.job).expect("job record was malformed!"); + let start = self.start as i32; + let end = self.end as i32; + + // at this point here, we shouldn't have to worry about Job's original rendering mode, + let job_record = WithId { + id: job_id, + item: job, + }; + // TODO: Find a way to handle expect() + let item = Ticket::from(job_record, start, end).expect("Malformed data detected!"); + WithId { id, item } + } +} + +#[async_trait::async_trait] +impl TicketStore for SqliteTicketStore { + async fn add_ticket(&self, task: Ticket) -> Result { + // let sql = ; + let id = Uuid::new_v4(); + let job = serde_json::to_string::(task.as_ref()) + .expect("Should be able to convert job into json"); + + let job_id = AsRef::::as_ref(&task).to_string(); + + // todo see if there's a better way to handle sqlite query? + let _ = query!( + r"INSERT INTO ticket(id, job_id, job, start, end) + VALUES($1, $2, $3, $4, $5)", + id, + job_id, + job, + task.start, + task.end + ) + .execute(&self.conn) + .await + .map_err(TicketError::DatabaseError)?; + + Ok(WithId { id, item: task }) + } + + // Poll next available task if there any. + async fn poll_ticket(&self) -> Result, TicketError> { + // fetch next available task to work on + // TODO: rely on id instead + // TODO: Implement safeguard logic checks to pull only the tickets that haven't complete the range of renders yet. + let result = query_as!( + TicketDAO, + r"SELECT id, job_id, job, start, end FROM ticket LIMIT 1" + ) + .fetch_optional(&self.conn) + .await + .map_err(TicketError::DatabaseError)?; + Ok(result.map(|d| Some(d.dto_to_task())).unwrap_or(None)) + } + + async fn list_tickets(&self) -> Result>, TicketError> { + let result = sqlx::query_as!( + TicketDAO, + r" + SELECT id, job_id, job, start, end + FROM ticket + LIMIT 10 + " + ) + .fetch_all(&self.conn) + .await; + + match result { + Ok(list) => Ok(Some(list.iter().map(|d| d.clone().dto_to_task()).collect())), + Err(e) => Err(TicketError::DatabaseError(e)), + } + } + + async fn delete_ticket(&self, id: &Uuid) -> Result<(), TicketError> { + let _ = sqlx::query(r"DELETE FROM ticket WHERE id = $1") + .bind(id.to_string()) + .execute(&self.conn) + .await; + Ok(()) + } + + async fn delete_job_ticket(&self, job_id: &Uuid) -> Result<(), TicketError> { + let _ = sqlx::query(r"DELETE FROM ticket WHERE job_id = $1") + .bind(job_id.to_string()) + .execute(&self.conn) + .await; + Ok(()) + } +} diff --git a/src-tauri/src/services/data_store/sqlite_worker_store.rs b/src-tauri/src/services/data_store/sqlite_worker_store.rs index fc429f24..24a50890 100644 --- a/src-tauri/src/services/data_store/sqlite_worker_store.rs +++ b/src-tauri/src/services/data_store/sqlite_worker_store.rs @@ -1,87 +1,92 @@ +use std::str::FromStr; + +use libp2p::PeerId; +use serde::{Deserialize, Serialize}; +use sqlx::{FromRow, SqlitePool}; + use crate::{ - domains::worker_store::WorkerStore, - models::{ - computer_spec::ComputerSpec, - worker::{Worker, WorkerError}, - }, + domains::worker_store::{WorkerError, WorkerStore}, + models::{computer_spec::ComputerSpec, worker::Worker}, }; -use sqlx::{prelude::FromRow, query, SqlitePool}; pub struct SqliteWorkerStore { conn: SqlitePool, } +#[derive(FromRow, Serialize, Deserialize, Debug)] +struct WorkerDTO { + peer_id: String, + // TODO: find a way to use #[sqlx(json)]? + spec: String, // deserialize/serialize as json +} + +impl WorkerDTO { + pub fn dto_to_obj(&self) -> Worker { + let peer_id = PeerId::from_str(&self.peer_id).expect("ID was mutated!"); + let spec = serde_json::from_str::(&self.spec).expect("spec was mutated!"); + Worker { peer_id, spec } + } +} + impl SqliteWorkerStore { pub fn new(conn: SqlitePool) -> Self { Self { conn } } } -#[derive(FromRow)] -struct WorkerDb { - machine_id: String, - spec: String, -} - #[async_trait::async_trait] impl WorkerStore for SqliteWorkerStore { // List async fn list_worker(&self) -> Result, WorkerError> { // we'll add a limit here for now. - let sql = r"SELECT machine_id, spec FROM workers LIMIT 255"; - sqlx::query_as(sql) - .fetch_all(&self.conn) - .await - .map_err(|e| WorkerError::Database(e.to_string())) - .and_then(|r: Vec| { - Ok(r.into_iter() - .map(|r: WorkerDb| { - let spec: ComputerSpec = serde_json::from_str(&r.spec).unwrap(); - Worker::new(r.machine_id, spec) - }) - .collect::>()) - }) + let result: Vec = + sqlx::query_as!(WorkerDTO, r"SELECT peer_id, spec FROM workers") + .fetch_all(&self.conn) + .await + .map_err(|e| WorkerError::Database(e.to_string()))?; + + Ok(result.iter().map(|e| e.dto_to_obj()).collect()) } // Create async fn add_worker(&mut self, worker: Worker) -> Result<(), WorkerError> { - let spec = serde_json::to_string(&worker.spec).unwrap(); - + let id = worker.peer_id.to_base58(); + let spec = serde_json::to_string(&worker.spec).expect("Fail to parse specs"); + // TODO: Update the record if it exist by marking it status "Active", relearn SQL again? if let Err(e) = sqlx::query( r" INSERT INTO workers (machine_id, spec) VALUES($1, $2); ", ) - .bind(worker.machine_id) + .bind(id) .bind(spec) .execute(&self.conn) .await { - eprintln!("{e}"); + eprintln!("Fail to insert new worker: {e}"); } Ok(()) } // Read - async fn get_worker(&self, id: &str) -> Option { - match query!( - r#"SELECT machine_id, spec FROM workers WHERE machine_id=$1"#, - id, + async fn get_worker(&self, id: &PeerId) -> Option { + let peer_id = id.to_base58(); + // Is there a way I could do optional instead of result? + let result: Result = sqlx::query_as!( + WorkerDTO, + r#"SELECT peer_id, spec FROM workers WHERE peer_id=$1"#, + peer_id ) .fetch_one(&self.conn) - .await - { - Ok(worker) => { - let spec = - serde_json::from_str::(&String::from_utf8(worker.spec).unwrap()) - .unwrap(); - Some(Worker::new(worker.machine_id, spec)) - } + .await; + + match result { + Ok(data) => Some(data.dto_to_obj()), Err(e) => { - eprintln!("{:?}", e.to_string()); - return None; + eprintln!("SQLx generated an error: {e:?}"); + None } } } @@ -89,11 +94,25 @@ impl WorkerStore for SqliteWorkerStore { // no update? // Delete - async fn delete_worker(&mut self, machine_id: &str) -> Result<(), WorkerError> { - let _ = sqlx::query(r"DELETE FROM workers WHERE machine_id = $1") - .bind(machine_id) + async fn delete_worker(&mut self, id: &PeerId) -> Result<(), WorkerError> { + let peer_id = id.to_base58(); + // TODO: mark the worker inactive instead. + let _ = sqlx::query!(r"DELETE FROM workers WHERE peer_id = $1", peer_id) + // my mind goes on a brainfart moment overcomplicating simplification and data requirement. + // should status be a enum type, then should it be a string instead? + // let _ = sqlx::query!("UPDATE workers SET status=false, ") + // .bind(peer_id) .execute(&self.conn) .await; Ok(()) } + + // Clear worker table + async fn clear_worker(&mut self) -> Result<(), WorkerError> { + let _ = sqlx::query(r"DELETE FROM workers") + .execute(&self.conn) + .await + .map_err(|e| WorkerError::Database(e.to_string()))?; + Ok(()) + } } diff --git a/src-tauri/src/services/mod.rs b/src-tauri/src/services/mod.rs index 48ef5c17..46046997 100644 --- a/src-tauri/src/services/mod.rs +++ b/src-tauri/src/services/mod.rs @@ -1,4 +1,5 @@ pub mod blend_farm; -pub mod cli_app; +pub mod server; pub mod data_store; pub mod tauri_app; +pub(crate) mod app_context; \ No newline at end of file diff --git a/src-tauri/src/services/server.rs b/src-tauri/src/services/server.rs new file mode 100644 index 00000000..1741cbd9 --- /dev/null +++ b/src-tauri/src/services/server.rs @@ -0,0 +1,562 @@ +/* +Have a look into TUI for CLI status display window to show user entertainment on screen +https://docs.rs/tui/latest/tui/ + +Feature request: + - See how we can treat this application process as service mode so that it can be initialize and start on machine reboot? + - receive command to properly reboot computer when possible? +*/ +use super::blend_farm::BlendFarm; +use crate::domains::render_store::RenderStore; +use crate::domains::ticket_store::{TicketError, TicketStore}; +use crate::models::computer_spec::ComputerSpec; +use crate::models::job::JobId; +use crate::network::PeerIdString; +use crate::network::message::{self, Event, NetworkError}; +use crate::network::provider_rule::ProviderRule; +use crate::services::app_context::AppContext; +use crate::services::data_store::sqlite_renders_store::SqliteRenderStore; +use crate::services::data_store::sqlite_ticket_store::SqliteTicketStore; +use crate::{ + models::{ + job::Job, + server_setting::ServerSetting, + ticket::Ticket, + }, + network::controller::Controller as NetworkController, +}; +use blender::blender::{Blender, Frame, Manager as BlenderManager, ManagerError}; +use blender::models::event::BlenderEvent; +use semver::Version; +use serde::{Deserialize, Serialize}; +use sqlx::{Pool, Sqlite}; +use std::collections::HashMap; +use std::path::PathBuf; +use tauri::async_runtime::Receiver; +use thiserror::Error; +use tokio::sync::{mpsc, oneshot}; +use tokio::{select, spawn}; +use uuid::Uuid; + +// this is invocation commands. Signal to start, stop, fetch blender information and relative info. +enum ServerCommand { + Start, + AddTask(Ticket), + DeleteTask(Uuid), + CheckBlender(String), // Name of the blender in compressed package enum. (e.g. "blender-5.0.0-linux-x64.tar.xz") + // this function seems confusing. Refine this a bit letter. + Fetch(JobId, oneshot::Sender>), + Abort, +} + +// Must be serializable to send data across network +// issue with this is that this cannot be convert into Encode,Decode by bincode. Instead we'll have to +#[derive(Debug, Serialize, Deserialize)] +pub enum ServerEvent { + Online(PeerIdString, ComputerSpec), + // Network received a disconnected signal from peer_id. + Disconnected { + peer_id: PeerIdString, + reason: Option, + }, + Rendering(Uuid), + BlenderStatus(BlenderEvent), + Idle, // waiting for task +} + +#[derive(Debug, Error)] +enum ServerError { + #[error("Encounter an network error! \n{0:}")] + NetworkError(#[from] message::NetworkError), + #[error("Encounter an IO error! \n{0}")] + Io(#[from] async_std::io::Error), + #[error("Manager Error: {0}")] + ManagerError(#[from] ManagerError), + #[error("Task Error: {0}")] + TaskError(#[from] TicketError) +} + +/// The behaviour described in the Cli App can be summarize below: +/// When running with listening server, client will spin a thread to listen for network messages. +/// Cli as a listening server can accept Request Task from available host. +/// The host can ask you about your task's progress, and how many image you've completed. +/// Additionally, the host may also request the images from you. +/// When running in pure cli mode, you can ask to fetch information and create new task from local machine. +/// This will let cli mode run the job in batch mode, customized for your experience. +/// and simply closes out. +/// This will be useful for blender add-on interface, we want to be able to invoke client/host commands from blender application, as an alternative solution. +pub struct Server { + manager: BlenderManager, + + // database connection + db_conn: Pool, + + // config + settings: ServerSetting, + + // current server specs + pub spec: ComputerSpec, +} + +// cli app should really be a stateless machine. A listener would just receive order from the network and proceed the task given queued. +// This program should close after completing the task queue, in non-listening mode +impl Server { + + pub(crate) fn new(context: AppContext, db: &Pool) -> Self { + Self { + settings: context.settings, + manager: context.manager, + db_conn: db.clone(), + spec: ComputerSpec::new(), + } + } + + // This function will ensure the directory will exist, and return the path to that given directory. + // It will remain valid unless directory or parent above is removed during runtime. + async fn generate_temp_project_task_directory( + settings: &ServerSetting, + task: &Ticket, + id: &str, + ) -> Result { + // create a path link where we think the file should be + let job = AsRef::::as_ref(&task); + let project_path = settings + .blend_dir + .join(id.to_string()) + .join(&job.get_file_name_expected()); + + // we only want the parent directory to exist. + match async_std::fs::create_dir_all(&project_path.parent().expect("I wouldn't think we'd be trying to check files in root? Please write a bug report and replicate step by step to reproduce the issue")).await { + Ok(_) => Ok(project_path), + Err(e) => { + Err(e) + } + } + } + + #[allow(dead_code)] + async fn validate_project_file( + &self, + client: &mut NetworkController, + task: &Ticket, + ) -> Result { + let id = AsRef::::as_ref(&task); + let project_file_path = + Server::generate_temp_project_task_directory(&self.settings, &task, &id.to_string()) + .await + .expect("Should have permission!"); + + // assume project file is located inside this directory. + println!("Checking for {:?}", &project_file_path); + + let job = AsRef::::as_ref(&task); + // Fetch the project from peer if we don't have it. + if !project_file_path.exists() { + println!( + "calling network for project file, asking to download from DHT: {:?}", + &job.get_file_name_expected() + ); + + let file_name = job.get_file_name_expected().clone().to_str().expect("Must have a string!"); + let providers = client.get_providers(file_name).await; + + // TODO: Find a way to implement network partition to break up files chunks for parallel network transfer. + + + let search_directory = project_file_path + .parent() + .expect("Shouldn't be anywhere near root level?"); + + // so I need to figure out something about this... + // TODO - find a way to break out of this if we can't fetch the project file. + let job = AsRef::::as_ref(&task); + let file_name = job.get_file_name_expected().to_string_lossy(); + + // TODO: To receive the path or not to modify existing project_file value? I expect both would have the same value? + let path = client + .get_file_from_peers(&file_name, search_directory) + .await + .map_err(ServerError::NetworkError)?; + return Ok(path); + } + + Ok(project_file_path) + } + + async fn verify_and_check_render_output_path( + &self, + id: &Uuid, + ) -> Result { + // create a output destination for the render image + let output = self.settings.render_dir.join(&id.to_string()); + async_std::fs::create_dir_all(&output).await?; + Ok(output) + } + + // Originally designed to be used to check blender version across network. + // TODO: Future work - Implement a pattern that + // A) Search the network if exact version of blender exist. + // B) If the network have similar or newer patches than target version + // C) Check local if exact or newer version exist + // D) Second to last resort: Download blender from internet + // E) Throw error that no blender installation could be fetch or found for this task. + #[allow(dead_code)] + async fn check_for_blender(&self, version: &Version) -> Result<&Blender, ServerError> { + // this script below was our internal implementation of handling DHT fallback mode + // save this for future feature updates + let blender = match self.manager.have_blender(version) { + Some(blend) => blend, + None => { + // when I do not have task blender version installed - two things will happen here before an error is thrown + // First, check our internal DHT services to see if any other client on the network have matching version - then fetch it. Install after completion + // Secondly, download the file online. + // If we reach here - it is because no other node have matching version, and unable to connect to download url (Internet connectivity most likely). + // TODO: It would be nice to broadcast everyone else "Hey! I'm download this version, could you wait until I'm done to distribute?" + panic!("Finish implementing this part"); + /* + let destination = self.manager.get_install_path(); + + + // should also use this to send CmdCommands for network stuff. + // where did this client come from? + let latest = self + .client + .get_file_from_peers(&link_name, destination) + .await; + + match latest { + Ok(path) => { + // assumed the file I downloaded is already zipped, proceed with caution on installing. + let folder_name = self.manager.get_install_path(); + let exe = + DownloadLink::extract_content(path, folder_name.to_str().unwrap()) + .expect( + "Unable to extract content, More likely a permission issue?", + ); + &Blender::from_executable(exe).expect("Received invalid blender copy!") + } + Err(e) => { + println!( + "No client on network is advertising target blender installation! {e:?}" + ); + &self + .manager + .fetch_blender(&version) + .expect("Fail to download blender") + } + } + */ + } + }; + Ok(blender) + } + + // TODO: This will change. We will treat network user as end point of cli interfaces to this app. + // Received network command. + // Obsolete. We should not rely on network asking us to render. + // not yet? + /* + async fn handle_job_from_network(&mut self, client: &Controller, event: JobEvent) { + // with the sqlite connection we can create and establish database struct here. + + match event { + // on render task received, we should store this in the database. + JobEvent::Render(peer_id_str, mut task) => { + let peer_id = match PeerId::from_str(&peer_id_str) { + Ok(peer_id) => peer_id, + Err(e) => { + eprintln!("Not a valid peer id! {e:?}"); + return; + } + }; + + if client.public_id.ne(&peer_id) { + return; + } + + // TODO: Does this kick off a background job? How do we let this program continue without hang? Threads? + if let Err(e) = &self.handle_render(&peer_id, &mut task, &client).await { + eprintln!("Received Error! {e:?}"); + } + } + + JobEvent::ImageCompleted { .. } => {} // ignored since we do not want to capture image? + // For future impl. we can take advantage about how we can allieve existing job load. E.g. if I'm still rendering 50%, try to send this node the remaining parts? + JobEvent::TaskComplete => {} // Ignored, we're treated as a client node, waiting for new job request. + // Remove all task with matching job id. + JobEvent::Remove(job_id) => { + let task_store = SqliteTaskStore::new(self.db_conn.clone()); + let db = &task_store; + if let Err(e) = db.delete_job_task(&job_id).await { + eprintln!("Unable to remove all task with matching job id! {e:?}"); + } + // Find a way to check and see if we are running any task that matches target job_id and stop the blender sequence immediately. + } + _ => println!("Unhandle Job Event: {event:?}"), + } + } + */ + + // Handle network event (Receiving network messages) + // async fn handle_net_event( + // // TODO: Remove self. Make this not dependent on cli struct (Use caller to send cmd commands) + // // &mut self, + // // network controller + // client: &Controller, + // // received network event + // event: Event, + // // used to interface cli background workers + // caller: Sender + // ) -> Result<(), NetworkError> { + // match event + // Ok(()) + // } + + // Take action from interface. (CLI mode) + async fn handle_command( + &mut self, + client: &NetworkController, + cmd: ServerCommand, + ) -> Result<(), NetworkError> { + // More to come soon. Just making it work for now is bare minimum. + match cmd { + ServerCommand::AddTask(ticket) => { + let ticket_db = SqliteTicketStore::new(self.db_conn.clone()); + ticket_db.add_ticket(ticket).await; + }, + // how does abort works? We'll come back to this later. + ServerCommand::Abort => { + // An abort was called. Stop blender. + todo!("Impl. cancellation token"); + }, + + ServerCommand::Fetch(job_id, sender) => { + // returns a hashset of all render frames from matching job. + // Inner join tasks inner join renders + // basically providing basic information to client what frames have been completed. + let render_db = SqliteRenderStore::new(self.db_conn.clone()); + if let Ok(result) = render_db.find(Some(job_id)).await { + sender.send(result); + } + }, + ServerCommand::Start => { + self.process_tickets(&client); + () + }, + ServerCommand::DeleteTask(uuid) => todo!(), + // TODO: Consider adding the peer_id that called this function + ServerCommand::CheckBlender(zip_file_name) => { + // ok so we get manager and fetch the package that matches file name asking + if let Some(path) = self.manager.check_compressed_by_file_name(&zip_file_name) { + let provider = ProviderRule::Custom(zip_file_name, path); + client.start_providing(&provider).await; + // TODO: reply back to the caller + + } + }, + }; + Ok(()) + } + + async fn process_tickets( + &mut self, + controller: &NetworkController, + ) -> Result, TicketError> { + // run the service here. + let ticket_db = SqliteTicketStore::new(self.db_conn.clone()); + // let render_db = SqliteRenderStore::new(self.db_conn.clone()); + + loop { + select! { + pending_task = &ticket_db.poll_ticket() => match pending_task { + Ok(query) => match query { + Some(record) => { + let mut ticket = record.item; + + // Skip this for now. We'll work on DHT at another time. + // TODO: validate and make sure that we have the files locally stored ready to be used. + // let project_file = match self.validate_project_file(client, &task).await { + // Ok(path) => path, + // Err(e) => { + // eprintln!("Fail to validate project file! {e:?}"); + // return; + // } + // }; + // let project_file = task.get_job().get_project_path(); + + let version = &ticket.job.blender_version; + // TODO: I want to find a way to utilize intranet DHT services to fetch installation from other computer node. It wouldn't make a lot of sense re-download the same version from source multiple of times. + let blender = match self.manager.have_blender(version) { + Some(blender) => blender, + None => { + // Update ticket status to "Error" -> Do not re-run this again until the issue has been resolved. + ticket_db.update(); + + // let (sender, receiver) = mpsc::<>channel(); + todo!("mute the rest to focus on lint issue"); + // &controller. + // Here, we'd like to try and fetch from client first, before we can download. + // &self.manager + // .fetch_blender(&version) + // .map_err(TicketError::Manager)? + } + }; + + // we will get to the part of handling receiver, but I wanted to make sure this works so far. + let _receiver = ticket.render(&blender).await?; + () + } + None => { + println!("No more task found! Setting to idle!"); + () + }, + }, + Err(e) => { + println!("Something went wrong {e:?}"); + () + } + } + } + } + + } +} + +#[async_trait::async_trait] +impl BlendFarm for Server { + /* + Some thoughts: + The Cli App mode should be stateless, e.g. no Idle state. The services that BlendFarm runs on should utilize the necessary components to run blender from network request. + The Cli must have a switch to listen for server connection to become state machines. (TODO: E.g. provide IP and Port) + */ + + /// This program will run into this following state machine: + /// It will continue to poll task from the database and work on the given assignments. + /// The task will be reflected by the host machine once available, and other peers can request tasks, if they're idle. + /// Once exhausted all pending task, this node will send out one RequestTask message to the network and remain idle. + /// It will also send discovered node a RequestTask as well. + /// The background network services will update and monitor the database connection, as well as governs the task lifetime handlers. + /// E.g. A job cancellation notice should terminate ongoing task jobs. Needs a way to interface ongoing thread and abort before resuming next task. + /// Future work: The node can be in a "Paused" state, given under circumstances, that it should await for host's further instructions. + /// E.g. Downloading blender in background. + /// The run command will launch two processes. One process will monitor and receive Blender activity. + /// The other process handles network events. + async fn run( + mut self, + mut client: NetworkController, + mut event_receiver: Receiver, + ) -> Result<(), NetworkError> { + + // I need to find a way to safely notify the background to stop in case the job was deleted from host machine. + // we will have one thread to process blender and queue, but I must have access to database. + let (event, mut command) = mpsc::channel(32); + + // background thread to handle blender invocation + let blender_controller = client.clone(); + + // if we exit early, how do we restart this service? + let task_db = SqliteTicketStore::new(self.db_conn.clone()); + let render_db = SqliteRenderStore::new(self.db_conn.clone()); + let spec = ComputerSpec::new(); + + spawn(async move { + let mut has_started = false; + let id = blender_controller.public_id; + let task_store = SqliteTicketStore::new(self.db_conn); + // loop until we have no more task left to work on. + loop { + select! { + blender_event = &mut self.process_tickets(&blender_controller) => match blender_event { + Ok(receiver) => while let Some(message) = receiver.recv().await { + // if receiver. + // BlenderEvent:: + // match message { + // BlenderEvent::Quit + // } + print!("Processing tickets: {message:?}"); + }, + Err(e) => { + // let server_event = ServerEvent:: + // client.send_node_status(server_event).await; + eprintln!("Something broke: {e:?}"); + break; + } + }, + } + } + + // Once we've exhausted all of the task here, we should send out Request Task message. + blender_controller.send_server_status(ServerEvent::Idle).await; + }); + + // Process commands inputs + // This will be moved somewhere else. + loop { + select! { + pending_event = event_receiver.recv() => match pending_event { + Some(network_event) => match network_event { + Event::Discovered(peer_id, multiaddr) => { + // I don't think I need to care about this? + println!("Discovered peer! {peer_id} | {multiaddr}"); + } + Event::JobUpdate(job_event) => { + println!("Received Job Event: {job_event:?}") + // caller + //self.handle_job_from_network(client, job_event).await, + }, + Event::InboundRequest { request, channel } => { + Self::handle_inbound_request(&client, request, channel).await + } + Event::ServerStatus(event) => { + match event { + + ServerEvent::Online(peer_id, spec) => { + // peer connected with specs. + // Once a computer becomes online, do nothing? + + println!("Peer connected with specs provided : {peer_id:?}\n{spec:?}"); + // if we are not connected to host, connect to this one. await further instructions. + // TODO: See where my multiaddr went? + // self.host = Some((PeerIdStr::from(peer_id), multiaddr)); + + // let public_ip = client.public_id.to_base58(); + // let mut machine = Machine::new(); + // let computer_spec = ComputerSpec::new(&mut machine); + // let status = NodeEvent::Hello(public_ip, computer_spec); + // client.send_node_status(status).await; + } + ServerEvent::Disconnected { peer_id, reason } => match reason { + Some(err) => { + // Reporting that we lost connection to peer_id by a connection IO error + println!("Peer Disconnected with reason [{peer_id:?}] {err}"); + // what shall the server ever do? Do we care? No? + } + None => println!("Peer Disconnected without reason! [{peer_id:?}]"), + }, + ServerEvent::BlenderStatus(_blender_event) => { + // println!("[Blender Status] {blender_event:?}"); + // probably doesn't matter, but shouldn't spam the network with this info yet... + }, + ServerEvent::Idle => { + eprintln!("A node has entered idle state... We should probably give that node some job to work on..."); + } + } + } + _ => println!("[Server] Unhandled event received from network: {event:?}"), + }, + None => { + // pipe was closed, begin shut down. + // TODO: See how we can gracefully shutdown? + () + } + + }, + // can I send this command to net event? + msg = command.recv() => match msg { + Some(cmd) => Self::handle_command(&client, cmd).await?, + None => (), + }, + } + } + } +} diff --git a/src-tauri/src/services/tauri_app.rs b/src-tauri/src/services/tauri_app.rs index 212a122f..3cc89f47 100644 --- a/src-tauri/src/services/tauri_app.rs +++ b/src-tauri/src/services/tauri_app.rs @@ -1,179 +1,166 @@ -use super::blend_farm::BlendFarm; +/* DEV Blog + + Issue: files provider are stored in memory, and do not recover after application restart. + - mitigate this by using a persistent storage solution instead of memory storage. + + Issue: Cannot debug this application unless it is built completely. See if there's a way to run debug mode without building the app entirely. +*/ + +use super::{ + blend_farm::BlendFarm, + data_store::{sqlite_job_store::SqliteJobStore, sqlite_worker_store::SqliteWorkerStore}, +}; +use crate::network::controller::Controller as NetworkController; +use crate::network::message::{Event, NetworkError, ServerEvent}; +use crate::network::provider_rule::ProviderRule; use crate::{ - domains::{job_store::JobStore, worker_store::WorkerStore}, + domains::{ + job_store::{JobError, JobStore}, + worker_store::WorkerStore, + }, models::{ app_state::AppState, + blender_action::BlenderAction, computer_spec::ComputerSpec, - job::{Job, JobEvent}, - message::{NetEvent, NetworkError}, - network::{NetworkController, HEARTBEAT, JOB, SPEC, STATUS}, + job::{CreatedJobDto, JobAction, JobEvent}, server_setting::ServerSetting, - task::Task, + setting_action::SettingsAction, + ticket::Ticket, worker::Worker, }, - routes::{job::*, remote_render::*, settings::*, util::*, worker::*}, + routes::{index::*, job::*, remote_render::*, settings::*, util::*, worker::*}, }; -use blender::manager::Manager as BlenderManager; -use blender::models::mode::Mode; -use libp2p::PeerId; -use maud::html; -use serde::Serialize; -use std::{collections::HashMap, ops::Range, sync::Arc}; -use std::{path::PathBuf, thread::sleep, time::Duration}; -use tauri::{self, command, App, AppHandle, Emitter, Manager}; -use tokio::{ - select, spawn, - sync::{ - mpsc::{self, Receiver, Sender}, - Mutex, RwLock, - }, +use bitflags; +use blender::{ + blend_file::BlendFile, manager::Manager as BlenderManager, models::mode::RenderMode, +}; +use futures::{ + SinkExt, StreamExt, + channel::mpsc::{self, Sender}, }; -use uuid::Uuid; +use libp2p::PeerId; +use semver::Version; +use sqlx::{Pool, Sqlite}; +use std::{collections::HashMap, path::PathBuf, str::FromStr}; +use tauri::{self, Url}; +use tokio::sync::mpsc::Receiver; +use tokio::{select, spawn, sync::Mutex}; + +bitflags::bitflags! { + #[derive(Debug, PartialEq)] + pub struct QueryMode: u8 { + const LOCAL = 0x1; + const ONLINE = 0x2; + } +} -pub const WORKPLACE: &str = "workplace"; +#[derive(Debug, PartialEq)] +pub enum Origin { + Local(PathBuf), + Online(Url), +} -// This UI Command represent the top level UI that user clicks and interface with. #[derive(Debug)] -pub enum UiCommand { - StartJob(Job), - StopJob(Uuid), - UploadFile(PathBuf, String), - RemoveJob(Uuid), +pub struct BlenderQuery { + pub version: Version, + pub origin: Origin, } -// TODO: make this user adjustable. -const MAX_BLOCK_SIZE: i32 = 30; +impl BlenderQuery { + pub fn is_install_locally(&self) -> bool { + match self.origin { + Origin::Local(_) => true, + _ => false, + } + } -pub struct TauriApp { - // I need the peer's address? - peers: HashMap, - worker_store: Arc>, - job_store: Arc>, + pub fn link(&self) -> String { + match &self.origin { + // TODO: Find a way to resolve expect() + Origin::Local(path) => path.to_str().expect("Should be valid").to_owned(), + Origin::Online(url) => url.to_string().to_owned(), + } + } } -#[derive(Clone, Serialize)] -struct FrameUpdatePayload { - id: Uuid, - frame: i32, - file_name: String, +#[derive(Debug)] +pub enum WorkerAction { + Get(PeerId, Sender>), + List(Sender>>), } -#[command] -pub fn index() -> String { - html! ( - div { - div class="sidebar" { - nav { - ul class="nav-menu-items" { - li key="manager" class="nav-bar" tauri-invoke="remote_render_page" hx-target=(format!("#{WORKPLACE}")) { - span { "Remote Render" } - }; - li key="setting" class="nav-bar" tauri-invoke="setting_page" hx-target=(format!("#{WORKPLACE}")) { - span { "Setting" } - }; - }; - }; - div { - h2 { "Computer Nodes" }; - div class="group" id="workers" tauri-invoke="list_workers" hx-trigger="every 2s" hx-target="this" {}; - }; - }; - - main tauri-invoke="remote_render_page" hx-trigger="load" hx-target="this" id=(WORKPLACE) {}; +impl PartialEq for WorkerAction { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Get(l0, ..), Self::Get(r0, ..)) => l0 == r0, + (Self::List(..), Self::List(..)) => true, + _ => false, } - ).0 + } +} + +#[derive(Debug, PartialEq)] +pub enum UiCommand { + Job(JobAction), + UploadFile(PathBuf), + Worker(WorkerAction), + Settings(SettingsAction), + Blender(BlenderAction), +} + +pub struct TauriApp { + // I need the peer's address? I don't think I need the PeerId, but will hold onto it just in case. + // we may ultimately change this to rely on the computer name instead of PeerId? + peers: HashMap, + worker_store: SqliteWorkerStore, + job_store: SqliteJobStore, + settings: ServerSetting, + manager: BlenderManager, } impl TauriApp { - pub async fn new( - worker_store: Arc>, - job_store: Arc>, - ) -> Self { + // Clear worker database before usage! + pub async fn clear_workers_collection(mut self) -> Self { + if let Err(e) = self.worker_store.clear_worker().await { + eprintln!("Error clearing worker database! {e:?}"); + } + self + } + + pub fn new(manager: BlenderManager, pool: &Pool) -> Self { Self { peers: Default::default(), - worker_store, - job_store, + worker_store: SqliteWorkerStore::new(pool.clone()), + job_store: SqliteJobStore::new(pool.clone()), + settings: ServerSetting::load(), + manager, } } // Create a builder to make Tauri application - fn config_tauri_builder(&self, to_network: Sender) -> Result { - // I would like to find a better way to update or append data to render_nodes, - // "Do not communicate with shared memory" - let builder = tauri::Builder::default() + // Let's just use the controller in here anyway. + pub fn init_tauri_plugins(builder: tauri::Builder) -> tauri::Builder { + builder .plugin(tauri_plugin_cli::init()) .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_fs::init()) - .plugin(tauri_plugin_sql::Builder::default().build()) .plugin(tauri_plugin_persisted_scope::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_dialog::init()) - .setup(|_| Ok(())); - - let manager = Arc::new(RwLock::new(BlenderManager::load())); - let setting = Arc::new(RwLock::new(ServerSetting::load())); - - // here we're setting the sender command to app state before the builder. - let app_state = AppState { - manager, - to_network, - setting, - job_db: self.job_store.clone(), - worker_db: self.worker_store.clone(), - }; - - let mut_app_state = Mutex::new(app_state); - - builder - .manage(mut_app_state) - .invoke_handler(tauri::generate_handler![ - index, - open_path, - select_directory, - select_file, - create_job, - delete_job, - get_job, - setting_page, - edit_settings, - get_settings, - update_settings, - create_new_job, - available_versions, - remote_render_page, - list_workers, - list_jobs, - get_worker, - import_blend, - add_blender_installation, - list_blender_installed, - remove_blender_installation, - fetch_blender_installation, - ]) - .build(tauri::generate_context!()) } - // because this is async, we can make our function wait for a new peers available. - async fn get_idle_peers(&self) -> PeerId { - // this will destroy the vector anyway. - // TODO: Impl. Round Robin or pick first idle worker, whichever have the most common hardware first in query? - // This code doesn't quite make sense, at least not yet? - loop { - if let Some((peer, ..)) = self.peers.clone().into_iter().nth(0) { - return peer; - } - sleep(Duration::from_secs(1)); - } - } - - fn generate_tasks(job: &Job, file_name: PathBuf, chunks: i32, requestor: PeerId) -> Vec { + // The idea here is to generate new task based on job creation. + // TODO: Explain the expect behaviour for this method before reference it. + #[allow(dead_code)] + fn generate_tasks(job: &CreatedJobDto, chunks: i32) -> Vec { // mode may be removed soon, we'll see? - let (time_start, time_end) = match &job.mode { - Mode::Animation(anim) => (anim.start, anim.end), - Mode::Frame(frame) => (frame.clone(), frame.clone()), + let (time_start, time_end) = match AsRef::::as_ref(&job.item) { + RenderMode::Animation { start, end } => (start, end), + RenderMode::Frame(frame) => (frame, frame), }; // What if it's in the negative? e.g. [-200, 2 ] ? would this result to -180 and what happen to the equation? + // ^^^^ TODO: This is a good example for unit test! let step = time_end - time_start; let max_step = step / chunks; let mut tasks = Vec::with_capacity(max_step as usize); @@ -191,166 +178,285 @@ impl TauriApp { let end = block + chunks; let end = match end.cmp(&time_end) { std::cmp::Ordering::Less => end, - _ => time_end, + _ => time_end.to_owned(), }; - let range = Range { start, end }; - - let task = Task::new( - requestor, - job.id, - file_name.clone(), - job.get_version().clone(), - range, - ); + + // TODO: Find a way to handle this error. + // It should only error if we don't have permission to temp cache storage location + let task = + Ticket::from(job.clone(), start, end).expect("Should be able to create task!"); tasks.push(task); } tasks } - // command received from UI - async fn handle_command(&mut self, client: &mut NetworkController, cmd: UiCommand) { - match cmd { - // TODO: This may subject to change. - // Issue: What if the app restarts? We no longer provide the file after reboot. - UiCommand::StartJob(job) => { - // first make the file available on the network - let file_name = job.project_file.file_name().unwrap(); - let path = job.project_file.clone(); - - client - .start_providing(file_name.to_str().unwrap().to_string(), path) - .await; + async fn handle_job_command(&mut self, job_action: JobAction, client: &mut NetworkController) { + match job_action { + JobAction::Find(job_id, mut sender) => { + let result = self.job_store.get_job(&job_id).await; + match result { + Ok(record) => { + if let Err(e) = sender.send(record).await { + eprintln!("unable to send record back! \n{e:?}"); + } + } + Err(e) => eprintln!("Job store reported an error: {e:?}"), + }; + } + JobAction::Update(job) => { + // as long as the uuid exist in the database, we should be fine to update the job entry. + let result = self.job_store.update_job(job).await; + if let Err(e) = result { + eprintln!("Fail to update job! {e:?}"); + } + } + JobAction::Create(job, mut sender) => { + let result = self.job_store.add_job(job).await; - let tasks = Self::generate_tasks( - &job, - PathBuf::from(file_name), - MAX_BLOCK_SIZE, - client.public_id.clone(), - ); - - // so here's the culprit. We're waiting for a peer to become idle and inactive waiting for the next job - for task in tasks { - let peer = self.get_idle_peers().await; // this means I must wait for an active peers to become available? - let event = JobEvent::Render(task); - client.send_job_message(peer, event).await; + match result { + Ok(job) => { + sender.send(Ok(job)).await.expect("Should not drop"); + } + Err(e) => { + sender + .send(Err(JobError::DatabaseError(e.to_string()))) + .await + .expect("Should not drop"); + } + }; + } + JobAction::Kill(job_id) => { + if let Err(e) = self.job_store.delete_job(&job_id).await { + eprintln!("Receiver/sender should not be dropped! {e:?}"); } + client.send_job_event(JobEvent::Remove(job_id)).await; } - UiCommand::UploadFile(path, file_name) => { - client.start_providing(file_name, path).await; + JobAction::AskForCompletedList(job_id) => { + // here we will try and send out network node asking for any available client for the list of completed frame images. + let event = JobEvent::AskForCompletedJobFrameList(job_id); + client.send_job_event(event).await; } - UiCommand::StopJob(id) => { - println!( - "Impl how to send a stop signal to stop the job and remove the job from queue {id:?}" - ); + JobAction::All(mut sender) => { + /* + There's something wrong with this datastructure. + On first call, this command works as expected, + however additional call afterward does not let this function continue or invoke? + I must be waiting for something here? + */ + // TODO: Consider looking into using Iter() mutations. + let result = match self.job_store.list_all().await { + Ok(jobs) => { + if jobs.is_empty() { + None + } else { + Some(jobs) + } + } + Err(e) => { + eprintln!("Unable to send list of jobs: {e:?}"); + None + } + }; + + if let Err(e) = sender.send(result).await { + eprintln!("Fail to send data back! {e:?}"); + } } - UiCommand::RemoveJob(id) => { - for (peer, _) in self.peers.clone() { - client.send_job_message(peer, JobEvent::Remove(id)).await; + + // Nothing is calling this yet??? + JobAction::Advertise(job_id) => + // Here we will simply add the job to the database, and let client poll them! + { + let result = match self.job_store.get_job(&job_id).await { + Ok(job) => job, + Err(e) => { + eprintln!("No Job record found! Skipping! {e:?}"); + return (); + } + }; + + // first make the file available on the network + if let Some(job) = result { + let project_file: &BlendFile = job.item.as_ref(); + let file_name = project_file + .to_path() + .file_name() + .expect("Must have a valid blender file name!"); // this is &OsStr + let path: &PathBuf = job.item.as_ref(); + + println!("Reached to this point of code {file_name:?}"); + + // Once job is initiated, we need to be able to provide the files for network distribution. + let _provider = ProviderRule::Default(path.to_path_buf()); + // this is where I'm confused? + // if let Err(e) = client.start_providing(&provider).await { + // eprintln!("Fail to provide file! {e:?}"); + // return; + // } + + // let tasks = Self::generate_tasks( + // &job, + // MAX_FRAME_CHUNK_SIZE + // ); + + // // so here's the culprit. We're waiting for a peer to become idle and inactive waiting for the next job + // for task in tasks { + // // problem here - I'm getting one client to do all of the rendering jobs, not the inactive one. + // // Perform a round-robin selection instead. + + // println!("Sending task to {:?} \nRange( {} - {} )\n", &host, &task.range.start, &task.range.end); + // client.send_job_event(Some(host.clone()), JobEvent::Render(task)).await; + // } } } } } - // commands received from network - async fn handle_net_event( - &mut self, - client: &mut NetworkController, - event: NetEvent, - // This is currently used to receive worker's status update. We do not want to store this information in the database, instead it should be sent only when the application is available. - app_handle: Arc>, - ) { - match event { - NetEvent::Status(peer_id, msg) => { - // this may soon change. - let handle = app_handle.read().await; - handle - .emit("node_status", (peer_id.to_base58(), msg)) - .unwrap(); + async fn handle_blender_command(&mut self, blender_action: BlenderAction) { + match blender_action { + BlenderAction::Add(_blender) => { + todo!("impl adding blender?"); } - NetEvent::NodeDiscovered(peer_id, spec) => { - let worker = Worker::new(peer_id.to_base58(), spec.clone()); - let mut db = self.worker_store.write().await; - if let Err(e) = db.add_worker(worker).await { - eprintln!("Error adding worker to database! {e:?}"); + BlenderAction::List(mut sender, flags) => { + let mut versions = Vec::new(); + + if flags.contains(QueryMode::LOCAL) { + let mut localblenders = self + .manager + .get_blenders() + .iter() + .map(|b| BlenderQuery { + version: b.get_version().to_owned(), + origin: Origin::Local(b.get_executable().into()), + }) + .collect::>(); + versions.append(&mut localblenders); } - self.peers.insert(peer_id, spec); - // let handle = app_handle.write().await; - // emit a signal to query the data. - // TODO: See how this can be done: https://github.com/ChristianPavilonis/tauri-htmx-extension - // let _ = handle.emit("worker_update"); - } - NetEvent::NodeDisconnected(peer_id) => { - let mut db = self.worker_store.write().await; - if let Err(e) = db.delete_worker(&peer_id.to_base58()).await { - eprintln!("Error deleting worker from database! {e:?}"); + // then display the rest of the download list + // TODO: Figure out why fetch_download_list() takes awhile to query the data. + // I expect the cache should fetch the info and provide that information rather than querying the internet + // everytime this function is called. + if flags.contains(QueryMode::ONLINE) { + let mut item = self.manager.get_online_version().iter().fold( + Vec::new(), + |mut map, (url, version)| { + let item = BlenderQuery { + version: version.clone(), + origin: Origin::Online(url.clone()), + }; + map.push(item); + map + }, + ); + versions.append(&mut item); } - self.peers.remove(&peer_id); - // let handle = app_handle.write().await; - // let _ = handle.emit("worker_update", ()); - } - NetEvent::InboundRequest { request, channel } => { - if let Some(path) = client.providing_files.get(&request) { - client - .respond_file(std::fs::read(path).unwrap(), channel) - .await + // send the collective list result back + if let Err(e) = sender.send(Some(versions)).await { + eprintln!("Fail to send back list of blenders to caller! {e:?}"); } } - NetEvent::JobUpdate(.., job_event) => match job_event { - // when we receive a completed image, send a notification to the host and update job index to obtain the latest render image. - JobEvent::ImageCompleted { - job_id: id, - frame, - file_name, - } => { - // create a destination with respective job id path. - let destination = client.settings.render_dir.join(id.to_string()); - if let Err(e) = async_std::fs::create_dir_all(destination.clone()).await { - println!("Issue creating temp job directory! {e:?}"); - } - - let handle = app_handle.write().await; - if let Err(e) = handle.emit( - "frame_update", - FrameUpdatePayload { - id, - frame, - file_name: file_name.clone(), - }, - ) { - eprintln!("Unable to send emit to app handler\n{e:?}"); - } - - // Fetch the completed image file from the network - if let Ok(file) = client.get_file_from_peers(&file_name, &destination).await { - let handle = app_handle.write().await; - if let Err(e) = handle.emit("job_image_complete", (id, frame, file)) { - eprintln!("Fail to publish image completion emit to front end! {e:?}"); + BlenderAction::Get(version, mut sender) => { + let result = self.manager.fetch_blender(&version); + match result { + Ok(blender) => { + if let Err(e) = sender.send(Some(blender)).await { + eprintln!("Fail to send result back to caller! {e:?}"); } } + Err(e) => { + eprintln!("Fail to fetch blender! {e:?}"); + let _ = sender.send(None); + } + }; + } + // severe connection - remove the entry from database, but do not touch the installation + BlenderAction::Disconnect(blender) => { + if let Err(e) = self.manager.remove_blender(&blender) { + eprintln!("Unable to disconnect blender: {e:?}"); } + } + // uninstall blender from local machine + BlenderAction::Remove(_blender) => { + todo!("Need to do some unit test before you can use this feature..."); + // self.manager.delete_blender(&blender); + } + } + } - // when a job is complete, check the poll for next available job queue? - JobEvent::JobComplete => {} // Hmm how do I go about handling this one? + async fn handle_worker_command(&mut self, worker_action: WorkerAction) { + match worker_action { + WorkerAction::Get(peer_id, mut sender) => { + let result = sender + .send(self.worker_store.get_worker(&peer_id).await) + .await; + if let Err(e) = result { + eprintln!("Unable to get worker!: {e:?}"); + } + } + WorkerAction::List(mut sender) => { + let result = sender + .send(self.worker_store.list_worker().await.ok()) + .await; + if let Err(e) = result { + eprintln!("Unable to send list of workers: {e:?}"); + } + } + } + } - // TODO: how do we handle error from node? What kind of errors are we expecting here and what can the host do about it? - JobEvent::Error(job_error) => { - todo!("See how this can be replicated? {job_error:?}") + async fn handle_setting_command(&mut self, setting_action: SettingsAction) { + match setting_action { + SettingsAction::Get(mut sender) => { + if let Err(e) = sender.send(self.settings.clone()).await { + eprintln!("Fail to send to invoker! {e:?}"); } + } + SettingsAction::Update(new_settings) => { + self.settings = new_settings; + self.settings.save(); + } + } + } - // send a render job - // this will soon go away - host should not be receiving render jobs. - JobEvent::Render(..) => {} - // this will soon go away - host should not receive request job. - JobEvent::RequestJob => {} - // this will soon go away - JobEvent::Remove(_) => { - // Should I do anything on the manager side? Shouldn't matter at this point? + // command received from UI + async fn handle_command(&mut self, client: &mut NetworkController, cmd: UiCommand) { + // println!("Received command from UI: {cmd:?}"); + match cmd { + // could this be used as a trait? + UiCommand::Blender(blender_action) => self.handle_blender_command(blender_action).await, + UiCommand::Settings(setting_action) => { + self.handle_setting_command(setting_action).await + } + UiCommand::Job(job_action) => self.handle_job_command(job_action, client).await, + UiCommand::Worker(worker_action) => self.handle_worker_command(worker_action).await, + UiCommand::UploadFile(path) => { + // this is design to notify the network controller to start advertise provided file path + let provider = ProviderRule::Default(path); + if let Err(e) = client.start_providing(&provider).await { + eprintln!("Network issue on providing file! {e:?}"); } + } + } + } + + // commands received from network + async fn handle_net_event(&mut self, client: &mut NetworkController, event: Event) { + match event { + Event::Discovered(peer_id, mutitaddr) => { + // Here should try to join the topic hash before sending message out in case it doesn't work? + let peer_id = client.public_id; + let spec = ComputerSpec::new(); + let server_status = ServerEvent::Online(peer_id, spec); + client.send_server_status(server_status).await }, - _ => println!("{:?}", event), + // what does inbound request do? + Event::InboundRequest { .. } => todo!(), + Event::ServerStatus(..) => todo!(), + Event::JobUpdate(..) => todo!(), + Event::ReceivedFileData(..) => todo!(), } } } @@ -360,32 +466,234 @@ impl BlendFarm for TauriApp { async fn run( mut self, mut client: NetworkController, - mut event_receiver: Receiver, + mut event_receiver: Receiver, ) -> Result<(), NetworkError> { - // for application side, we will subscribe to message event that's important to us to intercept. - client.subscribe_to_topic(SPEC.to_owned()).await; - client.subscribe_to_topic(HEARTBEAT.to_owned()).await; - client.subscribe_to_topic(STATUS.to_owned()).await; - client.subscribe_to_topic(JOB.to_owned()).await; // This might get changed? we'll see. - // this channel is used to send command to the network, and receive network notification back. + // ok where is this used? let (event, mut command) = mpsc::channel(32); + let app_state = AppState::new(event); + let mut_app_state = Mutex::new(app_state); + + // at the start of this program, I need to broadcast existing project file before the rest of the command hooks. + // This way, any job pending would have the file already available to distribute across the network. + // we send the sender to the tauri builder - which will send commands to "from_ui". - let app = self - .config_tauri_builder(event) + let app = Self::init_tauri_plugins(tauri::Builder::default()) + .invoke_handler(tauri::generate_handler![ + index, + open_path, + open_dir, + select_directory, + select_file, + create_job, + delete_job, + get_job_detail, + setting_page, + edit_settings, + get_settings, + update_settings, + open_dialog_for_blend_file, + available_versions, + list_workers, + list_jobs, + get_worker, + update_output_field, + add_blender_installation, + install_from_internet, + list_blender_installed, + disconnect_blender_installation, + delete_blender, + fetch_blender_installation, + ]) + .manage(mut_app_state) + .build(tauri::generate_context!("tauri.conf.json")) .expect("Fail to build tauri app - Is there an active display session running?"); - // create a safe and mutable way to pass application handler to send notification from network event. - // TODO: Get rid of this. - let app_handle = Arc::new(RwLock::new(app.app_handle().clone())); - - // create a background loop to send and process network event + // background thread to handle network process spawn(async move { loop { select! { - Some(msg) = command.recv() => self.handle_command(&mut client, msg).await, - Some(event) = event_receiver.recv() => self.handle_net_event(&mut client, event, app_handle.clone()).await, + msg = command.select_next_some() => self.handle_command(&mut client, msg).await, + + event = event_receiver.recv() => match event { + Some(net_event) => match net_event { + Event::ServerStatus(server_status) => match server_status { + ServerEvent::Hello(peer_id_string, spec) => { + // a new node acknowledges your activity. Revealing available server on the network. + // this node now listens to you, and has provided info to communicate back + let peer_id = + PeerId::from_str(&peer_id_string).expect("Peer id should be valid"); + + // We'll tag this node as a worker. + let worker = Worker::new(peer_id.clone(), spec.clone()); + + // append new worker to database store + if let Err(e) = self.worker_store.add_worker(worker).await { + eprintln!("Error adding worker to database! {e:?}"); + } + + println!("New worker added!"); + self.peers.insert(peer_id, spec); + + // let handle = app_handle.write().await; + // emit a signal to query the data. + // TODO: See how this can be done: https://github.com/ChristianPavilonis/tauri-htmx-extension + // let _ = handle.emit("worker_update"); + } + // concerning - this String could be anything? + // TODO: Find a better way to get around this. + ServerEvent::Disconnected { peer_id, reason } => { + if let Some(msg) = reason { + eprintln!("Node disconnected with reason!\n {msg}"); + } + + // So the main issue is that there's no way to identify by the machine id? + let peer_id = + PeerId::from_str(&peer_id).expect("Received invalid peer_id string!"); + + // probably best to mark the node "inactive" instead? + if let Err(e) = self.worker_store.delete_worker(&peer_id).await { + eprintln!("Error deleting worker from database! {e:?}"); + } + + self.peers.remove(&peer_id); + } + // this is the same as saying down in the garbage disposal. Anything goes here. Do not trust data source here! + ServerEvent::BlenderStatus(blend_event) => { + println!("Blender Status Received: {blend_event:?}") + } + }, + + // let me figure out what's going on here. + // a network sent us a inbound request - reply back with the file data in channel. + // yeah I wonder why we can't move this inside network class? + Event::InboundRequest { request, channel } => { + Self::handle_inbound_request(&client, request, channel).await; + } + + Event::JobUpdate(job_event) => match job_event { + // when we receive a completed image, send a notification to the host and update job index to obtain the latest render image. + JobEvent::AskForCompletedJobFrameList(_) => { + // this is reserved for the host side of the app to send out. We do not process this data here. + // only client should receive this notification, host will ignore this. + } + JobEvent::ImageCompletedList { job_id, files } => { + // first thing first, check and see if this job id matches what we have in our database. + // if it doesn't then we ignore this request and move on. + let result = self.job_store.get_job(&job_id).await; + + if result.is_err() { + return; // stop here. do not proceed forward. We do not care. + } + + // not that we have the job, we need to fetch for our existing files that we have completed + // We received a list of files from the client. We will run and compare this list to our local machine + // let local = + + // if we do not have the file locally, we will ask for the image from the provided node. + // In this case, we do not care who have the node, we will send out a signal stating I need this file. + // the node that receive the signal will message back. + + for file in files { + println!("file: {file}"); + } + } + // we received a job event that a node have finish rendering an image. + // We now need to make sure our output destination exist and valid. + // Afterward, we should try to fetch the file from that caller. + JobEvent::ImageCompleted { + job_id, + frame: _, + file_name, + } => { + // create a destination with respective job id path. + let destination = self.settings.render_dir.join(job_id.to_string()); + if let Err(e) = async_std::fs::create_dir_all(destination.clone()).await { + println!("Issue creating temp job directory! {e:?}"); + } + + /* send update to ui + let handle = app_handle.write().await; + if let Err(e) = handle.emit( + "frame_update", + FrameUpdatePayload { + id, + frame, + file_name: file_name.clone(), + }, + ) { + eprintln!("Unable to send emit to app handler\n{e:?}"); + } + */ + + // Fetch the completed image file from the network + match client.get_file_from_peers(&file_name, &destination).await { + Ok(file) => { + println!("File stored at {file:?}"); + // let handle = app_handle.write().await; + // if let Err(e) = handle.emit("job_image_complete", (job_id, frame, file)) { + // eprintln!("Fail to publish image completion emit to front end! {e:?}"); + // } + } + Err(e) => { + eprintln!("Failed to fetch the file from peers!\n{:?}", e); + } + } + } + // when a task is complete, check the poll for next available job queue? + JobEvent::TicketComplete => { + println!("Received Task Completed! Do something about this!"); + } + + // TODO: how do we handle error from node? What kind of errors are we expecting here and what can the host do about it? + JobEvent::Error(job_error) => { + todo!("See how this can be replicated? {job_error:?}") + } + + // send a render job + JobEvent::Render(..) => { + // if we have a local client up and running, we should just communicate it directly. This will help setup the output correctly. + // TODO: Host should try to communicate local client + println!( + "Host received a Render Job - Contact client and provide info about this job. Read on how Rust micromange services?" + ); + } + JobEvent::RequestTask(peer_id_str) => { + // a node is requesting task. + + let jobs = self.job_store.list_all().await.expect("Should have jobs?"); + if let Some(job) = jobs.first() { + // how do I reply back for this task then? + // use the peer_id_string. + match job.item.clone().generate_task(job.id) { + Some(task) => { + let event = JobEvent::Render(peer_id_str, task); + client.send_job_event(event).await; + } + None => return, + } + } + } + // this will soon go away + JobEvent::Failed(msg) => { + eprintln!("Job failed! {msg}"); + } + JobEvent::Remove(_) => { + // Should I do anything on the manager side? Shouldn't matter at this point? + } + }, + Event::Discovered(..) => { + // from this level, we have discovered other potential client on the network. + // at this level, we do absolutely nothing. We only respond to client incoming request. + }, + e => println!("Unhandled Network Event {e:?}") + }, + _ => { + println!("Received No network message. Pipe may have been closed?"); + () + } + } } } }); @@ -394,3 +702,43 @@ impl BlendFarm for TauriApp { Ok(()) } } + +#[cfg(test)] +mod test { + // use blender::models::blender_config::BlenderConfig; + + use super::*; + use crate::{config_sqlite_db, constant::DATABASE_FILE_NAME}; + + async fn get_sqlite_conn() -> Pool { + let pool = config_sqlite_db(DATABASE_FILE_NAME).await; + assert!(pool.is_ok()); + pool.expect("Assert above should force this to be ok()") + } + + // async fn get_mockup_config() -> BlenderConfig { + // todo!("Implement a mock up unit test for this blender config"); + // } + + async fn get_mockup_manager() -> BlenderManager { + todo!("Implement a mock up blender manager"); + } + + #[tokio::test] + async fn clear_workers_success() { + let pool = get_sqlite_conn().await; + // let config = get_mockup_config().await; + let manager = get_mockup_manager().await; + let app = TauriApp::new(manager, &pool); + + let app = app.clear_workers_collection().await; + assert!( + app.worker_store + .list_worker() + .await + .is_ok_and(|f| f.iter().count() == 0) + ); + } + + // todo: identify other part of this code that I can run unit test and list out potential edge cases +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index a534b747..b59811af 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -19,11 +19,16 @@ "security": { "assetProtocol": { "scope": [ - "*/**" + "**" ], "enable": true }, - "csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost; connect-src ipc: http://ipc.localhost" + "csp": { + "default-src": "'self'", + "ipc": "http://ipc.localhost", + "img-src": "'self'", + "asset": "http://asset.localhost" + } } }, "bundle": { diff --git a/src/htmx.js b/src/htmx.js index 9da48daa..93865749 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -1077,13 +1077,6 @@ var htmx = (function() { if (elt && elt.closest) { return elt.closest(selector) } else { - // TODO remove when IE goes away - do { - if (elt == null || matches(elt, selector)) { - return elt - } - } - while (elt = elt && asElement(parentElt(elt))) return null } } @@ -2962,7 +2955,7 @@ var htmx = (function() { function makeEvent(eventName, detail) { let evt if (window.CustomEvent && typeof window.CustomEvent === 'function') { - // TODO: `composed: true` here is a hack to make global event handlers work with events in shadow DOM + // `composed: true` here is a hack to make global event handlers work with events in shadow DOM // This breaks expected encapsulation but needs to be here until decided otherwise by core devs evt = new CustomEvent(eventName, { bubbles: true, cancelable: true, composed: true, detail }) } else { diff --git a/src/styles.css b/src/styles.css index 3e605ca3..f60c649e 100644 --- a/src/styles.css +++ b/src/styles.css @@ -23,12 +23,6 @@ body { height: 100%; } -/* TODO: Where is this used? what is this? */ -.imgbox { - display: grid; - height: 100%; -} - .center-fit { max-width: 100%; max-height: 100%; @@ -103,7 +97,10 @@ button { margin-right: 5px; } -/* TODO: Do we still use this anymore anywhere? */ +/* + Q: Do we still use this anymore anywhere? + A: Yes we are using nav-bar style in tauri_app.rs +*/ .nav-bar { font-weight: 500; color: #646cff; diff --git a/src/todo.txt b/src/todo.txt new file mode 100644 index 00000000..7942937f --- /dev/null +++ b/src/todo.txt @@ -0,0 +1,33 @@ +[todo] + Make the GUI app run in client mode? + - test fully through, see if it can render the job. + - Working on impl. Unit test. Few scripts have basic unit test coverage. + - Need to research about ideal unit test coverage. + Go through TODO list and see if there's any that can be done in five minutes. Work on that first. + Then come back to Network protocol + + +[issues] + - Client is not receiving network event from host. It receives connection established, but no network data exchanged yet? +E.g. +%> Sending task Task { job_id: f5f3af8b-4a74-4729-84e1-d25c4da4f4dc, blender_version: Version { major: 4, minor: 1, patch: 0 }, blend_file_name: "test.blend", range: 1..10 } to the gossip channel + - Deleting job does not clear entry from the job list + - Unable to open import_blend dialog + + - client does not send message while the job is running, I thought this was done async? what's going on? + - only at the end of the task does it ever notify host? + +[features] + provide the menu context to allow user to start or end local client mode session + Got image screen to display + Further separate Ui commands into event registration. + +Progress: + Still having problem with network code. + - read more into libp2p and run examples. + Got UI working again + - Provided buttons to open directory on setting page + - Job now display render image from output directory. + - See about how we can customize view from image tile to list + - [Feature] See about ffmpeg integration. Blender doesn't have ffmpeg + \ No newline at end of file diff --git a/system_arch.excalidraw b/system_arch.excalidraw new file mode 100644 index 00000000..7d20b946 --- /dev/null +++ b/system_arch.excalidraw @@ -0,0 +1,2811 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "hcNmKG6RNHlIZluTwlCyr", + "type": "rectangle", + "x": 29.181233835953634, + "y": -46.169481312454536, + "width": 187.42223968426126, + "height": 467.0168265896209, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0d", + "roundness": { + "type": 3 + }, + "seed": 924372424, + "version": 586, + "versionNonce": 574350024, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "lpX5o3RMN0SfJAdP1sp3J" + }, + { + "id": "3JcNs_ceXa04q4Fy8M3AQ", + "type": "arrow" + }, + { + "id": "VNBuMuhrBdk6-qyRSJScX", + "type": "arrow" + }, + { + "id": "VF2PQtfx_p4AoqPL252S-", + "type": "arrow" + }, + { + "id": "ffTH8nDNfJXaJdW5_qSo7", + "type": "arrow" + }, + { + "id": "C6HTF3smGfNHQai67ofiv", + "type": "arrow" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false + }, + { + "id": "lpX5o3RMN0SfJAdP1sp3J", + "type": "text", + "x": 72.52568879160965, + "y": -41.169481312454536, + "width": 100.73332977294922, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0l", + "roundness": null, + "seed": 1431033032, + "version": 464, + "versionNonce": 865521864, + "isDeleted": false, + "boundElements": null, + "updated": 1768791969315, + "link": null, + "locked": false, + "text": "libp2p", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "top", + "containerId": "hcNmKG6RNHlIZluTwlCyr", + "originalText": "libp2p", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "F6pKEwbBLGwpNddWJxrY0", + "type": "rectangle", + "x": 846.9707326469716, + "y": 68.94869616275065, + "width": 704, + "height": 375.0815261182259, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "a2", + "roundness": { + "type": 3 + }, + "seed": 2051888072, + "version": 775, + "versionNonce": 1299986632, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "utA6V4gprvoo5FzydQuWD" + } + ], + "updated": 1768793986688, + "link": null, + "locked": false + }, + { + "id": "utA6V4gprvoo5FzydQuWD", + "type": "text", + "x": 1104.8624028740223, + "y": 73.94869616275065, + "width": 188.21665954589844, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 201300680, + "version": 673, + "versionNonce": 1107943368, + "isDeleted": false, + "boundElements": [], + "updated": 1768793986688, + "link": null, + "locked": false, + "text": "Blender_rs", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "top", + "containerId": "F6pKEwbBLGwpNddWJxrY0", + "originalText": "Blender_rs", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "rSoDP6s_fSonPZvameC56", + "type": "rectangle", + "x": 868.9962095589171, + "y": 128.46907769230745, + "width": 221.99999999999997, + "height": 277, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "a4", + "roundness": { + "type": 3 + }, + "seed": 489176520, + "version": 624, + "versionNonce": 1145743048, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "zO-urD1mxa2c4mJ_NKAu6" + }, + { + "id": "0jZKIDlgSFE-48ZpcBnFN", + "type": "arrow" + }, + { + "id": "N_AsJslh4bXSQZrUxC5ai", + "type": "arrow" + }, + { + "id": "B9Uks7msNdSBTALz_Ibu6", + "type": "arrow" + }, + { + "id": "wxkhOnEba572OmVshH7Us", + "type": "arrow" + } + ], + "updated": 1768793986688, + "link": null, + "locked": false + }, + { + "id": "zO-urD1mxa2c4mJ_NKAu6", + "type": "text", + "x": 908.9295439095031, + "y": 244.46907769230745, + "width": 142.13333129882812, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 1370571192, + "version": 604, + "versionNonce": 1893371336, + "isDeleted": false, + "boundElements": null, + "updated": 1768793986688, + "link": null, + "locked": false, + "text": "Manager", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "rSoDP6s_fSonPZvameC56", + "originalText": "Manager", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "L4I697w34meinXEVNHdJi", + "type": "rectangle", + "x": 1252.4962095589171, + "y": 228.96907769230745, + "width": 279, + "height": 55, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "a6", + "roundness": { + "type": 3 + }, + "seed": 973692872, + "version": 560, + "versionNonce": 1775745992, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "dgwAEG0Gv5FXgrM75MLvc" + }, + { + "id": "N_AsJslh4bXSQZrUxC5ai", + "type": "arrow" + } + ], + "updated": 1768793986688, + "link": null, + "locked": false + }, + { + "id": "dgwAEG0Gv5FXgrM75MLvc", + "type": "text", + "x": 1327.137879785968, + "y": 233.96907769230745, + "width": 129.71665954589844, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "a7", + "roundness": null, + "seed": 274343624, + "version": 555, + "versionNonce": 1845297864, + "isDeleted": false, + "boundElements": [], + "updated": 1768793986688, + "link": null, + "locked": false, + "text": "Blender", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "L4I697w34meinXEVNHdJi", + "originalText": "Blender", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "tgzqc3vn0nAmbzeYlUbvQ", + "type": "rectangle", + "x": 1245.4962095589171, + "y": 147.46907769230745, + "width": 279, + "height": 55, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "a8", + "roundness": { + "type": 3 + }, + "seed": 1229728968, + "version": 566, + "versionNonce": 1809934792, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "fkTPONrIgzUmXfzOF8aIL" + }, + { + "id": "0jZKIDlgSFE-48ZpcBnFN", + "type": "arrow" + } + ], + "updated": 1768793986688, + "link": null, + "locked": false + }, + { + "id": "fkTPONrIgzUmXfzOF8aIL", + "type": "text", + "x": 1277.079545435382, + "y": 152.46907769230745, + "width": 215.8333282470703, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "a9", + "roundness": null, + "seed": 666588104, + "version": 577, + "versionNonce": 513863880, + "isDeleted": false, + "boundElements": [], + "updated": 1768793986688, + "link": null, + "locked": false, + "text": "Online Check", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "tgzqc3vn0nAmbzeYlUbvQ", + "originalText": "Online Check", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "IyIWOuIx444nBo5vkfGno", + "type": "rectangle", + "x": 1252.4962095589171, + "y": 312.46907769230745, + "width": 279, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "aA", + "roundness": { + "type": 3 + }, + "seed": 2080394424, + "version": 613, + "versionNonce": 2111281096, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "FM3NpPyA64mkABPT8oB6D" + }, + { + "id": "B9Uks7msNdSBTALz_Ibu6", + "type": "arrow" + } + ], + "updated": 1768793986688, + "link": null, + "locked": false + }, + { + "id": "FM3NpPyA64mkABPT8oB6D", + "type": "text", + "x": 1273.012879785968, + "y": 317.46907769230745, + "width": 237.96665954589844, + "height": 90, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "aB", + "roundness": null, + "seed": 12973496, + "version": 654, + "versionNonce": 765550280, + "isDeleted": false, + "boundElements": [], + "updated": 1768793986688, + "link": null, + "locked": false, + "text": "Database\n(Persist info)", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "IyIWOuIx444nBo5vkfGno", + "originalText": "Database\n(Persist info)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "0jZKIDlgSFE-48ZpcBnFN", + "type": "arrow", + "x": 1101.9962095589171, + "y": 170.1325822651816, + "width": 145, + "height": 3.33649542712584, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "aC", + "roundness": { + "type": 2 + }, + "seed": 1687379384, + "version": 354, + "versionNonce": 1855604168, + "isDeleted": false, + "boundElements": null, + "updated": 1768793986688, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 145, + 3.33649542712584 + ] + ], + "startBinding": { + "elementId": "rSoDP6s_fSonPZvameC56", + "mode": "orbit", + "fixedPoint": [ + 0.8532110091743116, + 0.14678899082568825 + ] + }, + "endBinding": { + "elementId": "tgzqc3vn0nAmbzeYlUbvQ", + "mode": "inside", + "fixedPoint": [ + 0.005376344086021506, + 0.4727272727272727 + ] + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "N_AsJslh4bXSQZrUxC5ai", + "type": "arrow", + "x": 1101.9962095589171, + "y": 260.0255573519698, + "width": 139.50000000000023, + "height": 0.529671133356203, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "aD", + "roundness": { + "type": 2 + }, + "seed": 1219497656, + "version": 337, + "versionNonce": 232672456, + "isDeleted": false, + "boundElements": null, + "updated": 1768793986688, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 139.50000000000023, + -0.529671133356203 + ] + ], + "startBinding": { + "elementId": "rSoDP6s_fSonPZvameC56", + "mode": "orbit", + "fixedPoint": [ + 0.5234657039711198, + 0.47653429602888125 + ] + }, + "endBinding": { + "elementId": "L4I697w34meinXEVNHdJi", + "mode": "orbit", + "fixedPoint": [ + 0.4544801395909596, + 0.5455198604090439 + ] + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "B9Uks7msNdSBTALz_Ibu6", + "type": "arrow", + "x": 1101.9962095589171, + "y": 367.5102394966007, + "width": 154, + "height": 0.9588381957067327, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RRmMeFmAMpiKag069kOmt" + ], + "frameId": null, + "index": "aE", + "roundness": { + "type": 2 + }, + "seed": 2036746936, + "version": 373, + "versionNonce": 1426682824, + "isDeleted": false, + "boundElements": null, + "updated": 1768793986688, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 154, + 0.9588381957067327 + ] + ], + "startBinding": { + "elementId": "rSoDP6s_fSonPZvameC56", + "mode": "orbit", + "fixedPoint": [ + 0.8620287602020997, + 0.862028760202099 + ] + }, + "endBinding": { + "elementId": "IyIWOuIx444nBo5vkfGno", + "mode": "inside", + "fixedPoint": [ + 0.012544802867383513, + 0.56 + ] + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "D4wC3cc2HWhwasVdehc-3", + "type": "text", + "x": -128.06856200524857, + "y": 17.314420298960613, + "width": 107.56666564941406, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aUV", + "roundness": null, + "seed": 465195720, + "version": 45, + "versionNonce": 221359288, + "isDeleted": false, + "boundElements": null, + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Announcement", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ko3rqWr3W74NUQbRi0SKz", + "originalText": "Announcement", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "5rBQ_ArF4Ru8VhORGSZVE", + "type": "rectangle", + "x": -452.2465467713236, + "y": -385.83725137258574, + "width": 1232.7673231428405, + "height": 107.66984856524238, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aZ", + "roundness": { + "type": 3 + }, + "seed": 1082713784, + "version": 286, + "versionNonce": 581511880, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "7fbDfKLlo-OxqNpuR7wU5" + }, + { + "id": "hKMlEQJyuB1XLOdlxBq16", + "type": "arrow" + }, + { + "id": "dhhjZ1q6N_9xmmb-_TsHv", + "type": "arrow" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false + }, + { + "id": "7fbDfKLlo-OxqNpuR7wU5", + "type": "text", + "x": 17.295455254198203, + "y": -354.5023270899645, + "width": 293.6833190917969, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aZV", + "roundness": null, + "seed": 1695228600, + "version": 312, + "versionNonce": 722754488, + "isDeleted": false, + "boundElements": null, + "updated": 1768793830785, + "link": null, + "locked": false, + "text": "User Interaction", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "5rBQ_ArF4Ru8VhORGSZVE", + "originalText": "User Interaction", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "hKMlEQJyuB1XLOdlxBq16", + "type": "arrow", + "x": -288.55129045741756, + "y": -267.1674028073433, + "width": 3.8967975700685997, + "height": 207.64611822472807, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ab", + "roundness": { + "type": 2 + }, + "seed": 807097784, + "version": 399, + "versionNonce": 126537144, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "VfbdOENlkUBklbTh0OwzH" + } + ], + "updated": 1768793830785, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 3.8967975700685997, + 207.64611822472807 + ] + ], + "startBinding": { + "elementId": "5rBQ_ArF4Ru8VhORGSZVE", + "mode": "orbit", + "fixedPoint": [ + 0.13240235246973892, + 0.8675976475302611 + ] + }, + "endBinding": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "VfbdOENlkUBklbTh0OwzH", + "type": "text", + "x": -332.62789319826214, + "y": -185.84434369497924, + "width": 92.05000305175781, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ab4", + "roundness": null, + "seed": 181283512, + "version": 17, + "versionNonce": 1502155464, + "isDeleted": false, + "boundElements": null, + "updated": 1768793837752, + "link": null, + "locked": false, + "text": "Tauri", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "hKMlEQJyuB1XLOdlxBq16", + "originalText": "Tauri", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "dhhjZ1q6N_9xmmb-_TsHv", + "type": "arrow", + "x": 587.5165188855618, + "y": -267.16740280734336, + "width": 2.0963570765007944, + "height": 195.9088950818114, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "af", + "roundness": { + "type": 2 + }, + "seed": 1622059464, + "version": 448, + "versionNonce": 1314352824, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "MSRQtzdfaETACS9uSflzh" + } + ], + "updated": 1768793935604, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 2.0963570765007944, + 195.9088950818114 + ] + ], + "startBinding": { + "elementId": "5rBQ_ArF4Ru8VhORGSZVE", + "mode": "orbit", + "fixedPoint": [ + 0.8431961791115165, + 0.8431961791115163 + ] + }, + "endBinding": { + "elementId": "u4UP0pE9dcRSdKQE-6Mii", + "mode": "orbit", + "fixedPoint": [ + 0.4774547624963938, + 0.477454762496394 + ] + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "MSRQtzdfaETACS9uSflzh", + "type": "text", + "x": 557.6563970436093, + "y": -191.7129552664376, + "width": 61.81666564941406, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "afV", + "roundness": null, + "seed": 1640085704, + "version": 38, + "versionNonce": 1296555704, + "isDeleted": false, + "boundElements": null, + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "CLI", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "dhhjZ1q6N_9xmmb-_TsHv", + "originalText": "CLI", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "wxkhOnEba572OmVshH7Us", + "type": "arrow", + "x": 712.1087181997963, + "y": 269.71828718934364, + "width": 158.42114516827542, + "height": 6.317928634050304, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ag", + "roundness": { + "type": 2 + }, + "seed": 717714616, + "version": 344, + "versionNonce": 1893045448, + "isDeleted": false, + "boundElements": null, + "updated": 1768793986688, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 158.42114516827542, + -6.317928634050304 + ] + ], + "startBinding": { + "elementId": "LNIk5qXErRLGTE77O-C8E", + "mode": "orbit", + "fixedPoint": [ + 0.62620400724163, + 0.6262040072416295 + ] + }, + "endBinding": { + "elementId": "rSoDP6s_fSonPZvameC56", + "mode": "inside", + "fixedPoint": [ + 0.0069083504916871335, + 0.4871165374115014 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "BLJxrLFWWrZcd5wgN-8jl", + "type": "text", + "x": 290.8963716338197, + "y": 16.954638276974045, + "width": 107.56666564941406, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "al", + "roundness": null, + "seed": 2131486152, + "version": 48, + "versionNonce": 519671992, + "isDeleted": false, + "boundElements": [], + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Announcement", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "VNBuMuhrBdk6-qyRSJScX", + "originalText": "Announcement", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "C6HTF3smGfNHQai67ofiv", + "type": "arrow", + "x": 227.60347352021492, + "y": 132.0044861068664, + "width": 228.20474256768514, + "height": 3.6359821395237475, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "am", + "roundness": { + "type": 2 + }, + "seed": 1568181960, + "version": 291, + "versionNonce": 761602232, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "42ML-8zyTqNqNJgcmQi9o" + } + ], + "updated": 1768793935604, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 228.20474256768514, + 3.6359821395237475 + ] + ], + "startBinding": { + "elementId": "hcNmKG6RNHlIZluTwlCyr", + "mode": "orbit", + "fixedPoint": [ + 0.6212821888451624, + 0.3787178111548376 + ] + }, + "endBinding": { + "elementId": "6GLKwsy__oleqFg_DKe0C", + "mode": "orbit", + "fixedPoint": [ + 0.4094413058545063, + 0.590558694145495 + ] + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "42ML-8zyTqNqNJgcmQi9o", + "type": "text", + "x": 296.39751045347157, + "y": 123.82575617927861, + "width": 90.61666870117188, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "an", + "roundness": null, + "seed": 2081125832, + "version": 40, + "versionNonce": 2079766968, + "isDeleted": false, + "boundElements": [], + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Job Update", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "C6HTF3smGfNHQai67ofiv", + "originalText": "Job Update", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "3JcNs_ceXa04q4Fy8M3AQ", + "type": "arrow", + "x": 457.5819585280833, + "y": 269.9608246207511, + "width": 239.38646446618048, + "height": 1.263441072312844, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ap", + "roundness": { + "type": 2 + }, + "seed": 754606792, + "version": 301, + "versionNonce": 404964024, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "sDL28GYsrqP0ui--5Wl8Q" + } + ], + "updated": 1768793963371, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -239.38646446618048, + 1.263441072312844 + ] + ], + "startBinding": { + "elementId": "LNIk5qXErRLGTE77O-C8E", + "mode": "orbit", + "fixedPoint": [ + 0.3784059354670282, + 0.6215940645329713 + ] + }, + "endBinding": { + "elementId": "PGvAIRHl59laFP1wJL99F", + "mode": "orbit", + "fixedPoint": [ + 0.6504670680418346, + 0.6504670680418341 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "sDL28GYsrqP0ui--5Wl8Q", + "type": "text", + "x": 285.0553942332255, + "y": 260.5923901977289, + "width": 105.66666412353516, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "apV", + "roundness": null, + "seed": 551842744, + "version": 28, + "versionNonce": 659244984, + "isDeleted": false, + "boundElements": null, + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Render Event", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "3JcNs_ceXa04q4Fy8M3AQ", + "originalText": "Render Event", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "VF2PQtfx_p4AoqPL252S-", + "type": "arrow", + "x": -175.18035794839324, + "y": 119.57511811623904, + "width": 204.2902908694808, + "height": 4.82742165401099, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "arG", + "roundness": { + "type": 2 + }, + "seed": 551797960, + "version": 424, + "versionNonce": 418286264, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "DDb_EC26v6QUf60EGXo6s" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 204.2902908694808, + 4.82742165401099 + ] + ], + "startBinding": { + "elementId": "hSITs0-BXLZEuSLyckysl", + "mode": "orbit", + "fixedPoint": [ + 0.7714395175744546, + 0.22856048242554547 + ] + }, + "endBinding": { + "elementId": "1C-V6FBRBZt1Bd4DqrDq9", + "mode": "orbit", + "fixedPoint": [ + 0.4847453803674703, + 0.51525461963253 + ] + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "DDb_EC26v6QUf60EGXo6s", + "type": "text", + "x": -118.34354686423877, + "y": 111.9897128912447, + "width": 90.61666870117188, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "arV", + "roundness": null, + "seed": 1357812680, + "version": 61, + "versionNonce": 617305528, + "isDeleted": false, + "boundElements": null, + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Job Update", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "VF2PQtfx_p4AoqPL252S-", + "originalText": "Job Update", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "ffTH8nDNfJXaJdW5_qSo7", + "type": "arrow", + "x": 35.52653847582883, + "y": 265.1939164618733, + "width": 210.70689642422207, + "height": 9.230724989191827, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "as", + "roundness": { + "type": 2 + }, + "seed": 1656221128, + "version": 399, + "versionNonce": 746037944, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "1pHqNXY7pBQJWRBqMdPIG" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -210.70689642422207, + -9.230724989191827 + ] + ], + "startBinding": { + "elementId": "PGvAIRHl59laFP1wJL99F", + "mode": "inside", + "fixedPoint": [ + 0.004830917874398955, + 0.5555555555555534 + ] + }, + "endBinding": { + "elementId": "hSITs0-BXLZEuSLyckysl", + "mode": "orbit", + "fixedPoint": [ + 0.8173631318365124, + 0.8173631318365125 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "1pHqNXY7pBQJWRBqMdPIG", + "type": "text", + "x": -122.66024179804978, + "y": 250.5785539672774, + "width": 105.66666412353516, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "at", + "roundness": null, + "seed": 455880, + "version": 30, + "versionNonce": 1152694200, + "isDeleted": false, + "boundElements": [], + "updated": 1768793926537, + "link": null, + "locked": false, + "text": "Render Event", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ffTH8nDNfJXaJdW5_qSo7", + "originalText": "Render Event", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "ko3rqWr3W74NUQbRi0SKz", + "type": "arrow", + "x": -175.18035794839312, + "y": 12.996709587404492, + "width": 201.7902575357032, + "height": 28.630782726012743, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "auG", + "roundness": { + "type": 2 + }, + "seed": 166747848, + "version": 342, + "versionNonce": 557609912, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "D4wC3cc2HWhwasVdehc-3" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 201.7902575357032, + 28.630782726012743 + ] + ], + "startBinding": { + "elementId": "iJTtIjuF5BPf2AA9OiSM-", + "mode": "orbit", + "fixedPoint": [ + 0.5587649555455054, + 0.44123504445449463 + ] + }, + "endBinding": { + "elementId": "AXSYFY_GFdM-eU0etWSJs", + "mode": "orbit", + "fixedPoint": [ + 0.3660714819234105, + 0.6339285180765895 + ] + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "VNBuMuhrBdk6-qyRSJScX", + "type": "arrow", + "x": 221.11219961797718, + "y": 47.38982705537615, + "width": 247.135009681099, + "height": 40.85896525828861, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "auV", + "roundness": { + "type": 2 + }, + "seed": 1405550280, + "version": 401, + "versionNonce": 434453688, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "BLJxrLFWWrZcd5wgN-8jl" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 247.135009681099, + -40.85896525828861 + ] + ], + "startBinding": { + "elementId": "AXSYFY_GFdM-eU0etWSJs", + "mode": "orbit", + "fixedPoint": [ + 0.7120533599662096, + 0.7120533599662096 + ] + }, + "endBinding": { + "elementId": "u4UP0pE9dcRSdKQE-6Mii", + "mode": "orbit", + "fixedPoint": [ + 0.45220610543257905, + 0.4522061054325794 + ] + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "uIDdeV3qmKiKJc0gO9Rga", + "type": "rectangle", + "x": -475.81934004920953, + "y": -128.06759963124705, + "width": 1237.6393746927079, + "height": 588.7548056237531, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "auX", + "roundness": { + "type": 3 + }, + "seed": 880272584, + "version": 630, + "versionNonce": 1919515592, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "Xak1IXlswugj0h2f2rDrX" + }, + { + "id": "Gcrk54RqYVHVP6UjqS5GG", + "type": "arrow" + }, + { + "id": "z79o6U2j5getfDGscbkjt", + "type": "arrow" + }, + { + "id": "hKMlEQJyuB1XLOdlxBq16", + "type": "arrow" + }, + { + "id": "dhhjZ1q6N_9xmmb-_TsHv", + "type": "arrow" + } + ], + "updated": 1768793936654, + "link": null, + "locked": false + }, + { + "id": "Xak1IXlswugj0h2f2rDrX", + "type": "text", + "x": 54.508678595972526, + "y": -123.06759963124705, + "width": 176.98333740234375, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "auZ", + "roundness": null, + "seed": 313080776, + "version": 524, + "versionNonce": 1607664824, + "isDeleted": false, + "boundElements": null, + "updated": 1768793936654, + "link": null, + "locked": false, + "text": "BlendFarm", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "top", + "containerId": "uIDdeV3qmKiKJc0gO9Rga", + "originalText": "BlendFarm", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "iJTtIjuF5BPf2AA9OiSM-", + "type": "rectangle", + "x": -418.70711762010615, + "y": -49.55334129619581, + "width": 232.52675967171302, + "height": 105.23108831858542, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "aub", + "roundness": { + "type": 3 + }, + "seed": 233121976, + "version": 151, + "versionNonce": 1850396360, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "FZuLAXlCHhgaSwbnqeq-R" + }, + { + "id": "ko3rqWr3W74NUQbRi0SKz", + "type": "arrow" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false + }, + { + "id": "FZuLAXlCHhgaSwbnqeq-R", + "type": "text", + "x": -392.0937393101285, + "y": -19.4377971369031, + "width": 179.3000030517578, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "aud", + "roundness": null, + "seed": 1053505976, + "version": 164, + "versionNonce": 738240696, + "isDeleted": false, + "boundElements": null, + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Supervisor", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "iJTtIjuF5BPf2AA9OiSM-", + "originalText": "Supervisor", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "u4UP0pE9dcRSdKQE-6Mii", + "type": "rectangle", + "x": 479.2472092990762, + "y": -60.258507725531956, + "width": 232.52675967171302, + "height": 105.23108831858542, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "auf", + "roundness": { + "type": 3 + }, + "seed": 748774840, + "version": 177, + "versionNonce": 863509432, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "SaTGVviYKVtxUtNesULpD" + }, + { + "id": "dhhjZ1q6N_9xmmb-_TsHv", + "type": "arrow" + }, + { + "id": "VNBuMuhrBdk6-qyRSJScX", + "type": "arrow" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false + }, + { + "id": "SaTGVviYKVtxUtNesULpD", + "type": "text", + "x": 537.0105891349327, + "y": -30.142963566239246, + "width": 117, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "auh", + "roundness": null, + "seed": 266071736, + "version": 188, + "versionNonce": 140169656, + "isDeleted": false, + "boundElements": [], + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Worker", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "u4UP0pE9dcRSdKQE-6Mii", + "originalText": "Worker", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "hSITs0-BXLZEuSLyckysl", + "type": "rectangle", + "x": -418.70711762010626, + "y": 65.43708182612954, + "width": 232.52675967171302, + "height": 230.23275500747474, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "aul", + "roundness": { + "type": 3 + }, + "seed": 2050118584, + "version": 260, + "versionNonce": 2104831176, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "Xq3pK4WcjMe9kDzA9L8RF" + }, + { + "id": "VF2PQtfx_p4AoqPL252S-", + "type": "arrow" + }, + { + "id": "ffTH8nDNfJXaJdW5_qSo7", + "type": "arrow" + }, + { + "id": "xLiK-jHXVGxJoxXujP5EK", + "type": "arrow" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false + }, + { + "id": "Xq3pK4WcjMe9kDzA9L8RF", + "type": "text", + "x": -404.5437362583708, + "y": 158.0534593298669, + "width": 204.1999969482422, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "aun", + "roundness": null, + "seed": 421469368, + "version": 288, + "versionNonce": 159898296, + "isDeleted": false, + "boundElements": [], + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "JobManager", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "hSITs0-BXLZEuSLyckysl", + "originalText": "JobManager", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "6GLKwsy__oleqFg_DKe0C", + "type": "rectangle", + "x": 466.80821608790006, + "y": 75.18708770038239, + "width": 232.52675967171302, + "height": 105.23108831858542, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "aup", + "roundness": { + "type": 3 + }, + "seed": 1837513672, + "version": 251, + "versionNonce": 436112824, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "SxbeBknFLTgcxtsDDaqhn" + }, + { + "id": "VF2PQtfx_p4AoqPL252S-", + "type": "arrow" + }, + { + "id": "C6HTF3smGfNHQai67ofiv", + "type": "arrow" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false + }, + { + "id": "SxbeBknFLTgcxtsDDaqhn", + "type": "text", + "x": 477.4132630990496, + "y": 105.3026318596751, + "width": 211.31666564941406, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "aut", + "roundness": null, + "seed": 2010874568, + "version": 313, + "versionNonce": 1378599864, + "isDeleted": false, + "boundElements": [], + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "JobSchedule", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "6GLKwsy__oleqFg_DKe0C", + "originalText": "JobSchedule", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "LNIk5qXErRLGTE77O-C8E", + "type": "rectangle", + "x": 468.5819585280833, + "y": 204.02738886076358, + "width": 232.52675967171302, + "height": 105.23108831858542, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "auv", + "roundness": { + "type": 3 + }, + "seed": 28821192, + "version": 235, + "versionNonce": 633342152, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "jLala9XQT2GHCQh9pgdvp" + }, + { + "id": "3JcNs_ceXa04q4Fy8M3AQ", + "type": "arrow" + }, + { + "id": "wxkhOnEba572OmVshH7Us", + "type": "arrow" + } + ], + "updated": 1768793963371, + "link": null, + "locked": false + }, + { + "id": "jLala9XQT2GHCQh9pgdvp", + "type": "text", + "x": 553.8120055392328, + "y": 234.1429330200563, + "width": 62.06666564941406, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "aux", + "roundness": null, + "seed": 1134800328, + "version": 254, + "versionNonce": 275418296, + "isDeleted": false, + "boundElements": [], + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Job", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "LNIk5qXErRLGTE77O-C8E", + "originalText": "Job", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "PGvAIRHl59laFP1wJL99F", + "type": "rectangle", + "x": 34.69319403123575, + "y": 227.6934164552067, + "width": 172.50230003066707, + "height": 67.50090001200022, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "av", + "roundness": { + "type": 3 + }, + "seed": 622740920, + "version": 141, + "versionNonce": 950522056, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "2NMG9x8eeSBS7dARiFw0I" + }, + { + "id": "3JcNs_ceXa04q4Fy8M3AQ", + "type": "arrow" + }, + { + "id": "ffTH8nDNfJXaJdW5_qSo7", + "type": "arrow" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false + }, + { + "id": "2NMG9x8eeSBS7dARiFw0I", + "type": "text", + "x": 68.1110119848017, + "y": 251.4438664612068, + "width": 105.66666412353516, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "av8", + "roundness": null, + "seed": 1715303352, + "version": 167, + "versionNonce": 1231296200, + "isDeleted": false, + "boundElements": null, + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Render Event", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "PGvAIRHl59laFP1wJL99F", + "originalText": "Render Event", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "HO5XzQNUlewF1NeJ8b9MQ", + "type": "rectangle", + "x": 37.5657980504497, + "y": 327.36947243008046, + "width": 172.50230003066707, + "height": 67.50090001200022, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "avG", + "roundness": { + "type": 3 + }, + "seed": 43763144, + "version": 188, + "versionNonce": 559342520, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "76fndoOHH8pamjTEAJNQF" + }, + { + "id": "M9zqCB7wcQlPUwpQcDaja", + "type": "arrow" + }, + { + "id": "xLiK-jHXVGxJoxXujP5EK", + "type": "arrow" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false + }, + { + "id": "76fndoOHH8pamjTEAJNQF", + "type": "text", + "x": 84.48361600401566, + "y": 351.11992243608057, + "width": 78.66666412353516, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "avV", + "roundness": null, + "seed": 632278216, + "version": 217, + "versionNonce": 680822456, + "isDeleted": false, + "boundElements": null, + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "File Event", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "HO5XzQNUlewF1NeJ8b9MQ", + "originalText": "File Event", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "1C-V6FBRBZt1Bd4DqrDq9", + "type": "rectangle", + "x": 40.10993292108756, + "y": 91.85827198661383, + "width": 172.50230003066707, + "height": 67.50090001200022, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "avd", + "roundness": { + "type": 3 + }, + "seed": 1221418168, + "version": 117, + "versionNonce": 475091896, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "7OryM90mKgFF8Q0Hl6TTZ" + }, + { + "id": "VF2PQtfx_p4AoqPL252S-", + "type": "arrow" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false + }, + { + "id": "7OryM90mKgFF8Q0Hl6TTZ", + "type": "text", + "x": 86.57775011171407, + "y": 115.60872199261394, + "width": 79.56666564941406, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "avl", + "roundness": null, + "seed": 1366361528, + "version": 116, + "versionNonce": 1900412360, + "isDeleted": false, + "boundElements": [], + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Job Event", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "1C-V6FBRBZt1Bd4DqrDq9", + "originalText": "Job Event", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "AXSYFY_GFdM-eU0etWSJs", + "type": "rectangle", + "x": 37.60989958731008, + "y": 9.357171971946741, + "width": 172.50230003066707, + "height": 67.50090001200022, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "aw", + "roundness": { + "type": 3 + }, + "seed": 1760130248, + "version": 143, + "versionNonce": 1833727176, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "Z-V_YiDmv-063k_XeYytO" + }, + { + "id": "ko3rqWr3W74NUQbRi0SKz", + "type": "arrow" + }, + { + "id": "VNBuMuhrBdk6-qyRSJScX", + "type": "arrow" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false + }, + { + "id": "Z-V_YiDmv-063k_XeYytO", + "type": "text", + "x": 78.86938471616901, + "y": 33.10762197794685, + "width": 89.98332977294922, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "awG", + "roundness": null, + "seed": 1953471432, + "version": 169, + "versionNonce": 333614008, + "isDeleted": false, + "boundElements": [], + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Node Event", + "fontSize": 16, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "AXSYFY_GFdM-eU0etWSJs", + "originalText": "Node Event", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "I66YaUTkYLMJ0_adpa6Pf", + "type": "rectangle", + "x": 465.73840202372617, + "y": 328.19529014764294, + "width": 232.52675967171302, + "height": 89.1671222282964, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "awV", + "roundness": { + "type": 3 + }, + "seed": 714130360, + "version": 270, + "versionNonce": 30273976, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "SV5mB5HHfnUyfxpGJ_5HZ" + }, + { + "id": "M9zqCB7wcQlPUwpQcDaja", + "type": "arrow" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false + }, + { + "id": "SV5mB5HHfnUyfxpGJ_5HZ", + "type": "text", + "x": 497.4767803337038, + "y": 350.27885126179115, + "width": 169.0500030517578, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "ax", + "roundness": null, + "seed": 338660536, + "version": 333, + "versionNonce": 545976, + "isDeleted": false, + "boundElements": [], + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Database", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "I66YaUTkYLMJ0_adpa6Pf", + "originalText": "Database", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "iqANu3qYKnTm6PYrsGqFc", + "type": "rectangle", + "x": -411.5745858632879, + "y": 321.02836092113284, + "width": 232.52675967171302, + "height": 89.1671222282964, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "axV", + "roundness": { + "type": 3 + }, + "seed": 582908856, + "version": 365, + "versionNonce": 585003720, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "8oN273RVW_2jaM_3sXhiO" + }, + { + "id": "xLiK-jHXVGxJoxXujP5EK", + "type": "arrow" + } + ], + "updated": 1768793935587, + "link": null, + "locked": false + }, + { + "id": "8oN273RVW_2jaM_3sXhiO", + "type": "text", + "x": -379.83620755331026, + "y": 343.11192203528105, + "width": 169.0500030517578, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "mUv5IzxC9f215gBQyFtLt" + ], + "frameId": null, + "index": "ay", + "roundness": null, + "seed": 770065592, + "version": 432, + "versionNonce": 1452080568, + "isDeleted": false, + "boundElements": [], + "updated": 1768793935587, + "link": null, + "locked": false, + "text": "Database", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "iqANu3qYKnTm6PYrsGqFc", + "originalText": "Database", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "M9zqCB7wcQlPUwpQcDaja", + "type": "arrow", + "x": 207.56819808245018, + "y": 364.0367613272657, + "width": 247.170203941276, + "height": 2.635801826420959, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b02", + "roundness": { + "type": 2 + }, + "seed": 510879688, + "version": 215, + "versionNonce": 1038508232, + "isDeleted": false, + "boundElements": null, + "updated": 1768793935587, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 247.170203941276, + -2.635801826420959 + ] + ], + "startBinding": { + "elementId": "HO5XzQNUlewF1NeJ8b9MQ", + "mode": "inside", + "fixedPoint": [ + 0.9855080193236718, + 0.5432118518518506 + ] + }, + "endBinding": { + "elementId": "I66YaUTkYLMJ0_adpa6Pf", + "mode": "orbit", + "fixedPoint": [ + 0.3610423712287407, + 0.3610423712287408 + ] + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "xLiK-jHXVGxJoxXujP5EK", + "type": "arrow", + "x": -175.18035794839324, + "y": 249.90780254270828, + "width": 202.1160955076612, + "height": 88.85635524737768, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b03", + "roundness": { + "type": 2 + }, + "seed": 40061880, + "version": 231, + "versionNonce": 1955390904, + "isDeleted": false, + "boundElements": null, + "updated": 1768793935604, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 202.1160955076612, + 88.85635524737768 + ] + ], + "startBinding": { + "elementId": "hSITs0-BXLZEuSLyckysl", + "mode": "orbit", + "fixedPoint": [ + 0.6047248309576458, + 0.6047248309576458 + ] + }, + "endBinding": { + "elementId": "HO5XzQNUlewF1NeJ8b9MQ", + "mode": "orbit", + "fixedPoint": [ + 0.3588233676159732, + 0.6411766323840272 + ] + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "J3AjrcTauZgXaqEBAoi6P", + "type": "text", + "x": 1136.1382866776255, + "y": 434.3976449696289, + "width": 14.399999618530273, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b04", + "roundness": null, + "seed": 276506056, + "version": 3, + "versionNonce": 1973660856, + "isDeleted": true, + "boundElements": null, + "updated": 1768793975138, + "link": null, + "locked": false, + "text": "", + "fontSize": 36, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file