diff --git a/package-lock.json b/package-lock.json index 98613253..56c3d059 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,9 +22,9 @@ "src-web" ], "devDependencies": { - "@tauri-apps/cli": "^2.2.7", - "@typescript-eslint/eslint-plugin": "^8.18.1", - "@typescript-eslint/parser": "^8.18.1", + "@tauri-apps/cli": "^2.4.0", + "@typescript-eslint/eslint-plugin": "^8.27.0", + "@typescript-eslint/parser": "^8.27.0", "eslint": "^8", "eslint-config-prettier": "^8", "eslint-plugin-import": "^2.31.0", @@ -34,7 +34,7 @@ "nodejs-file-downloader": "^4.13.0", "npm-run-all": "^4.1.5", "prettier": "^3.4.2", - "typescript": "^5.7.2" + "typescript": "^5.8.2" } }, "node_modules/@alloc/quick-lru": { @@ -2782,9 +2782,9 @@ } }, "node_modules/@tauri-apps/cli": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.2.7.tgz", - "integrity": "sha512-ZnsS2B4BplwXP37celanNANiIy8TCYhvg5RT09n72uR/o+navFZtGpFSqljV8fy1Y4ixIPds8FrGSXJCN2BerA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.4.0.tgz", + "integrity": "sha512-Esg7s20tuSULd2YF3lmtMa1vF7yr5eh/TlBHXjEyrC+XSD9aBxHVoXb6oz7oKybDY9Jf9OiBa0bf2PbybcmOLA==", "dev": true, "license": "Apache-2.0 OR MIT", "bin": { @@ -2798,22 +2798,23 @@ "url": "https://opencollective.com/tauri" }, "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.2.7", - "@tauri-apps/cli-darwin-x64": "2.2.7", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.2.7", - "@tauri-apps/cli-linux-arm64-gnu": "2.2.7", - "@tauri-apps/cli-linux-arm64-musl": "2.2.7", - "@tauri-apps/cli-linux-x64-gnu": "2.2.7", - "@tauri-apps/cli-linux-x64-musl": "2.2.7", - "@tauri-apps/cli-win32-arm64-msvc": "2.2.7", - "@tauri-apps/cli-win32-ia32-msvc": "2.2.7", - "@tauri-apps/cli-win32-x64-msvc": "2.2.7" + "@tauri-apps/cli-darwin-arm64": "2.4.0", + "@tauri-apps/cli-darwin-x64": "2.4.0", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.4.0", + "@tauri-apps/cli-linux-arm64-gnu": "2.4.0", + "@tauri-apps/cli-linux-arm64-musl": "2.4.0", + "@tauri-apps/cli-linux-riscv64-gnu": "2.4.0", + "@tauri-apps/cli-linux-x64-gnu": "2.4.0", + "@tauri-apps/cli-linux-x64-musl": "2.4.0", + "@tauri-apps/cli-win32-arm64-msvc": "2.4.0", + "@tauri-apps/cli-win32-ia32-msvc": "2.4.0", + "@tauri-apps/cli-win32-x64-msvc": "2.4.0" } }, "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.2.7.tgz", - "integrity": "sha512-54kcpxZ3X1Rq+pPTzk3iIcjEVY4yv493uRx/80rLoAA95vAC0c//31Whz75UVddDjJfZvXlXZ3uSZ+bnCOnt0A==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.4.0.tgz", + "integrity": "sha512-MVzYrahJBgDyzUJ2gNEU8H+0oCVEucN115+CVorFnidHcJ6DtDRMCaKLkpjOZNfJyag1WQ25fu7imvZSe0mz/g==", "cpu": [ "arm64" ], @@ -2828,9 +2829,9 @@ } }, "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.2.7.tgz", - "integrity": "sha512-Vgu2XtBWemLnarB+6LqQeLanDlRj7CeFN//H8bVVdjbNzxcSxsvbLYMBP8+3boa7eBnjDrqMImRySSgL6IrwTw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.4.0.tgz", + "integrity": "sha512-/4IdbWv6IWSuBn0WVe5JkiSIP1gZhXCZRcumSsYq3ZmOlq4BqXeXT36Oig5mlDnS/2/UpNS94kd8gOA1DNdIeQ==", "cpu": [ "x64" ], @@ -2845,9 +2846,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.2.7.tgz", - "integrity": "sha512-+Clha2iQAiK9zoY/KKW0KLHkR0k36O78YLx5Sl98tWkwI3OBZFg5H5WT1plH/4sbZIS2aLFN6dw58/JlY9Bu/g==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.4.0.tgz", + "integrity": "sha512-rOjlk3Vd6R847LXds4pOAFKUL5NVdSWlaiQvr4H9WDUwIWWoxnj7SQfpryTtElDb2wV7a0BNaOCXCpyFEAXjkw==", "cpu": [ "arm" ], @@ -2862,9 +2863,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.2.7.tgz", - "integrity": "sha512-Z/Lp4SQe6BUEOays9BQAEum2pvZF4w9igyXijP+WbkOejZx4cDvarFJ5qXrqSLmBh7vxrdZcLwoLk9U//+yQrg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.4.0.tgz", + "integrity": "sha512-X/uCwao6R/weWT2y4f3JKJMeUsujo9H4nBMAv9RZhRsz93n9Amw9ETavAOP11pyhl57YkasvKTCRQN6FwsaoXg==", "cpu": [ "arm64" ], @@ -2879,9 +2880,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.7.tgz", - "integrity": "sha512-+8HZ+txff/Y3YjAh80XcLXcX8kpGXVdr1P8AfjLHxHdS6QD4Md+acSxGTTNbplmHuBaSHJvuTvZf9tU1eDCTDg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.0.tgz", + "integrity": "sha512-GhvQtrTjadW3eLSmfrSfybSqgJMZzUXC+0WqDzFovLug3a1a1go0m9QK9YGvYLkyEpTY5zRxLtwv+tbZXN4tZw==", "cpu": [ "arm64" ], @@ -2895,10 +2896,27 @@ "node": ">= 10" } }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.4.0.tgz", + "integrity": "sha512-NgeNihQ9uHS/ibMWLge5VA/BgsS/g8VPSVtCp8DSPyub3bBuCy79A8V+bzNKlMOiDVrqK4vQ//FS9kSxoJOtXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.2.7.tgz", - "integrity": "sha512-ahlSnuCnUntblp9dG7/w5ZWZOdzRFi3zl0oScgt7GF4KNAOEa7duADsxPA4/FT2hLRa0SvpqtD4IYFvCxoVv3Q==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.4.0.tgz", + "integrity": "sha512-ebRmV2HLIVms1KlNNueQCp3OrXBv6cimU3mYEh5NbZ8dH88f2sF46dFCyPq8Qr/Zti4qPEaAArVG7RYFjfECPw==", "cpu": [ "x64" ], @@ -2913,9 +2931,9 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.7.tgz", - "integrity": "sha512-+qKAWnJRSX+pjjRbKAQgTdFY8ecdcu8UdJ69i7wn3ZcRn2nMMzOO2LOMOTQV42B7/Q64D1pIpmZj9yblTMvadA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.0.tgz", + "integrity": "sha512-FOp2cBFyq5LnUr3he95Z99PQm3nCSJv2GZNeH7UqmUpeHwdcYuhBERU7C+8VDJJPR98Q5fkcoV00Pc4nw0v5KQ==", "cpu": [ "x64" ], @@ -2930,9 +2948,9 @@ } }, "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.2.7.tgz", - "integrity": "sha512-aa86nRnrwT04u9D9fhf5JVssuAZlUCCc8AjqQjqODQjMd4BMA2+d4K9qBMpEG/1kVh95vZaNsLogjEaqSTTw4A==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.4.0.tgz", + "integrity": "sha512-SVf1wDagYsaFM+mpUYKmjNveKodcUSGPEM27WmMW4Enh6aXGzTJi4IYOE3GEFOJF1BpRNscslwE1Rd064kfpcg==", "cpu": [ "arm64" ], @@ -2947,9 +2965,9 @@ } }, "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.2.7.tgz", - "integrity": "sha512-EiJ5/25tLSQOSGvv+t6o3ZBfOTKB5S3vb+hHQuKbfmKdRF0XQu2YPdIi1CQw1DU97ZAE0Dq4frvnyYEKWgMzVQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.4.0.tgz", + "integrity": "sha512-j+fOFVeSSejk9hrUePY7bJuaYr+80xr+ftjXzxCj0CS0d2oSbq+lT8/zS514WemJk9e9yxUus+2ke/Ng17wkkQ==", "cpu": [ "ia32" ], @@ -2964,9 +2982,9 @@ } }, "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.2.7.tgz", - "integrity": "sha512-ZB8Kw90j8Ld+9tCWyD2fWCYfIrzbQohJ4DJSidNwbnehlZzP7wAz6Z3xjsvUdKtQ3ibtfoeTqVInzCCEpI+pWg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.4.0.tgz", + "integrity": "sha512-nv84b3a8eI5Y7ksTLBKjjvtr9NOlAGGGo7OJbjKT+xZLdiPOZ0nJ2cT+4IdfnNAZ33pKJugAfuj1fBvQKeTy0w==", "cpu": [ "x64" ], @@ -3296,21 +3314,21 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz", - "integrity": "sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==", + "version": "8.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.27.0.tgz", + "integrity": "sha512-4henw4zkePi5p252c8ncBLzLce52SEUz2Ebj8faDnuUXz2UuHEONYcJ+G0oaCF+bYCWVZtrGzq3FD7YXetmnSA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.18.1", - "@typescript-eslint/type-utils": "8.18.1", - "@typescript-eslint/utils": "8.18.1", - "@typescript-eslint/visitor-keys": "8.18.1", + "@typescript-eslint/scope-manager": "8.27.0", + "@typescript-eslint/type-utils": "8.27.0", + "@typescript-eslint/utils": "8.27.0", + "@typescript-eslint/visitor-keys": "8.27.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3322,20 +3340,20 @@ "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.1.tgz", - "integrity": "sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==", + "version": "8.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.27.0.tgz", + "integrity": "sha512-XGwIabPallYipmcOk45DpsBSgLC64A0yvdAkrwEzwZ2viqGqRUJ8eEYoPz0CWnutgAFbNMPdsGGvzjSmcWVlEA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.18.1", - "@typescript-eslint/types": "8.18.1", - "@typescript-eslint/typescript-estree": "8.18.1", - "@typescript-eslint/visitor-keys": "8.18.1", + "@typescript-eslint/scope-manager": "8.27.0", + "@typescript-eslint/types": "8.27.0", + "@typescript-eslint/typescript-estree": "8.27.0", + "@typescript-eslint/visitor-keys": "8.27.0", "debug": "^4.3.4" }, "engines": { @@ -3347,18 +3365,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.1.tgz", - "integrity": "sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==", + "version": "8.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.27.0.tgz", + "integrity": "sha512-8oI9GwPMQmBryaaxG1tOZdxXVeMDte6NyJA4i7/TWa4fBwgnAXYlIQP+uYOeqAaLJ2JRxlG9CAyL+C+YE9Xknw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.1", - "@typescript-eslint/visitor-keys": "8.18.1" + "@typescript-eslint/types": "8.27.0", + "@typescript-eslint/visitor-keys": "8.27.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3369,16 +3387,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.1.tgz", - "integrity": "sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ==", + "version": "8.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.27.0.tgz", + "integrity": "sha512-wVArTVcz1oJOIEJxui/nRhV0TXzD/zMSOYi/ggCfNq78EIszddXcJb7r4RCp/oBrjt8n9A0BSxRMKxHftpDxDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.18.1", - "@typescript-eslint/utils": "8.18.1", + "@typescript-eslint/typescript-estree": "8.27.0", + "@typescript-eslint/utils": "8.27.0", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3389,13 +3407,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.1.tgz", - "integrity": "sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==", + "version": "8.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.27.0.tgz", + "integrity": "sha512-/6cp9yL72yUHAYq9g6DsAU+vVfvQmd1a8KyA81uvfDE21O2DwQ/qxlM4AR8TSdAu+kJLBDrEHKC5/W2/nxsY0A==", "dev": true, "license": "MIT", "engines": { @@ -3407,20 +3425,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.1.tgz", - "integrity": "sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==", + "version": "8.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.27.0.tgz", + "integrity": "sha512-BnKq8cqPVoMw71O38a1tEb6iebEgGA80icSxW7g+kndx0o6ot6696HjG7NdgfuAVmVEtwXUr3L8R9ZuVjoQL6A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.1", - "@typescript-eslint/visitor-keys": "8.18.1", + "@typescript-eslint/types": "8.27.0", + "@typescript-eslint/visitor-keys": "8.27.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3430,20 +3448,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.1.tgz", - "integrity": "sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==", + "version": "8.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.27.0.tgz", + "integrity": "sha512-njkodcwH1yvmo31YWgRHNb/x1Xhhq4/m81PhtvmRngD8iHPehxffz1SNCO+kwaePhATC+kOa/ggmvPoPza5i0Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.18.1", - "@typescript-eslint/types": "8.18.1", - "@typescript-eslint/typescript-estree": "8.18.1" + "@typescript-eslint/scope-manager": "8.27.0", + "@typescript-eslint/types": "8.27.0", + "@typescript-eslint/typescript-estree": "8.27.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3454,17 +3472,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.1.tgz", - "integrity": "sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==", + "version": "8.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.27.0.tgz", + "integrity": "sha512-WsXQwMkILJvffP6z4U3FYJPlbf/j07HIxmDjZpbNvBJkMfvwXj5ACRkkHwBDvLBbDbtX5TdU64/rcvKJ/vuInQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/types": "8.27.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -13842,16 +13860,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-easing": { @@ -14440,9 +14458,9 @@ } }, "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -15562,9 +15580,7 @@ "@types/node": "^22.5.4" }, "devDependencies": { - "cpy-cli": "^5.0.0", - "npm-run-all": "^4.1.5", - "typescript": "^5.6.2" + "cpy-cli": "^5.0.0" } }, "packages/plugin-runtime/node_modules/ws": { diff --git a/package.json b/package.json index 73bc76df..6fbb1aaf 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,9 @@ "tauri-before-dev": "npm run --workspaces --if-present dev" }, "devDependencies": { - "@tauri-apps/cli": "^2.2.7", - "@typescript-eslint/eslint-plugin": "^8.18.1", - "@typescript-eslint/parser": "^8.18.1", + "@tauri-apps/cli": "^2.4.0", + "@typescript-eslint/eslint-plugin": "^8.27.0", + "@typescript-eslint/parser": "^8.27.0", "eslint": "^8", "eslint-config-prettier": "^8", "eslint-plugin-import": "^2.31.0", @@ -48,6 +48,6 @@ "nodejs-file-downloader": "^4.13.0", "npm-run-all": "^4.1.5", "prettier": "^3.4.2", - "typescript": "^5.7.2" + "typescript": "^5.8.2" } } diff --git a/packages/plugin-runtime-types/package.json b/packages/plugin-runtime-types/package.json index e376f5e9..5b6ee95f 100644 --- a/packages/plugin-runtime-types/package.json +++ b/packages/plugin-runtime-types/package.json @@ -20,8 +20,6 @@ "@types/node": "^22.5.4" }, "devDependencies": { - "cpy-cli": "^5.0.0", - "npm-run-all": "^4.1.5", - "typescript": "^5.6.2" + "cpy-cli": "^5.0.0" } } diff --git a/packages/plugin-runtime/src/PluginHandle.ts b/packages/plugin-runtime/src/PluginHandle.ts index 18dbbdb8..25adcfc3 100644 --- a/packages/plugin-runtime/src/PluginHandle.ts +++ b/packages/plugin-runtime/src/PluginHandle.ts @@ -15,7 +15,6 @@ export class PluginHandle { bootRequest: this.bootRequest, }; this.#instance = new PluginInstance(workerData, pluginToAppEvents); - console.log('Created plugin worker for ', this.bootRequest.dir); } sendToWorker(event: InternalEvent) { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 99aa0112..1c5289a6 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -543,6 +543,15 @@ dependencies = [ "objc2 0.5.2", ] +[[package]] +name = "block2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d59b4c170e16f0405a2e95aff44432a0d41aa97675f3d52623effe95792a037" +dependencies = [ + "objc2 0.6.0", +] + [[package]] name = "blocking" version = "1.6.1" @@ -737,9 +746,9 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.21.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fbd1fe9db3ebf71b89060adaf7b0504c2d6a425cf061313099547e382c2e472" +checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" dependencies = [ "serde", "toml", @@ -825,22 +834,6 @@ dependencies = [ "error-code", ] -[[package]] -name = "cocoa" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation 0.1.2", - "core-foundation 0.9.4", - "core-graphics 0.23.2", - "foreign-types 0.5.0", - "libc", - "objc", -] - [[package]] name = "cocoa" version = "0.26.0" @@ -849,7 +842,7 @@ checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" dependencies = [ "bitflags 2.6.0", "block", - "cocoa-foundation 0.2.0", + "cocoa-foundation", "core-foundation 0.10.0", "core-graphics 0.24.0", "foreign-types 0.5.0", @@ -857,20 +850,6 @@ dependencies = [ "objc", ] -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "libc", - "objc", -] - [[package]] name = "cocoa-foundation" version = "0.2.0" @@ -1252,34 +1231,13 @@ dependencies = [ "subtle", ] -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", -] - [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys 0.5.0", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.4.5", - "windows-sys 0.48.0", + "dirs-sys", ] [[package]] @@ -1290,7 +1248,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.0", + "redox_users", "windows-sys 0.59.0", ] @@ -2436,9 +2394,9 @@ dependencies = [ [[package]] name = "ico" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" dependencies = [ "byteorder", "png", @@ -3031,21 +2989,22 @@ dependencies = [ [[package]] name = "muda" -version = "0.15.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8123dfd4996055ac9b15a60ad263b44b01e539007523ad7a4a533a3d93b0591" +checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492" dependencies = [ "crossbeam-channel", "dpi", "gtk", "keyboard-types", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", + "objc2 0.6.0", + "objc2-app-kit 0.3.0", + "objc2-core-foundation", + "objc2-foundation 0.3.0", "once_cell", "png", "serde", - "thiserror 1.0.63", + "thiserror 2.0.11", "windows-sys 0.59.0", ] @@ -3289,9 +3248,6 @@ name = "objc-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" -dependencies = [ - "cc", -] [[package]] name = "objc2" @@ -3310,6 +3266,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59" dependencies = [ "objc2-encode", + "objc2-exception-helper", ] [[package]] @@ -3322,10 +3279,10 @@ dependencies = [ "block2 0.5.1", "libc", "objc2 0.5.2", - "objc2-core-data", - "objc2-core-image", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", "objc2-foundation 0.2.2", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", ] [[package]] @@ -3335,32 +3292,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb" dependencies = [ "bitflags 2.6.0", + "block2 0.6.0", + "libc", "objc2 0.6.0", + "objc2-cloud-kit", + "objc2-core-data 0.3.0", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image 0.3.0", "objc2-foundation 0.3.0", + "objc2-quartz-core 0.3.0", ] [[package]] name = "objc2-cloud-kit" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +checksum = "6c1948a9be5f469deadbd6bcb86ad7ff9e47b4f632380139722f7d9840c0d42c" dependencies = [ "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", + "objc2 0.6.0", + "objc2-foundation 0.3.0", ] [[package]] @@ -3375,6 +3327,17 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-core-data" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f860f8e841f6d32f754836f51e6bc7777cd7e7053cf18528233f6811d3eceb4" +dependencies = [ + "bitflags 2.6.0", + "objc2 0.6.0", + "objc2-foundation 0.3.0", +] + [[package]] name = "objc2-core-foundation" version = "0.3.0" @@ -3385,6 +3348,18 @@ dependencies = [ "objc2 0.6.0", ] +[[package]] +name = "objc2-core-graphics" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02" +dependencies = [ + "bitflags 2.6.0", + "objc2 0.6.0", + "objc2-core-foundation", + "objc2-io-surface", +] + [[package]] name = "objc2-core-image" version = "0.2.2" @@ -3398,15 +3373,13 @@ dependencies = [ ] [[package]] -name = "objc2-core-location" -version = "0.2.2" +name = "objc2-core-image" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +checksum = "6ffa6bea72bf42c78b0b34e89c0bafac877d5f80bf91e159a5d96ea7f693ca56" dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-contacts", - "objc2-foundation 0.2.2", + "objc2 0.6.0", + "objc2-foundation 0.3.0", ] [[package]] @@ -3415,6 +3388,15 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + [[package]] name = "objc2-foundation" version = "0.2.2" @@ -3435,20 +3417,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998" dependencies = [ "bitflags 2.6.0", + "block2 0.6.0", + "libc", "objc2 0.6.0", "objc2-core-foundation", ] [[package]] -name = "objc2-link-presentation" -version = "0.2.2" +name = "objc2-io-surface" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19" dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", + "bitflags 2.6.0", + "objc2 0.6.0", + "objc2-core-foundation", ] [[package]] @@ -3489,71 +3472,40 @@ dependencies = [ ] [[package]] -name = "objc2-symbols" -version = "0.2.2" +name = "objc2-quartz-core" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071" dependencies = [ - "objc2 0.5.2", - "objc2-foundation 0.2.2", + "bitflags 2.6.0", + "objc2 0.6.0", + "objc2-foundation 0.3.0", ] [[package]] name = "objc2-ui-kit" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +checksum = "777a571be14a42a3990d4ebedaeb8b54cd17377ec21b92e8200ac03797b3bee1" dependencies = [ "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation 0.2.2", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", + "objc2 0.6.0", + "objc2-core-foundation", + "objc2-foundation 0.3.0", ] [[package]] name = "objc2-web-kit" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65" +checksum = "b717127e4014b0f9f3e8bba3d3f2acec81f1bde01f656823036e823ed2c94dce" dependencies = [ "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", + "block2 0.6.0", + "objc2 0.6.0", + "objc2-app-kit 0.3.0", + "objc2-core-foundation", + "objc2-foundation 0.3.0", ] [[package]] @@ -3567,9 +3519,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" [[package]] name = "open" @@ -4459,17 +4411,6 @@ dependencies = [ "bitflags 2.6.0", ] -[[package]] -name = "redox_users" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" -dependencies = [ - "getrandom 0.2.15", - "libredox", - "thiserror 1.0.63", -] - [[package]] name = "redox_users" version = "0.5.0" @@ -4483,9 +4424,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -5291,7 +5232,7 @@ dependencies = [ "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", "raw-window-handle", "redox_syscall 0.5.3", "wasm-bindgen", @@ -5708,12 +5649,11 @@ dependencies = [ [[package]] name = "tao" -version = "0.31.1" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3731d04d4ac210cd5f344087733943b9bfb1a32654387dad4d1c70de21aee2c9" +checksum = "63c8b1020610b9138dd7b1e06cf259ae91aa05c30f3bd0d6b42a03997b92dec1" dependencies = [ "bitflags 2.6.0", - "cocoa 0.26.0", "core-foundation 0.10.0", "core-graphics 0.24.0", "crossbeam-channel", @@ -5730,7 +5670,9 @@ dependencies = [ "ndk", "ndk-context", "ndk-sys", - "objc", + "objc2 0.6.0", + "objc2-app-kit 0.3.0", + "objc2-foundation 0.3.0", "once_cell", "parking_lot", "raw-window-handle", @@ -5738,8 +5680,8 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows 0.58.0", - "windows-core 0.58.0", + "windows", + "windows-core 0.60.1", "windows-version", "x11-dl", ] @@ -5780,13 +5722,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.2.5" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58a998b6be84104ca05c7e9a21f2180ddec020c8b84ea59a8fc8530a2a19588d" +checksum = "511dd38065a5d3b36c33cdba4362b99a40a5103bebcd4aebb930717e7c8ba292" dependencies = [ "anyhow", "bytes", - "dirs 6.0.0", + "dirs", "dunce", "embed_plist", "futures-util", @@ -5801,9 +5743,9 @@ dependencies = [ "log", "mime", "muda", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", + "objc2 0.6.0", + "objc2-app-kit 0.3.0", + "objc2-foundation 0.3.0", "percent-encoding", "plist", "raw-window-handle", @@ -5826,18 +5768,18 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows 0.58.0", + "windows", ] [[package]] name = "tauri-build" -version = "2.0.6" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a2e96f3c0baa0581656bb58e6fdd0f7c9c31eaf6721a0c08689d938fe85f2d" +checksum = "7ffa8732a66f90903f5a585215f3cf1e87988d0359bc88c18a502efe7572c1de" dependencies = [ "anyhow", "cargo_toml", - "dirs 6.0.0", + "dirs", "glob", "heck 0.5.0", "json-patch", @@ -5853,9 +5795,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f77894f9ddb5cb6c04fcfe8c8869ebe0aded4dabf19917118d48be4a95599ab5" +checksum = "c266a247f14d63f40c6282c2653a8bac5cc3d482ca562a003a88513653ea817a" dependencies = [ "base64 0.22.1", "brotli 7.0.0", @@ -5880,9 +5822,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3240a5caed760a532e8f687be6f05b2c7d11a1d791fb53ccc08cfeb3e5308736" +checksum = "f47a1cf94b3bd6c4dc37dce1a43fc96120ff29a91757f0ab3cf713c7ad846e7c" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -5894,9 +5836,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5841b9a0200e954ef7457f8d327091424328891e267a97b641dc246cc54d0dec" +checksum = "9972871fcbddf16618f70412d965d4d845cd4b76d03fff168709961ef71e5cdf" dependencies = [ "anyhow", "glob", @@ -6005,7 +5947,7 @@ dependencies = [ "tauri-plugin", "thiserror 2.0.11", "url", - "windows 0.60.0", + "windows", "zbus 5.3.0", ] @@ -6070,7 +6012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31bfcfb4a8318008d2108ccfba439d8263cf48867baabf372cb0e9f24771896" dependencies = [ "base64 0.22.1", - "dirs 6.0.0", + "dirs", "flate2", "futures-util", "http", @@ -6112,10 +6054,11 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2274ef891ccc0a8d318deffa9d70053f947664d12d58b9c0d1ae5e89237e01f7" +checksum = "9e9c7bce5153f1ca7bc45eba37349b31ba50e975e28edc8b5766c5ec02b0b63a" dependencies = [ + "cookie", "dpi", "gtk", "http", @@ -6126,22 +6069,23 @@ dependencies = [ "tauri-utils", "thiserror 2.0.11", "url", - "windows 0.58.0", + "windows", ] [[package]] name = "tauri-runtime-wry" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3707b40711d3b9f6519150869e358ffbde7c57567fb9b5a8b51150606939b2a0" +checksum = "087188020fd6facb8578fe9b38e81fa0fe5fb85744c73da51a299f94a530a1e3" dependencies = [ "gtk", "http", "jni", "log", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", + "objc2 0.6.0", + "objc2-app-kit 0.3.0", + "objc2-foundation 0.3.0", + "once_cell", "percent-encoding", "raw-window-handle", "softbuffer", @@ -6151,16 +6095,17 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows 0.58.0", + "windows", "wry", ] [[package]] name = "tauri-utils" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107a959dbd5ff53d89a98f6f2e3e987c611334141a43630caae1d80e79446dd6" +checksum = "82dcced4014e59af9790cc22f5d271df3be09ecd6728ec68861642553c8d01b7" dependencies = [ + "anyhow", "brotli 7.0.0", "cargo_metadata", "ctor", @@ -6610,22 +6555,23 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533fc2d4105e0e3d96ce1c71f2d308c9fbbe2ef9c587cab63dd627ab5bde218f" +checksum = "d433764348e7084bad2c5ea22c96c71b61b17afe3a11645710f533bd72b6a2b5" dependencies = [ - "core-graphics 0.24.0", "crossbeam-channel", - "dirs 5.0.1", + "dirs", "libappindicator", "muda", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", + "objc2 0.6.0", + "objc2-app-kit 0.3.0", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.0", "once_cell", "png", "serde", - "thiserror 1.0.63", + "thiserror 2.0.11", "windows-sys 0.59.0", ] @@ -7198,16 +7144,16 @@ dependencies = [ [[package]] name = "webview2-com" -version = "0.34.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823e7ebcfaea51e78f72c87fc3b65a1e602c321f407a0b36dbb327d7bb7cd921" +checksum = "b0d606f600e5272b514dbb66539dd068211cc20155be8d3958201b4b5bd79ed3" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.58.0", - "windows-core 0.58.0", - "windows-implement 0.58.0", - "windows-interface 0.58.0", + "windows", + "windows-core 0.60.1", + "windows-implement", + "windows-interface", ] [[package]] @@ -7223,13 +7169,13 @@ dependencies = [ [[package]] name = "webview2-com-sys" -version = "0.34.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a82bce72db6e5ee83c68b5de1e2cd6ea195b9fbff91cb37df5884cbe3222df4" +checksum = "bfb27fccd3c27f68e9a6af1bcf48c2d82534b8675b83608a4d81446d095a17ac" dependencies = [ - "thiserror 1.0.63", - "windows 0.58.0", - "windows-core 0.58.0", + "thiserror 2.0.11", + "windows", + "windows-core 0.60.1", ] [[package]] @@ -7281,27 +7227,19 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window-vibrancy" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33082acd404763b315866e14a0d5193f3422c81086657583937a750cdd3ec340" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" dependencies = [ - "cocoa 0.25.0", - "objc", + "objc2 0.6.0", + "objc2-app-kit 0.3.0", + "objc2-core-foundation", + "objc2-foundation 0.3.0", "raw-window-handle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", "windows-version", ] -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.60.0" @@ -7333,27 +7271,14 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" dependencies = [ - "windows-implement 0.59.0", - "windows-interface 0.59.1", + "windows-implement", + "windows-interface", "windows-link", "windows-result 0.3.2", "windows-strings 0.3.1", @@ -7369,17 +7294,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "windows-implement" version = "0.59.0" @@ -7391,17 +7305,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "windows-interface" version = "0.59.1" @@ -7759,12 +7662,12 @@ dependencies = [ [[package]] name = "wry" -version = "0.48.0" +version = "0.50.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e644bf458e27b11b0ecafc9e5633d1304fdae82baca1d42185669752fe6ca4f" +checksum = "b19b78efae8b853c6c817e8752fc1dbf9cab8a8ffe9c30f399bd750ccf0f0730" dependencies = [ "base64 0.22.1", - "block2 0.5.1", + "block2 0.6.0", "cookie", "crossbeam-channel", "dpi", @@ -7778,9 +7681,10 @@ dependencies = [ "kuchikiki", "libc", "ndk", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", + "objc2 0.6.0", + "objc2-app-kit 0.3.0", + "objc2-core-foundation", + "objc2-foundation 0.3.0", "objc2-ui-kit", "objc2-web-kit", "once_cell", @@ -7794,8 +7698,8 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows 0.58.0", - "windows-core 0.58.0", + "windows", + "windows-core 0.60.1", "windows-version", "x11-dl", ] @@ -7873,7 +7777,7 @@ name = "yaak-app" version = "0.0.0" dependencies = [ "chrono", - "cocoa 0.26.0", + "cocoa", "encoding_rs", "eventsource-client", "hex_color", @@ -8006,7 +7910,9 @@ dependencies = [ "serde_json", "sqlx", "tauri", + "tauri-plugin", "thiserror 2.0.11", + "tokio", "ts-rs", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index bfac4cb0..7ebea8c7 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -31,7 +31,7 @@ strip = true # Automatically strip symbols from the binary. cargo-clippy = [] [build-dependencies] -tauri-build = { version = "2.0.6", features = [] } +tauri-build = { version = "2.1.0", features = [] } [target.'cfg(target_os = "macos")'.dependencies] objc = "0.2.7" @@ -87,8 +87,8 @@ yaak-ws = { path = "yaak-ws" } reqwest = "0.12.12" serde = "1.0.215" serde_json = "1.0.132" -tauri = "2.2.5" -tauri-plugin = "2.0.4" +tauri = "2.4.0" +tauri-plugin = "2.1.0" tauri-plugin-shell = "2.2.0" thiserror = "2.0.3" ts-rs = "10.0.0" diff --git a/src-tauri/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json index 26c087fa..3895fc2b 100644 --- a/src-tauri/gen/schemas/acl-manifests.json +++ b/src-tauri/gen/schemas/acl-manifests.json @@ -1 +1 @@ -{"clipboard-manager":{"default_permission":{"identifier":"default","description":"No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n","permissions":[]},"permissions":{"allow-clear":{"identifier":"allow-clear","description":"Enables the clear command without any pre-configured scope.","commands":{"allow":["clear"],"deny":[]}},"allow-read-image":{"identifier":"allow-read-image","description":"Enables the read_image command without any pre-configured scope.","commands":{"allow":["read_image"],"deny":[]}},"allow-read-text":{"identifier":"allow-read-text","description":"Enables the read_text command without any pre-configured scope.","commands":{"allow":["read_text"],"deny":[]}},"allow-write-html":{"identifier":"allow-write-html","description":"Enables the write_html command without any pre-configured scope.","commands":{"allow":["write_html"],"deny":[]}},"allow-write-image":{"identifier":"allow-write-image","description":"Enables the write_image command without any pre-configured scope.","commands":{"allow":["write_image"],"deny":[]}},"allow-write-text":{"identifier":"allow-write-text","description":"Enables the write_text command without any pre-configured scope.","commands":{"allow":["write_text"],"deny":[]}},"deny-clear":{"identifier":"deny-clear","description":"Denies the clear command without any pre-configured scope.","commands":{"allow":[],"deny":["clear"]}},"deny-read-image":{"identifier":"deny-read-image","description":"Denies the read_image command without any pre-configured scope.","commands":{"allow":[],"deny":["read_image"]}},"deny-read-text":{"identifier":"deny-read-text","description":"Denies the read_text command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text"]}},"deny-write-html":{"identifier":"deny-write-html","description":"Denies the write_html command without any pre-configured scope.","commands":{"allow":[],"deny":["write_html"]}},"deny-write-image":{"identifier":"deny-write-image","description":"Denies the write_image command without any pre-configured scope.","commands":{"allow":[],"deny":["write_image"]}},"deny-write-text":{"identifier":"deny-write-text","description":"Denies the write_text command without any pre-configured scope.","commands":{"allow":[],"deny":["write_text"]}}},"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"}},"log":{"default_permission":{"identifier":"default","description":"Allows the log command","permissions":["allow-log"]},"permissions":{"allow-log":{"identifier":"allow-log","description":"Enables the log command without any pre-configured scope.","commands":{"allow":["log"],"deny":[]}},"deny-log":{"identifier":"deny-log","description":"Denies the log command without any pre-configured scope.","commands":{"allow":[],"deny":["log"]}}},"permission_sets":{},"global_scope_schema":null},"opener":{"default_permission":{"identifier":"default","description":"This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer","permissions":["allow-open-url","allow-reveal-item-in-dir","allow-default-urls"]},"permissions":{"allow-default-urls":{"identifier":"allow-default-urls","description":"This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"url":"mailto:*"},{"url":"tel:*"},{"url":"http://*"},{"url":"https://*"}]}},"allow-open-path":{"identifier":"allow-open-path","description":"Enables the open_path command without any pre-configured scope.","commands":{"allow":["open_path"],"deny":[]}},"allow-open-url":{"identifier":"allow-open-url","description":"Enables the open_url command without any pre-configured scope.","commands":{"allow":["open_url"],"deny":[]}},"allow-reveal-item-in-dir":{"identifier":"allow-reveal-item-in-dir","description":"Enables the reveal_item_in_dir command without any pre-configured scope.","commands":{"allow":["reveal_item_in_dir"],"deny":[]}},"deny-open-path":{"identifier":"deny-open-path","description":"Denies the open_path command without any pre-configured scope.","commands":{"allow":[],"deny":["open_path"]}},"deny-open-url":{"identifier":"deny-open-url","description":"Denies the open_url command without any pre-configured scope.","commands":{"allow":[],"deny":["open_url"]}},"deny-reveal-item-in-dir":{"identifier":"deny-reveal-item-in-dir","description":"Denies the reveal_item_in_dir command without any pre-configured scope.","commands":{"allow":[],"deny":["reveal_item_in_dir"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"properties":{"app":{"allOf":[{"$ref":"#/definitions/Application"}],"description":"An application to open this url with, for example: firefox."},"url":{"description":"A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"","type":"string"}},"required":["url"],"type":"object"},{"properties":{"app":{"allOf":[{"$ref":"#/definitions/Application"}],"description":"An application to open this path with, for example: xdg-open."},"path":{"description":"A path that can be opened by the webview when using the Opener 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"}],"definitions":{"Application":{"anyOf":[{"description":"Open in default application.","type":"null"},{"description":"If true, allow open with any application.","type":"boolean"},{"description":"Allow specific application to open with.","type":"string"}],"description":"Opener scope application."}},"description":"Opener scope entry.","title":"OpenerScopeEntry"}},"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"}},"updater":{"default_permission":{"identifier":"default","description":"This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n","permissions":["allow-check","allow-download","allow-install","allow-download-and-install"]},"permissions":{"allow-check":{"identifier":"allow-check","description":"Enables the check command without any pre-configured scope.","commands":{"allow":["check"],"deny":[]}},"allow-download":{"identifier":"allow-download","description":"Enables the download command without any pre-configured scope.","commands":{"allow":["download"],"deny":[]}},"allow-download-and-install":{"identifier":"allow-download-and-install","description":"Enables the download_and_install command without any pre-configured scope.","commands":{"allow":["download_and_install"],"deny":[]}},"allow-install":{"identifier":"allow-install","description":"Enables the install command without any pre-configured scope.","commands":{"allow":["install"],"deny":[]}},"deny-check":{"identifier":"deny-check","description":"Denies the check command without any pre-configured scope.","commands":{"allow":[],"deny":["check"]}},"deny-download":{"identifier":"deny-download","description":"Denies the download command without any pre-configured scope.","commands":{"allow":[],"deny":["download"]}},"deny-download-and-install":{"identifier":"deny-download-and-install","description":"Denies the download_and_install command without any pre-configured scope.","commands":{"allow":[],"deny":["download_and_install"]}},"deny-install":{"identifier":"deny-install","description":"Denies the install command without any pre-configured scope.","commands":{"allow":[],"deny":["install"]}}},"permission_sets":{},"global_scope_schema":null},"window-state":{"default_permission":{"identifier":"default","description":"This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n","permissions":["allow-filename","allow-restore-state","allow-save-window-state"]},"permissions":{"allow-filename":{"identifier":"allow-filename","description":"Enables the filename command without any pre-configured scope.","commands":{"allow":["filename"],"deny":[]}},"allow-restore-state":{"identifier":"allow-restore-state","description":"Enables the restore_state command without any pre-configured scope.","commands":{"allow":["restore_state"],"deny":[]}},"allow-save-window-state":{"identifier":"allow-save-window-state","description":"Enables the save_window_state command without any pre-configured scope.","commands":{"allow":["save_window_state"],"deny":[]}},"deny-filename":{"identifier":"deny-filename","description":"Denies the filename command without any pre-configured scope.","commands":{"allow":[],"deny":["filename"]}},"deny-restore-state":{"identifier":"deny-restore-state","description":"Denies the restore_state command without any pre-configured scope.","commands":{"allow":[],"deny":["restore_state"]}},"deny-save-window-state":{"identifier":"deny-save-window-state","description":"Denies the save_window_state command without any pre-configured scope.","commands":{"allow":[],"deny":["save_window_state"]}}},"permission_sets":{},"global_scope_schema":null},"yaak-git":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin","permissions":["allow-add","allow-branch","allow-checkout","allow-commit","allow-delete-branch","allow-fetch-all","allow-initialize","allow-log","allow-merge-branch","allow-pull","allow-push","allow-status","allow-unstage"]},"permissions":{"allow-add":{"identifier":"allow-add","description":"Enables the add command without any pre-configured scope.","commands":{"allow":["add"],"deny":[]}},"allow-branch":{"identifier":"allow-branch","description":"Enables the branch command without any pre-configured scope.","commands":{"allow":["branch"],"deny":[]}},"allow-checkout":{"identifier":"allow-checkout","description":"Enables the checkout command without any pre-configured scope.","commands":{"allow":["checkout"],"deny":[]}},"allow-checkout-remote":{"identifier":"allow-checkout-remote","description":"Enables the checkout_remote command without any pre-configured scope.","commands":{"allow":["checkout_remote"],"deny":[]}},"allow-commit":{"identifier":"allow-commit","description":"Enables the commit command without any pre-configured scope.","commands":{"allow":["commit"],"deny":[]}},"allow-delete-branch":{"identifier":"allow-delete-branch","description":"Enables the delete_branch command without any pre-configured scope.","commands":{"allow":["delete_branch"],"deny":[]}},"allow-fetch-all":{"identifier":"allow-fetch-all","description":"Enables the fetch_all command without any pre-configured scope.","commands":{"allow":["fetch_all"],"deny":[]}},"allow-initialize":{"identifier":"allow-initialize","description":"Enables the initialize command without any pre-configured scope.","commands":{"allow":["initialize"],"deny":[]}},"allow-log":{"identifier":"allow-log","description":"Enables the log command without any pre-configured scope.","commands":{"allow":["log"],"deny":[]}},"allow-merge-branch":{"identifier":"allow-merge-branch","description":"Enables the merge_branch command without any pre-configured scope.","commands":{"allow":["merge_branch"],"deny":[]}},"allow-pull":{"identifier":"allow-pull","description":"Enables the pull command without any pre-configured scope.","commands":{"allow":["pull"],"deny":[]}},"allow-push":{"identifier":"allow-push","description":"Enables the push command without any pre-configured scope.","commands":{"allow":["push"],"deny":[]}},"allow-status":{"identifier":"allow-status","description":"Enables the status command without any pre-configured scope.","commands":{"allow":["status"],"deny":[]}},"allow-unstage":{"identifier":"allow-unstage","description":"Enables the unstage command without any pre-configured scope.","commands":{"allow":["unstage"],"deny":[]}},"deny-add":{"identifier":"deny-add","description":"Denies the add command without any pre-configured scope.","commands":{"allow":[],"deny":["add"]}},"deny-branch":{"identifier":"deny-branch","description":"Denies the branch command without any pre-configured scope.","commands":{"allow":[],"deny":["branch"]}},"deny-checkout":{"identifier":"deny-checkout","description":"Denies the checkout command without any pre-configured scope.","commands":{"allow":[],"deny":["checkout"]}},"deny-checkout-remote":{"identifier":"deny-checkout-remote","description":"Denies the checkout_remote command without any pre-configured scope.","commands":{"allow":[],"deny":["checkout_remote"]}},"deny-commit":{"identifier":"deny-commit","description":"Denies the commit command without any pre-configured scope.","commands":{"allow":[],"deny":["commit"]}},"deny-delete-branch":{"identifier":"deny-delete-branch","description":"Denies the delete_branch command without any pre-configured scope.","commands":{"allow":[],"deny":["delete_branch"]}},"deny-fetch-all":{"identifier":"deny-fetch-all","description":"Denies the fetch_all command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_all"]}},"deny-initialize":{"identifier":"deny-initialize","description":"Denies the initialize command without any pre-configured scope.","commands":{"allow":[],"deny":["initialize"]}},"deny-log":{"identifier":"deny-log","description":"Denies the log command without any pre-configured scope.","commands":{"allow":[],"deny":["log"]}},"deny-merge-branch":{"identifier":"deny-merge-branch","description":"Denies the merge_branch command without any pre-configured scope.","commands":{"allow":[],"deny":["merge_branch"]}},"deny-pull":{"identifier":"deny-pull","description":"Denies the pull command without any pre-configured scope.","commands":{"allow":[],"deny":["pull"]}},"deny-push":{"identifier":"deny-push","description":"Denies the push command without any pre-configured scope.","commands":{"allow":[],"deny":["push"]}},"deny-status":{"identifier":"deny-status","description":"Denies the status command without any pre-configured scope.","commands":{"allow":[],"deny":["status"]}},"deny-unstage":{"identifier":"deny-unstage","description":"Denies the unstage command without any pre-configured scope.","commands":{"allow":[],"deny":["unstage"]}}},"permission_sets":{},"global_scope_schema":null},"yaak-license":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin","permissions":["allow-check","allow-activate","allow-deactivate"]},"permissions":{"allow-activate":{"identifier":"allow-activate","description":"Enables the activate command without any pre-configured scope.","commands":{"allow":["activate"],"deny":[]}},"allow-check":{"identifier":"allow-check","description":"Enables the check command without any pre-configured scope.","commands":{"allow":["check"],"deny":[]}},"allow-deactivate":{"identifier":"allow-deactivate","description":"Enables the deactivate command without any pre-configured scope.","commands":{"allow":["deactivate"],"deny":[]}},"deny-activate":{"identifier":"deny-activate","description":"Denies the activate command without any pre-configured scope.","commands":{"allow":[],"deny":["activate"]}},"deny-check":{"identifier":"deny-check","description":"Denies the check command without any pre-configured scope.","commands":{"allow":[],"deny":["check"]}},"deny-deactivate":{"identifier":"deny-deactivate","description":"Denies the deactivate command without any pre-configured scope.","commands":{"allow":[],"deny":["deactivate"]}}},"permission_sets":{},"global_scope_schema":null},"yaak-sync":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin","permissions":["allow-calculate","allow-calculate-fs","allow-apply","allow-watch"]},"permissions":{"allow-apply":{"identifier":"allow-apply","description":"Enables the apply command without any pre-configured scope.","commands":{"allow":["apply"],"deny":[]}},"allow-calculate":{"identifier":"allow-calculate","description":"Enables the calculate command without any pre-configured scope.","commands":{"allow":["calculate"],"deny":[]}},"allow-calculate-fs":{"identifier":"allow-calculate-fs","description":"Enables the calculate_fs command without any pre-configured scope.","commands":{"allow":["calculate_fs"],"deny":[]}},"allow-watch":{"identifier":"allow-watch","description":"Enables the watch command without any pre-configured scope.","commands":{"allow":["watch"],"deny":[]}},"deny-apply":{"identifier":"deny-apply","description":"Denies the apply command without any pre-configured scope.","commands":{"allow":[],"deny":["apply"]}},"deny-calculate":{"identifier":"deny-calculate","description":"Denies the calculate command without any pre-configured scope.","commands":{"allow":[],"deny":["calculate"]}},"deny-calculate-fs":{"identifier":"deny-calculate-fs","description":"Denies the calculate_fs command without any pre-configured scope.","commands":{"allow":[],"deny":["calculate_fs"]}},"deny-watch":{"identifier":"deny-watch","description":"Denies the watch command without any pre-configured scope.","commands":{"allow":[],"deny":["watch"]}}},"permission_sets":{},"global_scope_schema":null},"yaak-ws":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin","permissions":["allow-close","allow-connect","allow-delete-connection","allow-delete-connections","allow-delete-request","allow-duplicate-request","allow-list-connections","allow-list-events","allow-list-requests","allow-send","allow-upsert-request"]},"permissions":{"allow-cancel":{"identifier":"allow-cancel","description":"Enables the cancel command without any pre-configured scope.","commands":{"allow":["cancel"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-connect":{"identifier":"allow-connect","description":"Enables the connect command without any pre-configured scope.","commands":{"allow":["connect"],"deny":[]}},"allow-delete-connection":{"identifier":"allow-delete-connection","description":"Enables the delete_connection command without any pre-configured scope.","commands":{"allow":["delete_connection"],"deny":[]}},"allow-delete-connections":{"identifier":"allow-delete-connections","description":"Enables the delete_connections command without any pre-configured scope.","commands":{"allow":["delete_connections"],"deny":[]}},"allow-delete-request":{"identifier":"allow-delete-request","description":"Enables the delete_request command without any pre-configured scope.","commands":{"allow":["delete_request"],"deny":[]}},"allow-duplicate-request":{"identifier":"allow-duplicate-request","description":"Enables the duplicate_request command without any pre-configured scope.","commands":{"allow":["duplicate_request"],"deny":[]}},"allow-list-connections":{"identifier":"allow-list-connections","description":"Enables the list_connections command without any pre-configured scope.","commands":{"allow":["list_connections"],"deny":[]}},"allow-list-events":{"identifier":"allow-list-events","description":"Enables the list_events command without any pre-configured scope.","commands":{"allow":["list_events"],"deny":[]}},"allow-list-requests":{"identifier":"allow-list-requests","description":"Enables the list_requests command without any pre-configured scope.","commands":{"allow":["list_requests"],"deny":[]}},"allow-list-websocket-connections":{"identifier":"allow-list-websocket-connections","description":"Enables the list_websocket_connections command without any pre-configured scope.","commands":{"allow":["list_websocket_connections"],"deny":[]}},"allow-list-websocket-requests":{"identifier":"allow-list-websocket-requests","description":"Enables the list_websocket_requests command without any pre-configured scope.","commands":{"allow":["list_websocket_requests"],"deny":[]}},"allow-send":{"identifier":"allow-send","description":"Enables the send command without any pre-configured scope.","commands":{"allow":["send"],"deny":[]}},"allow-upsert-request":{"identifier":"allow-upsert-request","description":"Enables the upsert_request command without any pre-configured scope.","commands":{"allow":["upsert_request"],"deny":[]}},"allow-upsert-websocket-request":{"identifier":"allow-upsert-websocket-request","description":"Enables the upsert_websocket_request command without any pre-configured scope.","commands":{"allow":["upsert_websocket_request"],"deny":[]}},"deny-cancel":{"identifier":"deny-cancel","description":"Denies the cancel command without any pre-configured scope.","commands":{"allow":[],"deny":["cancel"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-connect":{"identifier":"deny-connect","description":"Denies the connect command without any pre-configured scope.","commands":{"allow":[],"deny":["connect"]}},"deny-delete-connection":{"identifier":"deny-delete-connection","description":"Denies the delete_connection command without any pre-configured scope.","commands":{"allow":[],"deny":["delete_connection"]}},"deny-delete-connections":{"identifier":"deny-delete-connections","description":"Denies the delete_connections command without any pre-configured scope.","commands":{"allow":[],"deny":["delete_connections"]}},"deny-delete-request":{"identifier":"deny-delete-request","description":"Denies the delete_request command without any pre-configured scope.","commands":{"allow":[],"deny":["delete_request"]}},"deny-duplicate-request":{"identifier":"deny-duplicate-request","description":"Denies the duplicate_request command without any pre-configured scope.","commands":{"allow":[],"deny":["duplicate_request"]}},"deny-list-connections":{"identifier":"deny-list-connections","description":"Denies the list_connections command without any pre-configured scope.","commands":{"allow":[],"deny":["list_connections"]}},"deny-list-events":{"identifier":"deny-list-events","description":"Denies the list_events command without any pre-configured scope.","commands":{"allow":[],"deny":["list_events"]}},"deny-list-requests":{"identifier":"deny-list-requests","description":"Denies the list_requests command without any pre-configured scope.","commands":{"allow":[],"deny":["list_requests"]}},"deny-list-websocket-connections":{"identifier":"deny-list-websocket-connections","description":"Denies the list_websocket_connections command without any pre-configured scope.","commands":{"allow":[],"deny":["list_websocket_connections"]}},"deny-list-websocket-requests":{"identifier":"deny-list-websocket-requests","description":"Denies the list_websocket_requests command without any pre-configured scope.","commands":{"allow":[],"deny":["list_websocket_requests"]}},"deny-send":{"identifier":"deny-send","description":"Denies the send command without any pre-configured scope.","commands":{"allow":[],"deny":["send"]}},"deny-upsert-request":{"identifier":"deny-upsert-request","description":"Denies the upsert_request command without any pre-configured scope.","commands":{"allow":[],"deny":["upsert_request"]}},"deny-upsert-websocket-request":{"identifier":"deny-upsert-websocket-request","description":"Denies the upsert_websocket_request command without any pre-configured scope.","commands":{"allow":[],"deny":["upsert_websocket_request"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file +{"clipboard-manager":{"default_permission":{"identifier":"default","description":"No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n","permissions":[]},"permissions":{"allow-clear":{"identifier":"allow-clear","description":"Enables the clear command without any pre-configured scope.","commands":{"allow":["clear"],"deny":[]}},"allow-read-image":{"identifier":"allow-read-image","description":"Enables the read_image command without any pre-configured scope.","commands":{"allow":["read_image"],"deny":[]}},"allow-read-text":{"identifier":"allow-read-text","description":"Enables the read_text command without any pre-configured scope.","commands":{"allow":["read_text"],"deny":[]}},"allow-write-html":{"identifier":"allow-write-html","description":"Enables the write_html command without any pre-configured scope.","commands":{"allow":["write_html"],"deny":[]}},"allow-write-image":{"identifier":"allow-write-image","description":"Enables the write_image command without any pre-configured scope.","commands":{"allow":["write_image"],"deny":[]}},"allow-write-text":{"identifier":"allow-write-text","description":"Enables the write_text command without any pre-configured scope.","commands":{"allow":["write_text"],"deny":[]}},"deny-clear":{"identifier":"deny-clear","description":"Denies the clear command without any pre-configured scope.","commands":{"allow":[],"deny":["clear"]}},"deny-read-image":{"identifier":"deny-read-image","description":"Denies the read_image command without any pre-configured scope.","commands":{"allow":[],"deny":["read_image"]}},"deny-read-text":{"identifier":"deny-read-text","description":"Denies the read_text command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text"]}},"deny-write-html":{"identifier":"deny-write-html","description":"Denies the write_html command without any pre-configured scope.","commands":{"allow":[],"deny":["write_html"]}},"deny-write-image":{"identifier":"deny-write-image","description":"Denies the write_image command without any pre-configured scope.","commands":{"allow":[],"deny":["write_image"]}},"deny-write-text":{"identifier":"deny-write-text","description":"Denies the write_text command without any pre-configured scope.","commands":{"allow":[],"deny":["write_text"]}}},"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","allow-identifier"]},"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-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"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-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"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-is-always-on-top","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-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"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-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"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"}},"log":{"default_permission":{"identifier":"default","description":"Allows the log command","permissions":["allow-log"]},"permissions":{"allow-log":{"identifier":"allow-log","description":"Enables the log command without any pre-configured scope.","commands":{"allow":["log"],"deny":[]}},"deny-log":{"identifier":"deny-log","description":"Denies the log command without any pre-configured scope.","commands":{"allow":[],"deny":["log"]}}},"permission_sets":{},"global_scope_schema":null},"opener":{"default_permission":{"identifier":"default","description":"This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer","permissions":["allow-open-url","allow-reveal-item-in-dir","allow-default-urls"]},"permissions":{"allow-default-urls":{"identifier":"allow-default-urls","description":"This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"url":"mailto:*"},{"url":"tel:*"},{"url":"http://*"},{"url":"https://*"}]}},"allow-open-path":{"identifier":"allow-open-path","description":"Enables the open_path command without any pre-configured scope.","commands":{"allow":["open_path"],"deny":[]}},"allow-open-url":{"identifier":"allow-open-url","description":"Enables the open_url command without any pre-configured scope.","commands":{"allow":["open_url"],"deny":[]}},"allow-reveal-item-in-dir":{"identifier":"allow-reveal-item-in-dir","description":"Enables the reveal_item_in_dir command without any pre-configured scope.","commands":{"allow":["reveal_item_in_dir"],"deny":[]}},"deny-open-path":{"identifier":"deny-open-path","description":"Denies the open_path command without any pre-configured scope.","commands":{"allow":[],"deny":["open_path"]}},"deny-open-url":{"identifier":"deny-open-url","description":"Denies the open_url command without any pre-configured scope.","commands":{"allow":[],"deny":["open_url"]}},"deny-reveal-item-in-dir":{"identifier":"deny-reveal-item-in-dir","description":"Denies the reveal_item_in_dir command without any pre-configured scope.","commands":{"allow":[],"deny":["reveal_item_in_dir"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"properties":{"app":{"allOf":[{"$ref":"#/definitions/Application"}],"description":"An application to open this url with, for example: firefox."},"url":{"description":"A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"","type":"string"}},"required":["url"],"type":"object"},{"properties":{"app":{"allOf":[{"$ref":"#/definitions/Application"}],"description":"An application to open this path with, for example: xdg-open."},"path":{"description":"A path that can be opened by the webview when using the Opener 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"}],"definitions":{"Application":{"anyOf":[{"description":"Open in default application.","type":"null"},{"description":"If true, allow open with any application.","type":"boolean"},{"description":"Allow specific application to open with.","type":"string"}],"description":"Opener scope application."}},"description":"Opener scope entry.","title":"OpenerScopeEntry"}},"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"}},"updater":{"default_permission":{"identifier":"default","description":"This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n","permissions":["allow-check","allow-download","allow-install","allow-download-and-install"]},"permissions":{"allow-check":{"identifier":"allow-check","description":"Enables the check command without any pre-configured scope.","commands":{"allow":["check"],"deny":[]}},"allow-download":{"identifier":"allow-download","description":"Enables the download command without any pre-configured scope.","commands":{"allow":["download"],"deny":[]}},"allow-download-and-install":{"identifier":"allow-download-and-install","description":"Enables the download_and_install command without any pre-configured scope.","commands":{"allow":["download_and_install"],"deny":[]}},"allow-install":{"identifier":"allow-install","description":"Enables the install command without any pre-configured scope.","commands":{"allow":["install"],"deny":[]}},"deny-check":{"identifier":"deny-check","description":"Denies the check command without any pre-configured scope.","commands":{"allow":[],"deny":["check"]}},"deny-download":{"identifier":"deny-download","description":"Denies the download command without any pre-configured scope.","commands":{"allow":[],"deny":["download"]}},"deny-download-and-install":{"identifier":"deny-download-and-install","description":"Denies the download_and_install command without any pre-configured scope.","commands":{"allow":[],"deny":["download_and_install"]}},"deny-install":{"identifier":"deny-install","description":"Denies the install command without any pre-configured scope.","commands":{"allow":[],"deny":["install"]}}},"permission_sets":{},"global_scope_schema":null},"window-state":{"default_permission":{"identifier":"default","description":"This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n","permissions":["allow-filename","allow-restore-state","allow-save-window-state"]},"permissions":{"allow-filename":{"identifier":"allow-filename","description":"Enables the filename command without any pre-configured scope.","commands":{"allow":["filename"],"deny":[]}},"allow-restore-state":{"identifier":"allow-restore-state","description":"Enables the restore_state command without any pre-configured scope.","commands":{"allow":["restore_state"],"deny":[]}},"allow-save-window-state":{"identifier":"allow-save-window-state","description":"Enables the save_window_state command without any pre-configured scope.","commands":{"allow":["save_window_state"],"deny":[]}},"deny-filename":{"identifier":"deny-filename","description":"Denies the filename command without any pre-configured scope.","commands":{"allow":[],"deny":["filename"]}},"deny-restore-state":{"identifier":"deny-restore-state","description":"Denies the restore_state command without any pre-configured scope.","commands":{"allow":[],"deny":["restore_state"]}},"deny-save-window-state":{"identifier":"deny-save-window-state","description":"Denies the save_window_state command without any pre-configured scope.","commands":{"allow":[],"deny":["save_window_state"]}}},"permission_sets":{},"global_scope_schema":null},"yaak-git":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin","permissions":["allow-add","allow-branch","allow-checkout","allow-commit","allow-delete-branch","allow-fetch-all","allow-initialize","allow-log","allow-merge-branch","allow-pull","allow-push","allow-status","allow-unstage"]},"permissions":{"allow-add":{"identifier":"allow-add","description":"Enables the add command without any pre-configured scope.","commands":{"allow":["add"],"deny":[]}},"allow-branch":{"identifier":"allow-branch","description":"Enables the branch command without any pre-configured scope.","commands":{"allow":["branch"],"deny":[]}},"allow-checkout":{"identifier":"allow-checkout","description":"Enables the checkout command without any pre-configured scope.","commands":{"allow":["checkout"],"deny":[]}},"allow-checkout-remote":{"identifier":"allow-checkout-remote","description":"Enables the checkout_remote command without any pre-configured scope.","commands":{"allow":["checkout_remote"],"deny":[]}},"allow-commit":{"identifier":"allow-commit","description":"Enables the commit command without any pre-configured scope.","commands":{"allow":["commit"],"deny":[]}},"allow-delete-branch":{"identifier":"allow-delete-branch","description":"Enables the delete_branch command without any pre-configured scope.","commands":{"allow":["delete_branch"],"deny":[]}},"allow-fetch-all":{"identifier":"allow-fetch-all","description":"Enables the fetch_all command without any pre-configured scope.","commands":{"allow":["fetch_all"],"deny":[]}},"allow-initialize":{"identifier":"allow-initialize","description":"Enables the initialize command without any pre-configured scope.","commands":{"allow":["initialize"],"deny":[]}},"allow-log":{"identifier":"allow-log","description":"Enables the log command without any pre-configured scope.","commands":{"allow":["log"],"deny":[]}},"allow-merge-branch":{"identifier":"allow-merge-branch","description":"Enables the merge_branch command without any pre-configured scope.","commands":{"allow":["merge_branch"],"deny":[]}},"allow-pull":{"identifier":"allow-pull","description":"Enables the pull command without any pre-configured scope.","commands":{"allow":["pull"],"deny":[]}},"allow-push":{"identifier":"allow-push","description":"Enables the push command without any pre-configured scope.","commands":{"allow":["push"],"deny":[]}},"allow-status":{"identifier":"allow-status","description":"Enables the status command without any pre-configured scope.","commands":{"allow":["status"],"deny":[]}},"allow-unstage":{"identifier":"allow-unstage","description":"Enables the unstage command without any pre-configured scope.","commands":{"allow":["unstage"],"deny":[]}},"deny-add":{"identifier":"deny-add","description":"Denies the add command without any pre-configured scope.","commands":{"allow":[],"deny":["add"]}},"deny-branch":{"identifier":"deny-branch","description":"Denies the branch command without any pre-configured scope.","commands":{"allow":[],"deny":["branch"]}},"deny-checkout":{"identifier":"deny-checkout","description":"Denies the checkout command without any pre-configured scope.","commands":{"allow":[],"deny":["checkout"]}},"deny-checkout-remote":{"identifier":"deny-checkout-remote","description":"Denies the checkout_remote command without any pre-configured scope.","commands":{"allow":[],"deny":["checkout_remote"]}},"deny-commit":{"identifier":"deny-commit","description":"Denies the commit command without any pre-configured scope.","commands":{"allow":[],"deny":["commit"]}},"deny-delete-branch":{"identifier":"deny-delete-branch","description":"Denies the delete_branch command without any pre-configured scope.","commands":{"allow":[],"deny":["delete_branch"]}},"deny-fetch-all":{"identifier":"deny-fetch-all","description":"Denies the fetch_all command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_all"]}},"deny-initialize":{"identifier":"deny-initialize","description":"Denies the initialize command without any pre-configured scope.","commands":{"allow":[],"deny":["initialize"]}},"deny-log":{"identifier":"deny-log","description":"Denies the log command without any pre-configured scope.","commands":{"allow":[],"deny":["log"]}},"deny-merge-branch":{"identifier":"deny-merge-branch","description":"Denies the merge_branch command without any pre-configured scope.","commands":{"allow":[],"deny":["merge_branch"]}},"deny-pull":{"identifier":"deny-pull","description":"Denies the pull command without any pre-configured scope.","commands":{"allow":[],"deny":["pull"]}},"deny-push":{"identifier":"deny-push","description":"Denies the push command without any pre-configured scope.","commands":{"allow":[],"deny":["push"]}},"deny-status":{"identifier":"deny-status","description":"Denies the status command without any pre-configured scope.","commands":{"allow":[],"deny":["status"]}},"deny-unstage":{"identifier":"deny-unstage","description":"Denies the unstage command without any pre-configured scope.","commands":{"allow":[],"deny":["unstage"]}}},"permission_sets":{},"global_scope_schema":null},"yaak-license":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin","permissions":["allow-check","allow-activate","allow-deactivate"]},"permissions":{"allow-activate":{"identifier":"allow-activate","description":"Enables the activate command without any pre-configured scope.","commands":{"allow":["activate"],"deny":[]}},"allow-check":{"identifier":"allow-check","description":"Enables the check command without any pre-configured scope.","commands":{"allow":["check"],"deny":[]}},"allow-deactivate":{"identifier":"allow-deactivate","description":"Enables the deactivate command without any pre-configured scope.","commands":{"allow":["deactivate"],"deny":[]}},"deny-activate":{"identifier":"deny-activate","description":"Denies the activate command without any pre-configured scope.","commands":{"allow":[],"deny":["activate"]}},"deny-check":{"identifier":"deny-check","description":"Denies the check command without any pre-configured scope.","commands":{"allow":[],"deny":["check"]}},"deny-deactivate":{"identifier":"deny-deactivate","description":"Denies the deactivate command without any pre-configured scope.","commands":{"allow":[],"deny":["deactivate"]}}},"permission_sets":{},"global_scope_schema":null},"yaak-models":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin","permissions":["allow-upsert","allow-delete"]},"permissions":{"allow-delete":{"identifier":"allow-delete","description":"Enables the delete command without any pre-configured scope.","commands":{"allow":["delete"],"deny":[]}},"allow-delete-model":{"identifier":"allow-delete-model","description":"Enables the delete_model command without any pre-configured scope.","commands":{"allow":["delete_model"],"deny":[]}},"allow-upsert":{"identifier":"allow-upsert","description":"Enables the upsert command without any pre-configured scope.","commands":{"allow":["upsert"],"deny":[]}},"allow-upsert-model":{"identifier":"allow-upsert-model","description":"Enables the upsert_model command without any pre-configured scope.","commands":{"allow":["upsert_model"],"deny":[]}},"deny-delete":{"identifier":"deny-delete","description":"Denies the delete command without any pre-configured scope.","commands":{"allow":[],"deny":["delete"]}},"deny-delete-model":{"identifier":"deny-delete-model","description":"Denies the delete_model command without any pre-configured scope.","commands":{"allow":[],"deny":["delete_model"]}},"deny-upsert":{"identifier":"deny-upsert","description":"Denies the upsert command without any pre-configured scope.","commands":{"allow":[],"deny":["upsert"]}},"deny-upsert-model":{"identifier":"deny-upsert-model","description":"Denies the upsert_model command without any pre-configured scope.","commands":{"allow":[],"deny":["upsert_model"]}}},"permission_sets":{},"global_scope_schema":null},"yaak-sync":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin","permissions":["allow-calculate","allow-calculate-fs","allow-apply","allow-watch"]},"permissions":{"allow-apply":{"identifier":"allow-apply","description":"Enables the apply command without any pre-configured scope.","commands":{"allow":["apply"],"deny":[]}},"allow-calculate":{"identifier":"allow-calculate","description":"Enables the calculate command without any pre-configured scope.","commands":{"allow":["calculate"],"deny":[]}},"allow-calculate-fs":{"identifier":"allow-calculate-fs","description":"Enables the calculate_fs command without any pre-configured scope.","commands":{"allow":["calculate_fs"],"deny":[]}},"allow-watch":{"identifier":"allow-watch","description":"Enables the watch command without any pre-configured scope.","commands":{"allow":["watch"],"deny":[]}},"deny-apply":{"identifier":"deny-apply","description":"Denies the apply command without any pre-configured scope.","commands":{"allow":[],"deny":["apply"]}},"deny-calculate":{"identifier":"deny-calculate","description":"Denies the calculate command without any pre-configured scope.","commands":{"allow":[],"deny":["calculate"]}},"deny-calculate-fs":{"identifier":"deny-calculate-fs","description":"Denies the calculate_fs command without any pre-configured scope.","commands":{"allow":[],"deny":["calculate_fs"]}},"deny-watch":{"identifier":"deny-watch","description":"Denies the watch command without any pre-configured scope.","commands":{"allow":[],"deny":["watch"]}}},"permission_sets":{},"global_scope_schema":null},"yaak-ws":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin","permissions":["allow-close","allow-connect","allow-delete-connection","allow-delete-connections","allow-delete-request","allow-duplicate-request","allow-list-connections","allow-list-events","allow-list-requests","allow-send","allow-upsert-request"]},"permissions":{"allow-cancel":{"identifier":"allow-cancel","description":"Enables the cancel command without any pre-configured scope.","commands":{"allow":["cancel"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-connect":{"identifier":"allow-connect","description":"Enables the connect command without any pre-configured scope.","commands":{"allow":["connect"],"deny":[]}},"allow-delete-connection":{"identifier":"allow-delete-connection","description":"Enables the delete_connection command without any pre-configured scope.","commands":{"allow":["delete_connection"],"deny":[]}},"allow-delete-connections":{"identifier":"allow-delete-connections","description":"Enables the delete_connections command without any pre-configured scope.","commands":{"allow":["delete_connections"],"deny":[]}},"allow-delete-request":{"identifier":"allow-delete-request","description":"Enables the delete_request command without any pre-configured scope.","commands":{"allow":["delete_request"],"deny":[]}},"allow-duplicate-request":{"identifier":"allow-duplicate-request","description":"Enables the duplicate_request command without any pre-configured scope.","commands":{"allow":["duplicate_request"],"deny":[]}},"allow-list-connections":{"identifier":"allow-list-connections","description":"Enables the list_connections command without any pre-configured scope.","commands":{"allow":["list_connections"],"deny":[]}},"allow-list-events":{"identifier":"allow-list-events","description":"Enables the list_events command without any pre-configured scope.","commands":{"allow":["list_events"],"deny":[]}},"allow-list-requests":{"identifier":"allow-list-requests","description":"Enables the list_requests command without any pre-configured scope.","commands":{"allow":["list_requests"],"deny":[]}},"allow-list-websocket-connections":{"identifier":"allow-list-websocket-connections","description":"Enables the list_websocket_connections command without any pre-configured scope.","commands":{"allow":["list_websocket_connections"],"deny":[]}},"allow-list-websocket-requests":{"identifier":"allow-list-websocket-requests","description":"Enables the list_websocket_requests command without any pre-configured scope.","commands":{"allow":["list_websocket_requests"],"deny":[]}},"allow-send":{"identifier":"allow-send","description":"Enables the send command without any pre-configured scope.","commands":{"allow":["send"],"deny":[]}},"allow-upsert-request":{"identifier":"allow-upsert-request","description":"Enables the upsert_request command without any pre-configured scope.","commands":{"allow":["upsert_request"],"deny":[]}},"allow-upsert-websocket-request":{"identifier":"allow-upsert-websocket-request","description":"Enables the upsert_websocket_request command without any pre-configured scope.","commands":{"allow":["upsert_websocket_request"],"deny":[]}},"deny-cancel":{"identifier":"deny-cancel","description":"Denies the cancel command without any pre-configured scope.","commands":{"allow":[],"deny":["cancel"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-connect":{"identifier":"deny-connect","description":"Denies the connect command without any pre-configured scope.","commands":{"allow":[],"deny":["connect"]}},"deny-delete-connection":{"identifier":"deny-delete-connection","description":"Denies the delete_connection command without any pre-configured scope.","commands":{"allow":[],"deny":["delete_connection"]}},"deny-delete-connections":{"identifier":"deny-delete-connections","description":"Denies the delete_connections command without any pre-configured scope.","commands":{"allow":[],"deny":["delete_connections"]}},"deny-delete-request":{"identifier":"deny-delete-request","description":"Denies the delete_request command without any pre-configured scope.","commands":{"allow":[],"deny":["delete_request"]}},"deny-duplicate-request":{"identifier":"deny-duplicate-request","description":"Denies the duplicate_request command without any pre-configured scope.","commands":{"allow":[],"deny":["duplicate_request"]}},"deny-list-connections":{"identifier":"deny-list-connections","description":"Denies the list_connections command without any pre-configured scope.","commands":{"allow":[],"deny":["list_connections"]}},"deny-list-events":{"identifier":"deny-list-events","description":"Denies the list_events command without any pre-configured scope.","commands":{"allow":[],"deny":["list_events"]}},"deny-list-requests":{"identifier":"deny-list-requests","description":"Denies the list_requests command without any pre-configured scope.","commands":{"allow":[],"deny":["list_requests"]}},"deny-list-websocket-connections":{"identifier":"deny-list-websocket-connections","description":"Denies the list_websocket_connections command without any pre-configured scope.","commands":{"allow":[],"deny":["list_websocket_connections"]}},"deny-list-websocket-requests":{"identifier":"deny-list-websocket-requests","description":"Denies the list_websocket_requests command without any pre-configured scope.","commands":{"allow":[],"deny":["list_websocket_requests"]}},"deny-send":{"identifier":"deny-send","description":"Denies the send command without any pre-configured scope.","commands":{"allow":[],"deny":["send"]}},"deny-upsert-request":{"identifier":"deny-upsert-request","description":"Denies the upsert_request command without any pre-configured scope.","commands":{"allow":[],"deny":["upsert_request"]}},"deny-upsert-websocket-request":{"identifier":"deny-upsert-websocket-request","description":"Denies the upsert_websocket_request command without any pre-configured scope.","commands":{"allow":[],"deny":["upsert_websocket_request"]}}},"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 index 66760a06..1af4d71d 100644 --- a/src-tauri/gen/schemas/desktop-schema.json +++ b/src-tauri/gen/schemas/desktop-schema.json @@ -37,7 +37,7 @@ ], "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\"] } ```", + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its 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", @@ -70,14 +70,14 @@ "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\"]`", + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] 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\"]`", + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", "type": "array", "items": { "type": "string" @@ -2137,11 +2137,26 @@ "type": "string", "const": "core:app:allow-default-window-icon" }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers" + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier" + }, { "description": "Enables the name command without any pre-configured scope.", "type": "string", "const": "core:app:allow-name" }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store" + }, { "description": "Enables the set_app_theme command without any pre-configured scope.", "type": "string", @@ -2172,11 +2187,26 @@ "type": "string", "const": "core:app:deny-default-window-icon" }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers" + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier" + }, { "description": "Denies the name command without any pre-configured scope.", "type": "string", "const": "core:app:deny-name" }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store" + }, { "description": "Denies the set_app_theme command without any pre-configured scope.", "type": "string", @@ -2972,6 +3002,11 @@ "type": "string", "const": "core:window:allow-internal-toggle-maximize" }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top" + }, { "description": "Enables the is_closable command without any pre-configured scope.", "type": "string", @@ -3337,6 +3372,11 @@ "type": "string", "const": "core:window:deny-internal-toggle-maximize" }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top" + }, { "description": "Denies the is_closable command without any pre-configured scope.", "type": "string", @@ -5592,6 +5632,51 @@ "type": "string", "const": "yaak-license:deny-deactivate" }, + { + "description": "Default permissions for the plugin", + "type": "string", + "const": "yaak-models:default" + }, + { + "description": "Enables the delete command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:allow-delete" + }, + { + "description": "Enables the delete_model command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:allow-delete-model" + }, + { + "description": "Enables the upsert command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:allow-upsert" + }, + { + "description": "Enables the upsert_model command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:allow-upsert-model" + }, + { + "description": "Denies the delete command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:deny-delete" + }, + { + "description": "Denies the delete_model command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:deny-delete-model" + }, + { + "description": "Denies the upsert command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:deny-upsert" + }, + { + "description": "Denies the upsert_model command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:deny-upsert-model" + }, { "description": "Default permissions for the plugin", "type": "string", diff --git a/src-tauri/gen/schemas/macOS-schema.json b/src-tauri/gen/schemas/macOS-schema.json index 66760a06..1af4d71d 100644 --- a/src-tauri/gen/schemas/macOS-schema.json +++ b/src-tauri/gen/schemas/macOS-schema.json @@ -37,7 +37,7 @@ ], "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\"] } ```", + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its 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", @@ -70,14 +70,14 @@ "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\"]`", + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] 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\"]`", + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", "type": "array", "items": { "type": "string" @@ -2137,11 +2137,26 @@ "type": "string", "const": "core:app:allow-default-window-icon" }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers" + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier" + }, { "description": "Enables the name command without any pre-configured scope.", "type": "string", "const": "core:app:allow-name" }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store" + }, { "description": "Enables the set_app_theme command without any pre-configured scope.", "type": "string", @@ -2172,11 +2187,26 @@ "type": "string", "const": "core:app:deny-default-window-icon" }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers" + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier" + }, { "description": "Denies the name command without any pre-configured scope.", "type": "string", "const": "core:app:deny-name" }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store" + }, { "description": "Denies the set_app_theme command without any pre-configured scope.", "type": "string", @@ -2972,6 +3002,11 @@ "type": "string", "const": "core:window:allow-internal-toggle-maximize" }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top" + }, { "description": "Enables the is_closable command without any pre-configured scope.", "type": "string", @@ -3337,6 +3372,11 @@ "type": "string", "const": "core:window:deny-internal-toggle-maximize" }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top" + }, { "description": "Denies the is_closable command without any pre-configured scope.", "type": "string", @@ -5592,6 +5632,51 @@ "type": "string", "const": "yaak-license:deny-deactivate" }, + { + "description": "Default permissions for the plugin", + "type": "string", + "const": "yaak-models:default" + }, + { + "description": "Enables the delete command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:allow-delete" + }, + { + "description": "Enables the delete_model command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:allow-delete-model" + }, + { + "description": "Enables the upsert command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:allow-upsert" + }, + { + "description": "Enables the upsert_model command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:allow-upsert-model" + }, + { + "description": "Denies the delete command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:deny-delete" + }, + { + "description": "Denies the delete_model command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:deny-delete-model" + }, + { + "description": "Denies the upsert command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:deny-upsert" + }, + { + "description": "Denies the upsert_model command without any pre-configured scope.", + "type": "string", + "const": "yaak-models:deny-upsert-model" + }, { "description": "Default permissions for the plugin", "type": "string", diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs index 4c775897..6bd5ddaa 100644 --- a/src-tauri/src/error.rs +++ b/src-tauri/src/error.rs @@ -3,27 +3,30 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum Error { - #[error("Render error: {0}")] + #[error(transparent)] TemplateError(#[from] yaak_templates::error::Error), - #[error("Model error: {0}")] + #[error(transparent)] ModelError(#[from] yaak_models::error::Error), - #[error("Sync error: {0}")] + #[error(transparent)] SyncError(#[from] yaak_sync::error::Error), - #[error("Git error: {0}")] + #[error(transparent)] GitError(#[from] yaak_git::error::Error), - #[error("Websocket error: {0}")] + #[error(transparent)] WebsocketError(#[from] yaak_ws::error::Error), - #[error("License error: {0}")] + #[error(transparent)] LicenseError(#[from] yaak_license::error::Error), - #[error("Plugin error: {0}")] + #[error(transparent)] PluginError(#[from] yaak_plugins::error::Error), + #[error("Updater error: {0}")] + UpdaterError(#[from] tauri_plugin_updater::Error), + #[error("Request error: {0}")] RequestError(#[from] reqwest::Error), diff --git a/src-tauri/src/history.rs b/src-tauri/src/history.rs index 7bc5ff35..1e92f1e1 100644 --- a/src-tauri/src/history.rs +++ b/src-tauri/src/history.rs @@ -1,8 +1,6 @@ use tauri::{AppHandle, Runtime}; - -use yaak_models::queries::{ - get_key_value_int, get_key_value_string, set_key_value_int, set_key_value_string, UpdateSource, -}; +use yaak_models::manager::QueryManagerExt; +use yaak_models::queries_legacy::UpdateSource; const NAMESPACE: &str = "analytics"; const NUM_LAUNCHES_KEY: &str = "num_launches"; @@ -21,34 +19,28 @@ pub async fn store_launch_history(app_handle: &AppHandle) -> Laun let mut info = LaunchEventInfo::default(); info.num_launches = get_num_launches(app_handle).await + 1; - info.previous_version = - get_key_value_string(app_handle, NAMESPACE, last_tracked_version_key, "").await; info.current_version = app_handle.package_info().version.to_string(); - if info.previous_version.is_empty() { - } else { - info.launched_after_update = info.current_version != info.previous_version; - }; + app_handle + .queries() + .with_tx(|tx| { + info.previous_version = + tx.get_key_value_string(NAMESPACE, last_tracked_version_key, ""); - // Update key values + if !info.previous_version.is_empty() { + info.launched_after_update = info.current_version != info.previous_version; + }; - set_key_value_string( - app_handle, - NAMESPACE, - last_tracked_version_key, - info.current_version.as_str(), - &UpdateSource::Background, - ) - .await; + // Update key values - set_key_value_int( - app_handle, - NAMESPACE, - NUM_LAUNCHES_KEY, - info.num_launches, - &UpdateSource::Background, - ) - .await; + let source = &UpdateSource::Background; + let version = info.current_version.as_str(); + tx.set_key_value_string(NAMESPACE, last_tracked_version_key, version, source); + tx.set_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches, source); + Ok(()) + }) + .await + .unwrap(); info } @@ -66,5 +58,5 @@ pub fn get_os() -> &'static str { } pub async fn get_num_launches(app_handle: &AppHandle) -> i32 { - get_key_value_int(app_handle, NAMESPACE, NUM_LAUNCHES_KEY, 0).await + app_handle.queries().connect().await.unwrap().get_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, 0) } diff --git a/src-tauri/src/http_request.rs b/src-tauri/src/http_request.rs index ec2f0902..ad339689 100644 --- a/src-tauri/src/http_request.rs +++ b/src-tauri/src/http_request.rs @@ -24,14 +24,12 @@ use tokio::fs::{create_dir_all, File}; use tokio::io::AsyncWriteExt; use tokio::sync::watch::Receiver; use tokio::sync::{oneshot, Mutex}; +use yaak_models::manager::QueryManagerExt; use yaak_models::models::{ Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader, HttpResponseState, ProxySetting, ProxySettingAuth, }; -use yaak_models::queries::{ - get_base_environment, get_http_response, get_or_create_settings, get_workspace, - update_response_if_id, upsert_cookie_jar, UpdateSource, -}; +use yaak_models::queries_legacy::UpdateSource; use yaak_plugins::events::{ CallHttpAuthenticationRequest, HttpHeader, RenderPurpose, WindowContext, }; @@ -48,10 +46,18 @@ pub async fn send_http_request( ) -> Result { let app_handle = window.app_handle().clone(); let plugin_manager = app_handle.state::(); - let workspace = get_workspace(&app_handle, &unrendered_request.workspace_id).await?; - let base_environment = - get_base_environment(&app_handle, &unrendered_request.workspace_id).await?; - let settings = get_or_create_settings(&app_handle).await; + let update_source = &UpdateSource::from_window(&window); + let (settings, workspace) = { + let db = window.queries().connect().await?; + let settings = db.get_or_create_settings(update_source)?; + let workspace = db.get_workspace(&unrendered_request.workspace_id)?; + (settings, workspace) + }; + let base_environment = app_handle + .queries() + .connect() + .await? + .get_base_environment(&unrendered_request.workspace_id)?; let response_id = og_response.id.clone(); let response = Arc::new(Mutex::new(og_response.clone())); @@ -534,8 +540,12 @@ pub async fn send_http_request( }; r.state = HttpResponseState::Connected; - update_response_if_id(&app_handle, &r, &update_source) + app_handle + .queries() + .connect() .await + .unwrap() + .update_http_response_if_id(&r, &update_source) .expect("Failed to update response after connected"); } @@ -563,8 +573,12 @@ pub async fn send_http_request( f.flush().await.expect("Failed to flush file"); written_bytes += bytes.len(); r.content_length = Some(written_bytes as i32); - update_response_if_id(&app_handle, &r, &update_source) + app_handle + .queries() + .connect() .await + .unwrap() + .update_http_response_if_id(&r, &update_source) .expect("Failed to update response"); } Ok(None) => { @@ -591,8 +605,12 @@ pub async fn send_http_request( None => Some(written_bytes as i32), }; r.state = HttpResponseState::Closed; - update_response_if_id(&app_handle, &r, &UpdateSource::from_window(&window)) + app_handle + .queries() + .connect() .await + .unwrap() + .update_http_response_if_id(&r, &UpdateSource::from_window(&window)) .expect("Failed to update response"); }; @@ -617,12 +635,12 @@ pub async fn send_http_request( }) .collect::>(); cookie_jar.cookies = json_cookies; - if let Err(e) = upsert_cookie_jar( - &app_handle, - &cookie_jar, - &UpdateSource::from_window(&window), - ) - .await + if let Err(e) = app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_cookie_jar(&cookie_jar, &UpdateSource::from_window(&window)) { error!("Failed to update cookie jar: {}", e); }; @@ -649,10 +667,16 @@ pub async fn send_http_request( Ok(tokio::select! { Ok(r) = done_rx => r, _ = cancelled_rx.changed() => { - match get_http_response(&app_handle, response_id.as_str()).await { + match app_handle.queries().with_conn(|c| c.get_http_response(&response_id)).await { Ok(mut r) => { r.state = HttpResponseState::Closed; - update_response_if_id(&app_handle, &r, &UpdateSource::from_window(window)).await.expect("Failed to update response") + app_handle + .queries() + .connect() + .await + .unwrap() + .update_http_response_if_id(&r, &UpdateSource::from_window(window)) + .expect("Failed to update response") }, _ => { response_err(&app_handle, &*response.lock().await, "Ephemeral request was cancelled".to_string(), &update_source).await diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e647c784..bc483252 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -30,30 +30,14 @@ use tokio::sync::Mutex; use tokio::task::block_in_place; use yaak_grpc::manager::{DynamicMessage, GrpcHandle}; use yaak_grpc::{deserialize_message, serialize_message, Code, ServiceDefinition}; +use yaak_models::manager::QueryManagerExt; use yaak_models::models::{ CookieJar, Environment, EnvironmentVariable, Folder, GrpcConnection, GrpcConnectionState, GrpcEvent, GrpcEventType, GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, KeyValue, ModelType, Plugin, Settings, WebsocketRequest, Workspace, WorkspaceMeta, }; -use yaak_models::queries::{ - batch_upsert, cancel_pending_grpc_connections, cancel_pending_http_responses, - cancel_pending_websocket_connections, create_default_http_response, - delete_all_grpc_connections, delete_all_grpc_connections_for_workspace, - delete_all_http_responses_for_request, delete_all_http_responses_for_workspace, - delete_all_websocket_connections_for_workspace, delete_cookie_jar, delete_environment, - delete_folder, delete_grpc_connection, delete_grpc_request, delete_http_request, - delete_http_response, delete_plugin, delete_workspace, duplicate_folder, - duplicate_grpc_request, duplicate_http_request, ensure_base_environment, generate_model_id, - get_base_environment, get_cookie_jar, get_environment, get_folder, get_grpc_connection, - get_grpc_request, get_http_request, get_http_response, get_key_value_raw, - get_or_create_settings, get_or_create_workspace_meta, get_plugin, get_workspace, - get_workspace_export_resources, list_cookie_jars, list_environments, list_folders, - list_grpc_connections_for_workspace, list_grpc_events, list_grpc_requests, list_http_requests, - list_http_responses_for_workspace, list_key_values_raw, list_plugins, list_workspaces, - set_key_value_raw, update_response_if_id, update_settings, upsert_cookie_jar, - upsert_environment, upsert_folder, upsert_grpc_connection, upsert_grpc_event, - upsert_grpc_request, upsert_http_request, upsert_plugin, upsert_workspace, - upsert_workspace_meta, BatchUpsertResult, UpdateSource, +use yaak_models::queries_legacy::{ + generate_model_id, get_workspace_export_resources, BatchUpsertResult, UpdateSource, }; use yaak_plugins::events::{ BootResponse, CallHttpAuthenticationRequest, CallHttpRequestActionRequest, FilterResponse, @@ -124,10 +108,11 @@ async fn cmd_render_template( environment_id: Option<&str>, ) -> YaakResult { let environment = match environment_id { - Some(id) => get_environment(&app_handle, id).await.ok(), + Some(id) => app_handle.queries().connect().await?.get_environment(id).ok(), None => None, }; - let base_environment = get_base_environment(&app_handle, &workspace_id).await?; + let base_environment = + app_handle.queries().connect().await?.get_base_environment(&workspace_id)?; let result = render_template( template, &base_environment, @@ -157,15 +142,17 @@ async fn cmd_grpc_reflect( proto_files: Vec, app_handle: AppHandle, grpc_handle: State<'_, Mutex>, -) -> Result, String> { - let req = get_grpc_request(&app_handle, request_id) - .await - .map_err(|e| e.to_string())? - .ok_or("Failed to find GRPC request")?; +) -> YaakResult> { + let req = app_handle + .queries() + .connect() + .await? + .get_grpc_request(request_id)? + .ok_or(GenericError("Failed to find GRPC request".to_string()))?; let uri = safe_uri(&req.url); - grpc_handle + Ok(grpc_handle .lock() .await .services( @@ -174,6 +161,7 @@ async fn cmd_grpc_reflect( &proto_files.iter().map(|p| PathBuf::from_str(p).unwrap()).collect(), ) .await + .map_err(|e| GenericError(e.to_string()))?) } #[tauri::command] @@ -187,14 +175,20 @@ async fn cmd_grpc_go( grpc_handle: State<'_, Mutex>, ) -> YaakResult { let environment = match environment_id { - Some(id) => get_environment(&app_handle, id).await.ok(), + Some(id) => app_handle.queries().connect().await?.get_environment(id).ok(), None => None, }; - let unrendered_request = get_grpc_request(&app_handle, request_id) + let unrendered_request = app_handle + .queries() + .connect() .await? + .get_grpc_request(request_id)? .ok_or(GenericError("Failed to get GRPC request".to_string()))?; - let base_environment = - get_base_environment(&app_handle, &unrendered_request.workspace_id).await?; + let base_environment = app_handle + .queries() + .connect() + .await? + .get_base_environment(&unrendered_request.workspace_id)?; let request = render_grpc_request( &unrendered_request, &base_environment, @@ -243,8 +237,7 @@ async fn cmd_grpc_go( } } - let conn = upsert_grpc_connection( - &app_handle, + let conn = app_handle.queries().connect().await?.upsert_grpc_connection( &GrpcConnection { workspace_id: request.workspace_id.clone(), request_id: request.id.clone(), @@ -255,8 +248,7 @@ async fn cmd_grpc_go( ..Default::default() }, &UpdateSource::from_window(&window), - ) - .await?; + )?; let conn_id = conn.id.clone(); @@ -297,8 +289,7 @@ async fn cmd_grpc_go( let connection = match connection { Ok(c) => c, Err(err) => { - upsert_grpc_connection( - &app_handle, + app_handle.queries().connect().await?.upsert_grpc_connection( &GrpcConnection { elapsed: start.elapsed().as_millis() as i32, error: Some(err.clone()), @@ -306,8 +297,7 @@ async fn cmd_grpc_go( ..conn.clone() }, &UpdateSource::from_window(&window), - ) - .await?; + )?; return Ok(conn_id); } }; @@ -373,34 +363,40 @@ async fn cmd_grpc_go( Ok(d_msg) => d_msg, Err(e) => { tauri::async_runtime::spawn(async move { - upsert_grpc_event( - &app_handle, - &GrpcEvent { - event_type: GrpcEventType::Error, - content: e.to_string(), - ..base_msg.clone() - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_grpc_event( + &GrpcEvent { + event_type: GrpcEventType::Error, + content: e.to_string(), + ..base_msg.clone() + }, + &UpdateSource::from_window(&window), + ) + .unwrap(); }); return; } }; in_msg_tx.try_send(d_msg).unwrap(); tauri::async_runtime::spawn(async move { - upsert_grpc_event( - &app_handle, - &GrpcEvent { - content: msg, - event_type: GrpcEventType::ClientMessage, - ..base_msg.clone() - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_grpc_event( + &GrpcEvent { + content: msg, + event_type: GrpcEventType::ClientMessage, + ..base_msg.clone() + }, + &UpdateSource::from_window(&window), + ) + .unwrap(); }); } Ok(IncomingMsg::Commit) => { @@ -435,8 +431,7 @@ async fn cmd_grpc_go( ) .await?; - upsert_grpc_event( - &app_handle, + app_handle.queries().connect().await?.upsert_grpc_event( &GrpcEvent { content: format!("Connecting to {}", req.url), event_type: GrpcEventType::ConnectionStart, @@ -444,8 +439,7 @@ async fn cmd_grpc_go( ..base_event.clone() }, &UpdateSource::from_window(&window), - ) - .await?; + )?; async move { let (maybe_stream, maybe_msg) = @@ -474,86 +468,101 @@ async fn cmd_grpc_go( }; if !method_desc.is_client_streaming() { - upsert_grpc_event( - &app_handle, - &GrpcEvent { - event_type: GrpcEventType::ClientMessage, - content: msg, - ..base_event.clone() - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_grpc_event( + &GrpcEvent { + event_type: GrpcEventType::ClientMessage, + content: msg, + ..base_event.clone() + }, + &UpdateSource::from_window(&window), + ) + .unwrap(); } match maybe_msg { Some(Ok(msg)) => { - upsert_grpc_event( - &app_handle, - &GrpcEvent { - metadata: metadata_to_map(msg.metadata().clone()), - content: if msg.metadata().len() == 0 { - "Received response" - } else { - "Received response with metadata" - } - .to_string(), - event_type: GrpcEventType::Info, - ..base_event.clone() - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); - upsert_grpc_event( - &app_handle, - &GrpcEvent { - content: serialize_message(&msg.into_inner()).unwrap(), - event_type: GrpcEventType::ServerMessage, - ..base_event.clone() - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); - upsert_grpc_event( - &app_handle, - &GrpcEvent { - content: "Connection complete".to_string(), - event_type: GrpcEventType::ConnectionEnd, - status: Some(Code::Ok as i32), - ..base_event.clone() - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_grpc_event( + &GrpcEvent { + metadata: metadata_to_map(msg.metadata().clone()), + content: if msg.metadata().len() == 0 { + "Received response" + } else { + "Received response with metadata" + } + .to_string(), + event_type: GrpcEventType::Info, + ..base_event.clone() + }, + &UpdateSource::from_window(&window), + ) + .unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_grpc_event( + &GrpcEvent { + content: serialize_message(&msg.into_inner()).unwrap(), + event_type: GrpcEventType::ServerMessage, + ..base_event.clone() + }, + &UpdateSource::from_window(&window), + ) + .unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_grpc_event( + &GrpcEvent { + content: "Connection complete".to_string(), + event_type: GrpcEventType::ConnectionEnd, + status: Some(Code::Ok as i32), + ..base_event.clone() + }, + &UpdateSource::from_window(&window), + ) + .unwrap(); } Some(Err(e)) => { - upsert_grpc_event( - &app_handle, - &(match e.status { - Some(s) => GrpcEvent { - error: Some(s.message().to_string()), - status: Some(s.code() as i32), - content: "Failed to connect".to_string(), - metadata: metadata_to_map(s.metadata().clone()), - event_type: GrpcEventType::ConnectionEnd, - ..base_event.clone() - }, - None => GrpcEvent { - error: Some(e.message), - status: Some(Code::Unknown as i32), - content: "Failed to connect".to_string(), - event_type: GrpcEventType::ConnectionEnd, - ..base_event.clone() - }, - }), - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_grpc_event( + &(match e.status { + Some(s) => GrpcEvent { + error: Some(s.message().to_string()), + status: Some(s.code() as i32), + content: "Failed to connect".to_string(), + metadata: metadata_to_map(s.metadata().clone()), + event_type: GrpcEventType::ConnectionEnd, + ..base_event.clone() + }, + None => GrpcEvent { + error: Some(e.message), + status: Some(Code::Unknown as i32), + content: "Failed to connect".to_string(), + event_type: GrpcEventType::ConnectionEnd, + ..base_event.clone() + }, + }), + &UpdateSource::from_window(&window), + ) + .unwrap(); } None => { // Server streaming doesn't return the initial message @@ -562,50 +571,56 @@ async fn cmd_grpc_go( let mut stream = match maybe_stream { Some(Ok(stream)) => { - upsert_grpc_event( - &app_handle, - &GrpcEvent { - metadata: metadata_to_map(stream.metadata().clone()), - content: if stream.metadata().len() == 0 { - "Received response" - } else { - "Received response with metadata" - } - .to_string(), - event_type: GrpcEventType::Info, - ..base_event.clone() - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_grpc_event( + &GrpcEvent { + metadata: metadata_to_map(stream.metadata().clone()), + content: if stream.metadata().len() == 0 { + "Received response" + } else { + "Received response with metadata" + } + .to_string(), + event_type: GrpcEventType::Info, + ..base_event.clone() + }, + &UpdateSource::from_window(&window), + ) + .unwrap(); stream.into_inner() } Some(Err(e)) => { warn!("GRPC stream error {e:?}"); - upsert_grpc_event( - &app_handle, - &(match e.status { - Some(s) => GrpcEvent { - error: Some(s.message().to_string()), - status: Some(s.code() as i32), - content: "Failed to connect".to_string(), - metadata: metadata_to_map(s.metadata().clone()), - event_type: GrpcEventType::ConnectionEnd, - ..base_event.clone() - }, - None => GrpcEvent { - error: Some(e.message), - status: Some(Code::Unknown as i32), - content: "Failed to connect".to_string(), - event_type: GrpcEventType::ConnectionEnd, - ..base_event.clone() - }, - }), - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_grpc_event( + &(match e.status { + Some(s) => GrpcEvent { + error: Some(s.message().to_string()), + status: Some(s.code() as i32), + content: "Failed to connect".to_string(), + metadata: metadata_to_map(s.metadata().clone()), + event_type: GrpcEventType::ConnectionEnd, + ..base_event.clone() + }, + None => GrpcEvent { + error: Some(e.message), + status: Some(Code::Unknown as i32), + content: "Failed to connect".to_string(), + event_type: GrpcEventType::ConnectionEnd, + ..base_event.clone() + }, + }), + &UpdateSource::from_window(&window), + ) + .unwrap(); return; } None => return, @@ -615,50 +630,59 @@ async fn cmd_grpc_go( match stream.message().await { Ok(Some(msg)) => { let message = serialize_message(&msg).unwrap(); - upsert_grpc_event( - &app_handle, - &GrpcEvent { - content: message, - event_type: GrpcEventType::ServerMessage, - ..base_event.clone() - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_grpc_event( + &GrpcEvent { + content: message, + event_type: GrpcEventType::ServerMessage, + ..base_event.clone() + }, + &UpdateSource::from_window(&window), + ) + .unwrap(); } Ok(None) => { let trailers = stream.trailers().await.unwrap_or_default().unwrap_or_default(); - upsert_grpc_event( - &app_handle, - &GrpcEvent { - content: "Connection complete".to_string(), - status: Some(Code::Ok as i32), - metadata: metadata_to_map(trailers), - event_type: GrpcEventType::ConnectionEnd, - ..base_event.clone() - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_grpc_event( + &GrpcEvent { + content: "Connection complete".to_string(), + status: Some(Code::Ok as i32), + metadata: metadata_to_map(trailers), + event_type: GrpcEventType::ConnectionEnd, + ..base_event.clone() + }, + &UpdateSource::from_window(&window), + ) + .unwrap(); break; } Err(status) => { - upsert_grpc_event( - &app_handle, - &GrpcEvent { - content: status.to_string(), - status: Some(status.code() as i32), - metadata: metadata_to_map(status.metadata().clone()), - event_type: GrpcEventType::ConnectionEnd, - ..base_event.clone() - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_grpc_event( + &GrpcEvent { + content: status.to_string(), + status: Some(status.code() as i32), + metadata: metadata_to_map(status.metadata().clone()), + event_type: GrpcEventType::ConnectionEnd, + ..base_event.clone() + }, + &UpdateSource::from_window(&window), + ) + .unwrap(); } } } @@ -671,27 +695,25 @@ async fn cmd_grpc_go( let w = app_handle.clone(); tokio::select! { _ = grpc_listen => { - let events = list_grpc_events(&w, &conn_id) - .await - .unwrap(); + let events = w.queries().connect().await.unwrap().list_grpc_events(&conn_id).unwrap(); let closed_event = events .iter() .find(|e| GrpcEventType::ConnectionEnd == e.event_type); let closed_status = closed_event.and_then(|e| e.status).unwrap_or(Code::Unavailable as i32); - upsert_grpc_connection( - &w, - &GrpcConnection{ - elapsed: start.elapsed().as_millis() as i32, - status: closed_status, - state: GrpcConnectionState::Closed, - ..get_grpc_connection(&w, &conn_id).await.unwrap().clone() - }, - &UpdateSource::from_window(&window), - ).await.unwrap(); + w.queries().with_conn(|c| { + c.upsert_grpc_connection( + &GrpcConnection{ + elapsed: start.elapsed().as_millis() as i32, + status: closed_status, + state: GrpcConnectionState::Closed, + ..c.get_grpc_connection( &conn_id).unwrap().clone() + }, + &UpdateSource::from_window(&window), + ) + }).await.unwrap(); }, _ = cancelled_rx.changed() => { - upsert_grpc_event( - &w, + w.queries().connect().await.unwrap().upsert_grpc_event( &GrpcEvent { content: "Cancelled".to_string(), event_type: GrpcEventType::ConnectionEnd, @@ -699,19 +721,18 @@ async fn cmd_grpc_go( ..base_msg.clone() }, &UpdateSource::from_window(&window), - ).await.unwrap(); - upsert_grpc_connection( - &w, - &GrpcConnection { + ).unwrap(); + w.queries().with_conn(|c| { + c.upsert_grpc_connection( + &GrpcConnection{ elapsed: start.elapsed().as_millis() as i32, status: Code::Cancelled as i32, state: GrpcConnectionState::Closed, - ..get_grpc_connection(&w, &conn_id).await.unwrap().clone() - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + ..c.get_grpc_connection( &conn_id).unwrap().clone() + }, + &UpdateSource::from_window(&window), + ) + }).await.unwrap(); }, } w.unlisten(event_handler); @@ -729,16 +750,14 @@ async fn cmd_send_ephemeral_request( window: WebviewWindow, app_handle: AppHandle, ) -> YaakResult { - let response = HttpResponse::new(); + let response = HttpResponse::default(); request.id = "".to_string(); let environment = match environment_id { - Some(id) => { - Some(get_environment(&app_handle, id).await.expect("Failed to get environment")) - } + Some(id) => Some(app_handle.queries().connect().await?.get_environment(id)?), None => None, }; let cookie_jar = match cookie_jar_id { - Some(id) => Some(get_cookie_jar(&app_handle, id).await.expect("Failed to get cookie jar")), + Some(id) => Some(app_handle.queries().connect().await?.get_cookie_jar(id)?), None => None, }; @@ -764,12 +783,11 @@ async fn cmd_filter_response( response_id: &str, plugin_manager: State<'_, PluginManager>, filter: &str, -) -> Result { - let response = - get_http_response(&app_handle, response_id).await.expect("Failed to get http response"); +) -> YaakResult { + let response = app_handle.queries().connect().await?.get_http_response(response_id)?; if let None = response.body_path { - return Err("Response body path not set".to_string()); + return Err(GenericError("Response body path not set".to_string())); } let mut content_type = "".to_string(); @@ -780,13 +798,12 @@ async fn cmd_filter_response( } } - let body = read_response_body(response).await.unwrap(); + let body = read_response_body(response) + .await + .ok_or(GenericError("Failed to find response body".to_string()))?; // TODO: Have plugins register their own content type (regex?) - plugin_manager - .filter_data(&window, filter, &body, &content_type) - .await - .map_err(|e| e.to_string()) + Ok(plugin_manager.filter_data(&window, filter, &body, &content_type).await?) } #[tauri::command] @@ -816,13 +833,12 @@ async fn cmd_import_data( app_handle: AppHandle, plugin_manager: State<'_, PluginManager>, file_path: &str, -) -> Result { +) -> YaakResult { let file = read_to_string(file_path) .await .unwrap_or_else(|_| panic!("Unable to read file {}", file_path)); let file_contents = file.as_str(); - let import_result = - plugin_manager.import_data(&window, file_contents).await.map_err(|e| e.to_string())?; + let import_result = plugin_manager.import_data(&window, file_contents).await?; let mut id_map: BTreeMap = BTreeMap::new(); @@ -924,18 +940,20 @@ async fn cmd_import_data( }) .collect(); - let upserted = batch_upsert( - &app_handle, - workspaces, - environments, - folders, - http_requests, - grpc_requests, - websocket_requests, - &UpdateSource::Import, - ) - .await - .map_err(|e| e.to_string())?; + let upserted = app_handle + .queries() + .with_tx(|tx| { + tx.batch_upsert( + workspaces, + environments, + folders, + http_requests, + grpc_requests, + websocket_requests, + &UpdateSource::Import, + ) + }) + .await?; Ok(upserted) } @@ -1058,17 +1076,12 @@ async fn cmd_save_response( app_handle: AppHandle, response_id: &str, filepath: &str, -) -> Result<(), String> { - let response = get_http_response(&app_handle, response_id).await.map_err(|e| e.to_string())?; +) -> YaakResult<()> { + let response = app_handle.queries().connect().await?.get_http_response(response_id)?; - let body_path = match response.body_path { - None => { - return Err("Response does not have a body".to_string()); - } - Some(p) => p, - }; - - fs::copy(body_path, filepath).map_err(|e| e.to_string())?; + let body_path = + response.body_path.ok_or(GenericError("Response does not have a body".to_string()))?; + fs::copy(body_path, filepath).map_err(|e| GenericError(e.to_string()))?; Ok(()) } @@ -1084,9 +1097,14 @@ async fn cmd_send_http_request( // that has not yet been saved in the DB. request: HttpRequest, ) -> YaakResult { - let response = - create_default_http_response(&app_handle, &request.id, &UpdateSource::from_window(&window)) - .await?; + let response = app_handle.queries().connect().await?.upsert_http_response( + &HttpResponse { + request_id: request.id.clone(), + workspace_id: request.workspace_id.clone(), + ..Default::default() + }, + &UpdateSource::from_window(&window), + )?; let (cancel_tx, mut cancel_rx) = tokio::sync::watch::channel(false); app_handle.listen_any(format!("cancel_http_response_{}", response.id), move |_event| { @@ -1096,7 +1114,7 @@ async fn cmd_send_http_request( }); let environment = match environment_id { - Some(id) => match get_environment(&app_handle, id).await { + Some(id) => match app_handle.queries().connect().await?.get_environment(id) { Ok(env) => Some(env), Err(e) => { warn!("Failed to find environment by id {id} {}", e); @@ -1107,7 +1125,7 @@ async fn cmd_send_http_request( }; let cookie_jar = match cookie_jar_id { - Some(id) => Some(get_cookie_jar(&app_handle, id).await.expect("Failed to get cookie jar")), + Some(id) => Some(app_handle.queries().connect().await?.get_cookie_jar(id)?), None => None, }; @@ -1124,8 +1142,12 @@ async fn response_err( let mut response = response.clone(); response.state = HttpResponseState::Closed; response.error = Some(error.clone()); - response = update_response_if_id(app_handle, &response, update_source) + response = app_handle + .queries() + .connect() .await + .unwrap() + .update_http_response_if_id(&response, update_source) .expect("Failed to update response"); response } @@ -1136,14 +1158,12 @@ async fn cmd_set_update_mode( app_handle: AppHandle, window: WebviewWindow, ) -> YaakResult { - let (key_value, _created) = set_key_value_raw( - &app_handle, + let (key_value, _created) = app_handle.queries().connect().await?.set_key_value_raw( "app", "update_mode", update_mode, &UpdateSource::from_window(&window), - ) - .await; + ); Ok(key_value) } @@ -1152,9 +1172,8 @@ async fn cmd_get_key_value( namespace: &str, key: &str, app_handle: AppHandle, -) -> Result, ()> { - let result = get_key_value_raw(&app_handle, namespace, key).await; - Ok(result) +) -> YaakResult> { + Ok(app_handle.queries().connect().await?.get_key_value_raw(namespace, key)) } #[tauri::command] @@ -1164,10 +1183,13 @@ async fn cmd_set_key_value( namespace: &str, key: &str, value: &str, -) -> Result { - let (key_value, _created) = - set_key_value_raw(&app_handle, namespace, key, value, &UpdateSource::from_window(&window)) - .await; +) -> YaakResult { + let (key_value, _created) = app_handle.queries().connect().await?.set_key_value_raw( + namespace, + key, + value, + &UpdateSource::from_window(&window), + ); Ok(key_value) } @@ -1178,25 +1200,19 @@ async fn cmd_install_plugin( plugin_manager: State<'_, PluginManager>, app_handle: AppHandle, window: WebviewWindow, -) -> Result { +) -> YaakResult { plugin_manager .add_plugin_by_dir(&WindowContext::from_window(&window), &directory, true) - .await - .map_err(|e| e.to_string())?; + .await?; - let plugin = upsert_plugin( - &app_handle, - Plugin { + Ok(app_handle.queries().connect().await?.upsert_plugin( + &Plugin { directory: directory.into(), url, ..Default::default() }, &UpdateSource::from_window(&window), - ) - .await - .map_err(|e| e.to_string())?; - - Ok(plugin) + )?) } #[tauri::command] @@ -1205,15 +1221,16 @@ async fn cmd_uninstall_plugin( plugin_manager: State<'_, PluginManager>, window: WebviewWindow, app_handle: AppHandle, -) -> Result { - let plugin = delete_plugin(&app_handle, plugin_id, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string())?; +) -> YaakResult { + let plugin = app_handle + .queries() + .connect() + .await? + .delete_plugin_by_id(plugin_id, &UpdateSource::from_window(&window))?; plugin_manager .uninstall(&WindowContext::from_window(&window), plugin.directory.as_str()) - .await - .map_err(|e| e.to_string())?; + .await?; Ok(plugin) } @@ -1223,10 +1240,12 @@ async fn cmd_update_cookie_jar( cookie_jar: CookieJar, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - upsert_cookie_jar(&app_handle, &cookie_jar, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .upsert_cookie_jar(&cookie_jar, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1234,10 +1253,12 @@ async fn cmd_delete_cookie_jar( app_handle: AppHandle, window: WebviewWindow, cookie_jar_id: &str, -) -> Result { - delete_cookie_jar(&app_handle, cookie_jar_id, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .delete_cookie_jar_by_id(cookie_jar_id, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1246,18 +1267,15 @@ async fn cmd_create_cookie_jar( name: &str, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - upsert_cookie_jar( - &app_handle, +) -> YaakResult { + Ok(app_handle.queries().connect().await?.upsert_cookie_jar( &CookieJar { name: name.to_string(), workspace_id: workspace_id.to_string(), ..Default::default() }, &UpdateSource::from_window(&window), - ) - .await - .map_err(|e| e.to_string()) + )?) } #[tauri::command] @@ -1268,10 +1286,9 @@ async fn cmd_create_environment( variables: Vec, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - upsert_environment( - &app_handle, - Environment { +) -> YaakResult { + Ok(app_handle.queries().connect().await?.upsert_environment( + &Environment { workspace_id: workspace_id.to_string(), environment_id: environment_id.map(|s| s.to_string()), name: name.to_string(), @@ -1279,9 +1296,7 @@ async fn cmd_create_environment( ..Default::default() }, &UpdateSource::from_window(&window), - ) - .await - .map_err(|e| e.to_string()) + )?) } #[tauri::command] @@ -1292,10 +1307,9 @@ async fn cmd_create_grpc_request( folder_id: Option<&str>, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - upsert_grpc_request( - &app_handle, - GrpcRequest { +) -> YaakResult { + Ok(app_handle.queries().connect().await?.upsert_grpc_request( + &GrpcRequest { workspace_id: workspace_id.to_string(), name: name.to_string(), folder_id: folder_id.map(|s| s.to_string()), @@ -1303,9 +1317,7 @@ async fn cmd_create_grpc_request( ..Default::default() }, &UpdateSource::from_window(&window), - ) - .await - .map_err(|e| e.to_string()) + )?) } #[tauri::command] @@ -1313,10 +1325,10 @@ async fn cmd_duplicate_grpc_request( id: &str, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - duplicate_grpc_request(&app_handle, id, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + let db = app_handle.queries().connect().await?; + let request = db.get_grpc_request(id)?.unwrap(); + Ok(db.duplicate_grpc_request(&request, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1324,22 +1336,10 @@ async fn cmd_duplicate_folder( app_handle: AppHandle, window: WebviewWindow, id: &str, -) -> YaakResult<()> { - let folder = get_folder(&app_handle, id).await?; - let new_folder = - duplicate_folder(&app_handle, &folder, &UpdateSource::from_window(&window)).await?; - Ok(new_folder) -} - -#[tauri::command] -async fn cmd_create_http_request( - request: HttpRequest, - app_handle: AppHandle, - window: WebviewWindow, -) -> Result { - upsert_http_request(&app_handle, request, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + let db = app_handle.queries().connect().await?; + let folder = db.get_folder(id)?; + Ok(db.duplicate_folder(&folder, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1347,10 +1347,10 @@ async fn cmd_duplicate_http_request( id: &str, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - duplicate_http_request(&app_handle, id, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + let db = app_handle.queries().connect().await?; + let request = db.get_http_request(id)?.unwrap(); + Ok(db.duplicate_http_request(&request, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1358,10 +1358,12 @@ async fn cmd_update_workspace( workspace: Workspace, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - upsert_workspace(&app_handle, workspace, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .upsert_workspace(&workspace, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1369,10 +1371,12 @@ async fn cmd_update_workspace_meta( workspace_meta: WorkspaceMeta, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - upsert_workspace_meta(&app_handle, workspace_meta, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .upsert_workspace_meta(&workspace_meta, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1380,10 +1384,12 @@ async fn cmd_update_environment( environment: Environment, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - upsert_environment(&app_handle, environment, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .upsert_environment(&environment, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1391,21 +1397,25 @@ async fn cmd_update_grpc_request( request: GrpcRequest, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - upsert_grpc_request(&app_handle, request, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .upsert_grpc_request(&request, &UpdateSource::from_window(&window))?) } #[tauri::command] -async fn cmd_update_http_request( +async fn cmd_upsert_http_request( request: HttpRequest, - app_handle: AppHandle, window: WebviewWindow, -) -> Result { - upsert_http_request(&app_handle, request, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) + app_handle: AppHandle, +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .upsert(&request, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1413,10 +1423,12 @@ async fn cmd_delete_grpc_request( app_handle: AppHandle, request_id: &str, window: WebviewWindow, -) -> Result { - delete_grpc_request(&app_handle, request_id, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .delete_grpc_request_by_id(request_id, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1424,18 +1436,20 @@ async fn cmd_delete_http_request( app_handle: AppHandle, request_id: &str, window: WebviewWindow, -) -> Result { - delete_http_request(&app_handle, request_id, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .delete_http_request_by_id(request_id, &UpdateSource::from_window(&window))?) } #[tauri::command] async fn cmd_list_folders( workspace_id: &str, app_handle: AppHandle, -) -> Result, String> { - list_folders(&app_handle, workspace_id).await.map_err(|e| e.to_string()) +) -> YaakResult> { + Ok(app_handle.queries().connect().await?.list_folders(workspace_id)?) } #[tauri::command] @@ -1444,7 +1458,11 @@ async fn cmd_update_folder( app_handle: AppHandle, window: WebviewWindow, ) -> YaakResult { - Ok(upsert_folder(&app_handle, folder, &UpdateSource::from_window(&window)).await?) + Ok(app_handle + .queries() + .connect() + .await? + .upsert_folder(&folder, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1452,10 +1470,12 @@ async fn cmd_delete_folder( app_handle: AppHandle, window: WebviewWindow, folder_id: &str, -) -> Result { - delete_folder(&app_handle, folder_id, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .delete_folder_by_id(folder_id, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1463,57 +1483,64 @@ async fn cmd_delete_environment( app_handle: AppHandle, window: WebviewWindow, environment_id: &str, -) -> Result { - delete_environment(&app_handle, environment_id, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .delete_environment_by_id(environment_id, &UpdateSource::from_window(&window))?) } #[tauri::command] async fn cmd_list_grpc_connections( workspace_id: &str, app_handle: AppHandle, -) -> Result, String> { - list_grpc_connections_for_workspace(&app_handle, workspace_id).await.map_err(|e| e.to_string()) +) -> YaakResult> { + Ok(app_handle + .queries() + .connect() + .await? + .list_grpc_connections_for_workspace(workspace_id, None)?) } #[tauri::command] async fn cmd_list_grpc_events( connection_id: &str, app_handle: AppHandle, -) -> Result, String> { - list_grpc_events(&app_handle, connection_id).await.map_err(|e| e.to_string()) +) -> YaakResult> { + Ok(app_handle.queries().connect().await?.list_grpc_events(connection_id)?) } #[tauri::command] async fn cmd_list_grpc_requests( workspace_id: &str, app_handle: AppHandle, -) -> Result, String> { - list_grpc_requests(&app_handle, workspace_id).await.map_err(|e| e.to_string()) +) -> YaakResult> { + Ok(app_handle.queries().connect().await?.list_grpc_requests(workspace_id)?) } #[tauri::command] async fn cmd_list_http_requests( workspace_id: &str, app_handle: AppHandle, -) -> Result, String> { - list_http_requests(&app_handle, workspace_id).await.map_err(|e| e.to_string()) +) -> YaakResult> { + Ok(app_handle.queries().connect().await?.list_http_requests(workspace_id)?) } #[tauri::command] async fn cmd_list_environments( workspace_id: &str, app_handle: AppHandle, -) -> Result, String> { +) -> YaakResult> { // Not sure of a better place to put this... - ensure_base_environment(&app_handle, workspace_id).await.map_err(|e| e.to_string())?; - list_environments(&app_handle, workspace_id).await.map_err(|e| e.to_string()) + let db = app_handle.queries().connect().await?; + db.ensure_base_environment(workspace_id)?; + Ok(db.list_environments(workspace_id)?) } #[tauri::command] -async fn cmd_list_plugins(app_handle: AppHandle) -> Result, String> { - list_plugins(&app_handle).await.map_err(|e| e.to_string()) +async fn cmd_list_plugins(app_handle: AppHandle) -> YaakResult> { + Ok(app_handle.queries().connect().await?.list_plugins()?) } #[tauri::command] @@ -1534,19 +1561,23 @@ async fn cmd_plugin_info( id: &str, app_handle: AppHandle, plugin_manager: State<'_, PluginManager>, -) -> Result { - let plugin = get_plugin(&app_handle, id).await.map_err(|e| e.to_string())?; +) -> YaakResult { + let plugin = app_handle.queries().connect().await?.get_plugin(id)?; Ok(plugin_manager .get_plugin_by_dir(plugin.directory.as_str()) .await - .ok_or("Failed to find plugin for info".to_string())? + .ok_or(GenericError("Failed to find plugin for info".to_string()))? .info() .await) } #[tauri::command] -async fn cmd_get_settings(app_handle: AppHandle) -> Result { - Ok(get_or_create_settings(&app_handle).await) +async fn cmd_get_settings(window: WebviewWindow) -> YaakResult { + Ok(window + .queries() + .connect() + .await? + .get_or_create_settings(&UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1554,39 +1585,41 @@ async fn cmd_update_settings( settings: Settings, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - update_settings(&app_handle, settings, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .upsert_settings(&settings, &UpdateSource::from_window(&window))?) } #[tauri::command] -async fn cmd_get_folder(id: &str, app_handle: AppHandle) -> Result { - get_folder(&app_handle, id).await.map_err(|e| e.to_string()) +async fn cmd_get_folder(id: &str, app_handle: AppHandle) -> YaakResult { + Ok(app_handle.queries().connect().await?.get_folder(id)?) } #[tauri::command] async fn cmd_get_grpc_request( id: &str, app_handle: AppHandle, -) -> Result, String> { - get_grpc_request(&app_handle, id).await.map_err(|e| e.to_string()) +) -> YaakResult> { + Ok(app_handle.queries().connect().await?.get_grpc_request(id)?) } #[tauri::command] async fn cmd_get_http_request( id: &str, app_handle: AppHandle, -) -> Result, String> { - get_http_request(&app_handle, id).await.map_err(|e| e.to_string()) +) -> YaakResult> { + Ok(app_handle.queries().connect().await?.get_http_request(id)?) } #[tauri::command] async fn cmd_get_cookie_jar( id: &str, app_handle: AppHandle, -) -> Result { - get_cookie_jar(&app_handle, id).await.map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle.queries().connect().await?.get_cookie_jar(id)?) } #[tauri::command] @@ -1594,22 +1627,19 @@ async fn cmd_list_cookie_jars( workspace_id: &str, app_handle: AppHandle, window: WebviewWindow, -) -> Result, String> { - let cookie_jars = - list_cookie_jars(&app_handle, workspace_id).await.expect("Failed to find cookie jars"); +) -> YaakResult> { + let db = app_handle.queries().connect().await?; + let cookie_jars = db.list_cookie_jars(workspace_id)?; if cookie_jars.is_empty() { - let cookie_jar = upsert_cookie_jar( - &app_handle, + let cookie_jar = db.upsert_cookie_jar( &CookieJar { name: "Default".to_string(), workspace_id: workspace_id.to_string(), ..Default::default() }, &UpdateSource::from_window(&window), - ) - .await - .expect("Failed to create CookieJar"); + )?; Ok(vec![cookie_jar]) } else { Ok(cookie_jars) @@ -1617,26 +1647,24 @@ async fn cmd_list_cookie_jars( } #[tauri::command] -async fn cmd_list_key_values( - app_handle: AppHandle, -) -> Result, String> { - list_key_values_raw(&app_handle).await.map_err(|e| e.to_string()) +async fn cmd_list_key_values(app_handle: AppHandle) -> YaakResult> { + Ok(app_handle.queries().connect().await?.list_key_values_raw()?) } #[tauri::command] async fn cmd_get_environment( id: &str, app_handle: AppHandle, -) -> Result { - get_environment(&app_handle, id).await.map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle.queries().connect().await?.get_environment(id)?) } #[tauri::command] async fn cmd_get_workspace( id: &str, app_handle: AppHandle, -) -> Result { - get_workspace(&app_handle, id).await.map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle.queries().connect().await?.get_workspace(id)?) } #[tauri::command] @@ -1644,10 +1672,12 @@ async fn cmd_list_http_responses( workspace_id: &str, limit: Option, app_handle: AppHandle, -) -> Result, String> { - list_http_responses_for_workspace(&app_handle, workspace_id, limit) - .await - .map_err(|e| e.to_string()) +) -> YaakResult> { + Ok(app_handle + .queries() + .connect() + .await? + .list_http_responses_for_workspace(workspace_id, limit.map(|l| l as u64))?) } #[tauri::command] @@ -1655,10 +1685,10 @@ async fn cmd_delete_http_response( id: &str, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - delete_http_response(&app_handle, id, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + let db = app_handle.queries().connect().await?; + let http_response = db.get_http_response(id)?; + Ok(db.delete_http_response(&http_response, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1666,10 +1696,12 @@ async fn cmd_delete_grpc_connection( id: &str, app_handle: AppHandle, window: WebviewWindow, -) -> Result { - delete_grpc_connection(&app_handle, id, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .delete_grpc_connection_by_id(id, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1677,10 +1709,12 @@ async fn cmd_delete_all_grpc_connections( request_id: &str, app_handle: AppHandle, window: WebviewWindow, -) -> Result<(), String> { - delete_all_grpc_connections(&app_handle, request_id, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult<()> { + Ok(app_handle + .queries() + .connect() + .await? + .delete_all_grpc_connections_for_request(request_id, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1688,29 +1722,17 @@ async fn cmd_delete_send_history( workspace_id: &str, app_handle: AppHandle, window: WebviewWindow, -) -> Result<(), String> { - delete_all_http_responses_for_workspace( - &app_handle, - workspace_id, - &UpdateSource::from_window(&window), - ) - .await - .map_err(|e| e.to_string())?; - delete_all_grpc_connections_for_workspace( - &app_handle, - workspace_id, - &UpdateSource::from_window(&window), - ) - .await - .map_err(|e| e.to_string())?; - delete_all_websocket_connections_for_workspace( - &app_handle, - workspace_id, - &UpdateSource::from_window(&window), - ) - .await - .map_err(|e| e.to_string())?; - Ok(()) +) -> YaakResult<()> { + Ok(app_handle + .queries() + .with_tx(|tx| { + let source = &UpdateSource::from_window(&window); + tx.delete_all_http_responses_for_workspace(workspace_id, source)?; + tx.delete_all_grpc_connections_for_workspace(workspace_id, source)?; + tx.delete_all_websocket_connections_for_workspace(workspace_id, source)?; + Ok(()) + }) + .await?) } #[tauri::command] @@ -1718,35 +1740,31 @@ async fn cmd_delete_all_http_responses( request_id: &str, app_handle: AppHandle, window: WebviewWindow, -) -> Result<(), String> { - delete_all_http_responses_for_request( - &app_handle, - request_id, - &UpdateSource::from_window(&window), - ) - .await - .map_err(|e| e.to_string()) +) -> YaakResult<()> { + Ok(app_handle + .queries() + .connect() + .await? + .delete_all_http_responses_for_request(request_id, &UpdateSource::from_window(&window))?) } #[tauri::command] async fn cmd_list_workspaces( app_handle: AppHandle, window: WebviewWindow, -) -> Result, String> { - let workspaces = list_workspaces(&app_handle).await.expect("Failed to find workspaces"); +) -> YaakResult> { + let queries = app_handle.queries().connect().await?; + let workspaces = queries.find_all::()?; if workspaces.is_empty() { - let workspace = upsert_workspace( - &app_handle, - Workspace { + let workspace = queries.upsert_workspace( + &Workspace { name: "Yaak".to_string(), setting_follow_redirects: true, setting_validate_certificates: true, ..Default::default() }, &UpdateSource::from_window(&window), - ) - .await - .expect("Failed to create Workspace"); + )?; Ok(vec![workspace]) } else { Ok(workspaces) @@ -1758,11 +1776,10 @@ async fn cmd_get_workspace_meta( app_handle: AppHandle, window: WebviewWindow, workspace_id: &str, -) -> Result { - let workspace = get_workspace(&app_handle, workspace_id).await.map_err(|e| e.to_string())?; - get_or_create_workspace_meta(&app_handle, &workspace, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + let db = app_handle.queries().connect().await?; + let workspace = db.get_workspace(workspace_id)?; + Ok(db.get_or_create_workspace_meta(&workspace, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -1788,24 +1805,21 @@ async fn cmd_delete_workspace( app_handle: AppHandle, window: WebviewWindow, workspace_id: &str, -) -> Result { - delete_workspace(&app_handle, workspace_id, &UpdateSource::from_window(&window)) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + Ok(app_handle + .queries() + .connect() + .await? + .delete_workspace_by_id(workspace_id, &UpdateSource::from_window(&window))?) } #[tauri::command] -async fn cmd_check_for_updates( - app_handle: AppHandle, +async fn cmd_check_for_updates( + window: WebviewWindow, yaak_updater: State<'_, Mutex>, -) -> Result { - let update_mode = get_update_mode(&app_handle).await; - yaak_updater - .lock() - .await - .check_now(&app_handle, update_mode, UpdateTrigger::User) - .await - .map_err(|e| e.to_string()) +) -> YaakResult { + let update_mode = get_update_mode(&window).await?; + Ok(yaak_updater.lock().await.check_now(&window, update_mode, UpdateTrigger::User).await?) } #[cfg_attr(mobile, tauri::mobile_entry_point)] @@ -1856,7 +1870,7 @@ pub fn run() { .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_fs::init()) .plugin(yaak_license::init()) - .plugin(yaak_models::plugin::Builder::default().build()) + .plugin(yaak_models::init()) .plugin(yaak_plugins::init()) .plugin(yaak_git::init()) .plugin(yaak_ws::init()) @@ -1895,7 +1909,6 @@ pub fn run() { cmd_create_cookie_jar, cmd_create_environment, cmd_create_grpc_request, - cmd_create_http_request, cmd_curl_to_request, cmd_delete_all_grpc_connections, cmd_delete_all_http_responses, @@ -1962,7 +1975,7 @@ pub fn run() { cmd_update_environment, cmd_update_folder, cmd_update_grpc_request, - cmd_update_http_request, + cmd_upsert_http_request, cmd_update_settings, cmd_update_workspace, cmd_update_workspace_meta, @@ -1983,21 +1996,24 @@ pub fn run() { // Cancel pending requests let h = app_handle.clone(); tauri::async_runtime::block_on(async move { - let _ = cancel_pending_http_responses(&h).await; - let _ = cancel_pending_grpc_connections(&h).await; - let _ = cancel_pending_websocket_connections(&h).await; + let db = h.queries().connect().await.unwrap(); + let _ = db.cancel_pending_http_responses(); + let _ = db.cancel_pending_grpc_connections(); + let _ = db.cancel_pending_websocket_connections(); }); } RunEvent::WindowEvent { event: WindowEvent::Focused(true), + label, .. } => { + let w = app_handle.get_webview_window(&label).unwrap(); let h = app_handle.clone(); - // Run update check whenever window is focused + // Run update check whenever the window is focused tauri::async_runtime::spawn(async move { let val: State<'_, Mutex> = h.state(); - let update_mode = get_update_mode(&h).await; - if let Err(e) = val.lock().await.maybe_check(&h, update_mode).await { + let update_mode = get_update_mode(&w).await.unwrap(); + if let Err(e) = val.lock().await.maybe_check(&w, update_mode).await { warn!("Failed to check for updates {e:?}"); }; }); @@ -2014,11 +2030,6 @@ pub fn run() { } }); } - _ => {} - }; - - // Save window state on exit - match event { RunEvent::WindowEvent { event: WindowEvent::CloseRequested { .. }, .. @@ -2034,9 +2045,13 @@ pub fn run() { }); } -async fn get_update_mode(h: &AppHandle) -> UpdateMode { - let settings = get_or_create_settings(h).await; - UpdateMode::new(settings.update_channel.as_str()) +async fn get_update_mode(window: &WebviewWindow) -> YaakResult { + let settings = window + .queries() + .connect() + .await? + .get_or_create_settings(&UpdateSource::from_window(window))?; + Ok(UpdateMode::new(settings.update_channel.as_str())) } fn safe_uri(endpoint: &str) -> String { @@ -2134,10 +2149,10 @@ fn workspace_id_from_window(window: &WebviewWindow) -> Option(window: &WebviewWindow) -> Option { +async fn workspace_from_window(window: &WebviewWindow) -> YaakResult { match workspace_id_from_window(&window) { - None => None, - Some(id) => get_workspace(window.app_handle(), id.as_str()).await.ok(), + None => Err(GenericError("Failed to get workspace ID from window".to_string())), + Some(id) => Ok(window.queries().connect().await?.get_workspace(id.as_str())?), } } @@ -2150,7 +2165,7 @@ fn environment_id_from_window(window: &WebviewWindow) -> Option(window: &WebviewWindow) -> Option { match environment_id_from_window(&window) { None => None, - Some(id) => get_environment(window.app_handle(), id.as_str()).await.ok(), + Some(id) => window.queries().connect().await.unwrap().get_environment(&id).ok(), } } @@ -2163,6 +2178,6 @@ fn cookie_jar_id_from_window(window: &WebviewWindow) -> Option(window: &WebviewWindow) -> Option { match cookie_jar_id_from_window(&window) { None => None, - Some(id) => get_cookie_jar(window.app_handle(), id.as_str()).await.ok(), + Some(id) => window.queries().connect().await.unwrap().get_cookie_jar(&id).ok(), } } diff --git a/src-tauri/src/notifications.rs b/src-tauri/src/notifications.rs index fc4a94a7..432f3cbc 100644 --- a/src-tauri/src/notifications.rs +++ b/src-tauri/src/notifications.rs @@ -7,7 +7,8 @@ use reqwest::Method; use serde::{Deserialize, Serialize}; use serde_json::Value; use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow}; -use yaak_models::queries::{get_key_value_raw, set_key_value_raw, UpdateSource}; +use yaak_models::manager::QueryManagerExt; +use yaak_models::queries_legacy::UpdateSource; // Check for updates every hour const MAX_UPDATE_CHECK_SECONDS: u64 = 60 * 60; @@ -43,13 +44,22 @@ impl YaakNotifier { } } - pub async fn seen(&mut self, window: &WebviewWindow, id: &str) -> Result<(), String> { + pub async fn seen( + &mut self, + window: &WebviewWindow, + id: &str, + ) -> Result<(), String> { let app_handle = window.app_handle(); let mut seen = get_kv(app_handle).await?; seen.push(id.to_string()); debug!("Marked notification as seen {}", id); let seen_json = serde_json::to_string(&seen).map_err(|e| e.to_string())?; - set_key_value_raw(app_handle, KV_NAMESPACE, KV_KEY, seen_json.as_str(), &UpdateSource::from_window(window)).await; + window.queries().connect().await.map_err(|e| e.to_string())?.set_key_value_raw( + KV_NAMESPACE, + KV_KEY, + seen_json.as_str(), + &UpdateSource::from_window(window), + ); Ok(()) } @@ -70,7 +80,7 @@ impl YaakNotifier { .query(&[ ("version", info.version.to_string().as_str()), ("launches", num_launches.to_string().as_str()), - ("platform", get_os()) + ("platform", get_os()), ]); let resp = req.send().await.map_err(|e| e.to_string())?; if resp.status() != 200 { @@ -108,7 +118,13 @@ impl YaakNotifier { } async fn get_kv(app_handle: &AppHandle) -> Result, String> { - match get_key_value_raw(app_handle, "notifications", "seen").await { + match app_handle + .queries() + .connect() + .await + .map_err(|e| e.to_string())? + .get_key_value_raw("notifications", "seen") + { None => Ok(Vec::new()), Some(v) => serde_json::from_str(&v.value).map_err(|e| e.to_string()), } diff --git a/src-tauri/src/plugin_events.rs b/src-tauri/src/plugin_events.rs index 5a680c46..95209639 100644 --- a/src-tauri/src/plugin_events.rs +++ b/src-tauri/src/plugin_events.rs @@ -9,12 +9,9 @@ use chrono::Utc; use log::warn; use tauri::{AppHandle, Emitter, Manager, Runtime, State}; use tauri_plugin_clipboard_manager::ClipboardExt; +use yaak_models::manager::QueryManagerExt; use yaak_models::models::{HttpResponse, Plugin}; -use yaak_models::queries::{ - create_default_http_response, delete_plugin_key_value, get_base_environment, get_http_request, - get_plugin_key_value, list_http_responses_for_request, list_plugins, set_plugin_key_value, - upsert_plugin, UpdateSource, -}; +use yaak_models::queries_legacy::UpdateSource; use yaak_plugins::events::{ Color, DeleteKeyValueResponse, EmptyPayload, FindHttpResponsesResponse, GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent, InternalEventPayload, @@ -55,19 +52,28 @@ pub(crate) async fn handle_plugin_event( call_frontend(window, event).await } InternalEventPayload::FindHttpResponsesRequest(req) => { - let http_responses = list_http_responses_for_request( - app_handle, - req.request_id.as_str(), - req.limit.map(|l| l as i64), - ) - .await - .unwrap_or_default(); + let http_responses = app_handle + .queries() + .connect() + .await + .unwrap() + .list_http_responses_for_request( + req.request_id.as_str(), + req.limit.map(|l| l as u64), + ) + .unwrap_or_default(); Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse { http_responses, })) } InternalEventPayload::GetHttpRequestByIdRequest(req) => { - let http_request = get_http_request(app_handle, req.id.as_str()).await.unwrap(); + let http_request = app_handle + .queries() + .connect() + .await + .unwrap() + .get_http_request(req.id.as_str()) + .unwrap(); Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse { http_request, })) @@ -80,8 +86,12 @@ pub(crate) async fn handle_plugin_event( .await .expect("Failed to get workspace_id from window URL"); let environment = environment_from_window(&window).await; - let base_environment = get_base_environment(app_handle, workspace.id.as_str()) + let base_environment = app_handle + .queries() + .connect() .await + .unwrap() + .get_base_environment(&workspace.id) .expect("Failed to get base environment"); let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); let http_request = render_http_request( @@ -104,8 +114,12 @@ pub(crate) async fn handle_plugin_event( .await .expect("Failed to get workspace_id from window URL"); let environment = environment_from_window(&window).await; - let base_environment = get_base_environment(app_handle, workspace.id.as_str()) + let base_environment = app_handle + .queries() + .connect() .await + .unwrap() + .get_base_environment(&workspace.id) .expect("Failed to get base environment"); let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); let data = render_json_value(req.data, &base_environment, environment.as_ref(), &cb) @@ -135,7 +149,7 @@ pub(crate) async fn handle_plugin_event( InternalEventPayload::ReloadResponse(_) => { let window = get_window_from_window_context(app_handle, &window_context) .expect("Failed to find window for plugin reload"); - let plugins = list_plugins(app_handle).await.unwrap(); + let plugins = app_handle.queries().connect().await.unwrap().list_plugins().unwrap(); for plugin in plugins { if plugin.directory != plugin_handle.dir { continue; @@ -145,7 +159,13 @@ pub(crate) async fn handle_plugin_event( updated_at: Utc::now().naive_utc(), // TODO: Add reloaded_at field to use instead ..plugin }; - upsert_plugin(app_handle, new_plugin, &UpdateSource::Plugin).await.unwrap(); + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_plugin(&new_plugin, &UpdateSource::Plugin) + .unwrap(); } let toast_event = plugin_handle.build_event_to_send( &WindowContext::from_window(&window), @@ -173,22 +193,29 @@ pub(crate) async fn handle_plugin_event( http_request.workspace_id = workspace.id; } - let resp = if http_request.id.is_empty() { - HttpResponse::new() + let http_response = if http_request.id.is_empty() { + HttpResponse::default() } else { - create_default_http_response( - app_handle, - http_request.id.as_str(), - &UpdateSource::Plugin, - ) - .await - .unwrap() + window + .queries() + .connect() + .await + .unwrap() + .upsert_http_response( + &HttpResponse { + request_id: http_request.id.clone(), + workspace_id: http_request.workspace_id.clone(), + ..Default::default() + }, + &UpdateSource::Plugin, + ) + .unwrap() }; let result = send_http_request( &window, &http_request, - &resp, + &http_response, environment, cookie_jar, &mut tokio::sync::watch::channel(false).1, // No-op cancel channel @@ -265,17 +292,34 @@ pub(crate) async fn handle_plugin_event( } InternalEventPayload::SetKeyValueRequest(req) => { let name = plugin_handle.name().await; - set_plugin_key_value(app_handle, &name, &req.key, &req.value).await; + app_handle + .queries() + .connect() + .await + .unwrap() + .set_plugin_key_value(&name, &req.key, &req.value); Some(InternalEventPayload::SetKeyValueResponse(SetKeyValueResponse {})) } InternalEventPayload::GetKeyValueRequest(req) => { let name = plugin_handle.name().await; - let value = get_plugin_key_value(app_handle, &name, &req.key).await.map(|v| v.value); + let value = app_handle + .queries() + .connect() + .await + .unwrap() + .get_plugin_key_value(&name, &req.key) + .map(|v| v.value); Some(InternalEventPayload::GetKeyValueResponse(GetKeyValueResponse { value })) } InternalEventPayload::DeleteKeyValueRequest(req) => { let name = plugin_handle.name().await; - let deleted = delete_plugin_key_value(app_handle, &name, &req.key).await; + let deleted = app_handle + .queries() + .connect() + .await + .unwrap() + .delete_plugin_key_value(&name, &req.key) + .unwrap(); Some(InternalEventPayload::DeleteKeyValueResponse(DeleteKeyValueResponse { deleted })) } _ => None, diff --git a/src-tauri/src/updates.rs b/src-tauri/src/updates.rs index 8d108bae..123b893e 100644 --- a/src-tauri/src/updates.rs +++ b/src-tauri/src/updates.rs @@ -1,12 +1,14 @@ use std::fmt::{Display, Formatter}; use std::time::SystemTime; +use crate::error::Result; use log::info; -use tauri::{AppHandle, Manager}; +use tauri::{Manager, Runtime, WebviewWindow}; use tauri_plugin_dialog::{DialogExt, MessageDialogButtons}; use tauri_plugin_updater::UpdaterExt; use tokio::task::block_in_place; -use yaak_models::queries::get_or_create_settings; +use yaak_models::manager::QueryManagerExt; +use yaak_models::queries_legacy::UpdateSource; use yaak_plugins::manager::PluginManager; use crate::is_dev; @@ -59,30 +61,34 @@ impl YaakUpdater { } } - pub async fn check_now( + pub async fn check_now( &mut self, - app_handle: &AppHandle, + window: &WebviewWindow, mode: UpdateMode, update_trigger: UpdateTrigger, - ) -> Result { - let settings = get_or_create_settings(app_handle).await; + ) -> Result { + let settings = window + .queries() + .connect() + .await? + .get_or_create_settings(&UpdateSource::from_window(window))?; let update_key = format!("{:x}", md5::compute(settings.id)); self.last_update_check = SystemTime::now(); info!("Checking for updates mode={}", mode); - let h = app_handle.clone(); - let update_check_result = app_handle + let w = window.clone(); + let update_check_result = w .updater_builder() .on_before_exit(move || { // Kill plugin manager before exit or NSIS installer will fail to replace sidecar // while it's running. // NOTE: This is only called on Windows - let h = h.clone(); + let w = w.clone(); block_in_place(|| { tauri::async_runtime::block_on(async move { info!("Shutting down plugin manager before update"); - let plugin_manager = h.state::(); + let plugin_manager = w.state::(); plugin_manager.terminate().await; }); }); @@ -100,11 +106,11 @@ impl YaakUpdater { .check() .await; - match update_check_result { - Ok(Some(update)) => { - let h = app_handle.clone(); - app_handle - .dialog() + let result = match update_check_result? { + None => false, + Some(update) => { + let w = window.clone(); + w.dialog() .message(format!( "{} is available. Would you like to download and install it now?", update.version @@ -121,7 +127,7 @@ impl YaakUpdater { tauri::async_runtime::spawn(async move { match update.download_and_install(|_, _| {}, || {}).await { Ok(_) => { - if h.dialog() + if w.dialog() .message("Would you like to restart the app?") .title("Update Installed") .buttons(MessageDialogButtons::OkCancelCustom( @@ -130,27 +136,27 @@ impl YaakUpdater { )) .blocking_show() { - h.restart(); + w.app_handle().restart(); } } Err(e) => { - h.dialog() + w.dialog() .message(format!("The update failed to install: {}", e)); } } }); }); - Ok(true) + true } - Ok(None) => Ok(false), - Err(e) => Err(e), - } + }; + + Ok(result) } - pub async fn maybe_check( + pub async fn maybe_check( &mut self, - app_handle: &AppHandle, + window: &WebviewWindow, mode: UpdateMode, - ) -> Result { + ) -> Result { let update_period_seconds = match mode { UpdateMode::Stable => MAX_UPDATE_CHECK_HOURS_STABLE, UpdateMode::Beta => MAX_UPDATE_CHECK_HOURS_BETA, @@ -167,6 +173,6 @@ impl YaakUpdater { return Ok(false); } - self.check_now(app_handle, mode, UpdateTrigger::Background).await + self.check_now(window, mode, UpdateTrigger::Background).await } } diff --git a/src-tauri/yaak-git/Cargo.toml b/src-tauri/yaak-git/Cargo.toml index 65afd973..28dc1bbc 100644 --- a/src-tauri/yaak-git/Cargo.toml +++ b/src-tauri/yaak-git/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] chrono = { version = "0.4.38", features = ["serde"] } -git2 = { version = "0.20.0" , features = ["vendored-libgit2", "vendored-openssl"]} +git2 = { version = "0.20.0", features = ["vendored-libgit2", "vendored-openssl"] } log = "0.4.22" serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.132" @@ -19,4 +19,4 @@ yaak-models = { workspace = true } yaak-sync = { workspace = true } [build-dependencies] -tauri-plugin = { version = "2.0.3", features = ["build"] } +tauri-plugin = { workspace = true, features = ["build"] } diff --git a/src-tauri/yaak-git/permissions/schemas/schema.json b/src-tauri/yaak-git/permissions/schemas/schema.json index e7b50e70..b996cc21 100644 --- a/src-tauri/yaak-git/permissions/schemas/schema.json +++ b/src-tauri/yaak-git/permissions/schemas/schema.json @@ -49,7 +49,7 @@ "minimum": 1.0 }, "description": { - "description": "Human-readable description of what the permission does. Tauri convention is to use

headings in markdown content for Tauri documentation generation purposes.", + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", "type": [ "string", "null" @@ -111,7 +111,7 @@ "type": "string" }, "description": { - "description": "Human-readable description of what the permission does. Tauri internal convention is to use

headings in markdown content for Tauri documentation generation purposes.", + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", "type": [ "string", "null" diff --git a/src-tauri/yaak-license/permissions/schemas/schema.json b/src-tauri/yaak-license/permissions/schemas/schema.json index 3071b110..f4a272bb 100644 --- a/src-tauri/yaak-license/permissions/schemas/schema.json +++ b/src-tauri/yaak-license/permissions/schemas/schema.json @@ -49,7 +49,7 @@ "minimum": 1.0 }, "description": { - "description": "Human-readable description of what the permission does. Tauri convention is to use

headings in markdown content for Tauri documentation generation purposes.", + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", "type": [ "string", "null" @@ -111,7 +111,7 @@ "type": "string" }, "description": { - "description": "Human-readable description of what the permission does. Tauri internal convention is to use

headings in markdown content for Tauri documentation generation purposes.", + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", "type": [ "string", "null" diff --git a/src-tauri/yaak-license/src/commands.rs b/src-tauri/yaak-license/src/commands.rs index aadf268d..ced67c96 100644 --- a/src-tauri/yaak-license/src/commands.rs +++ b/src-tauri/yaak-license/src/commands.rs @@ -5,16 +5,16 @@ use crate::{ }; use log::{debug, info}; use std::string::ToString; -use tauri::{command, AppHandle, Manager, Runtime, WebviewWindow}; +use tauri::{command, Manager, Runtime, WebviewWindow}; #[command] -pub async fn check(app_handle: AppHandle) -> Result { +pub async fn check(window: WebviewWindow) -> Result { debug!("Checking license"); check_license( - &app_handle, + &window, CheckActivationRequestPayload { app_platform: get_os().to_string(), - app_version: app_handle.package_info().version.to_string(), + app_version: window.package_info().version.to_string(), }, ) .await diff --git a/src-tauri/yaak-license/src/error.rs b/src-tauri/yaak-license/src/error.rs index e48f57b4..81792690 100644 --- a/src-tauri/yaak-license/src/error.rs +++ b/src-tauri/yaak-license/src/error.rs @@ -12,6 +12,9 @@ pub enum Error { #[error("{message}")] ClientError { message: String, error: String }, + #[error(transparent)] + ModelError(#[from] yaak_models::error::Error), + #[error("Internal server error")] ServerError, } diff --git a/src-tauri/yaak-license/src/license.rs b/src-tauri/yaak-license/src/license.rs index e5b2b8ef..c7e512bc 100644 --- a/src-tauri/yaak-license/src/license.rs +++ b/src-tauri/yaak-license/src/license.rs @@ -7,7 +7,8 @@ use std::ops::Add; use std::time::Duration; use tauri::{is_dev, AppHandle, Emitter, Manager, Runtime, WebviewWindow}; use ts_rs::TS; -use yaak_models::queries::UpdateSource; +use yaak_models::manager::QueryManagerExt; +use yaak_models::queries_legacy::UpdateSource; const KV_NAMESPACE: &str = "license"; const KV_ACTIVATION_ID_KEY: &str = "activation_id"; @@ -80,14 +81,12 @@ pub async fn activate_license( } let body: ActivateLicenseResponsePayload = response.json().await?; - yaak_models::queries::set_key_value_string( - window.app_handle(), + window.app_handle().queries().connect().await?.set_key_value_string( KV_ACTIVATION_ID_KEY, KV_NAMESPACE, body.activation_id.as_str(), &UpdateSource::from_window(&window), - ) - .await; + ); if let Err(e) = window.emit("license-activated", true) { warn!("Failed to emit check-license event: {}", e); @@ -119,13 +118,11 @@ pub async fn deactivate_license( return Err(ServerError); } - yaak_models::queries::delete_key_value( - app_handle, + app_handle.queries().connect().await?.delete_key_value( KV_ACTIVATION_ID_KEY, KV_NAMESPACE, &UpdateSource::from_window(&window), - ) - .await; + )?; if let Err(e) = app_handle.emit("license-deactivated", true) { warn!("Failed to emit deactivate-license event: {}", e); @@ -145,11 +142,15 @@ pub enum LicenseCheckStatus { } pub async fn check_license( - app_handle: &AppHandle, + window: &WebviewWindow, payload: CheckActivationRequestPayload, ) -> Result { - let activation_id = get_activation_id(app_handle).await; - let settings = yaak_models::queries::get_or_create_settings(app_handle).await; + let activation_id = get_activation_id(window.app_handle()).await; + let settings = window + .queries() + .connect() + .await? + .get_or_create_settings(&UpdateSource::from_window(window))?; let trial_end = settings.created_at.add(Duration::from_secs(TRIAL_SECONDS)); debug!("Trial ending at {trial_end:?}"); @@ -200,6 +201,9 @@ fn build_url(path: &str) -> String { } pub async fn get_activation_id(app_handle: &AppHandle) -> String { - yaak_models::queries::get_key_value_string(app_handle, KV_ACTIVATION_ID_KEY, KV_NAMESPACE, "") - .await + app_handle.queries().connect().await.unwrap().get_key_value_string( + KV_ACTIVATION_ID_KEY, + KV_NAMESPACE, + "", + ) } diff --git a/src-tauri/yaak-models/Cargo.toml b/src-tauri/yaak-models/Cargo.toml index 60f06352..8a7c5898 100644 --- a/src-tauri/yaak-models/Cargo.toml +++ b/src-tauri/yaak-models/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "yaak-models" +links = "yaak-models" version = "0.1.0" edition = "2021" publish = false @@ -18,4 +19,8 @@ serde_json = "1.0.122" sqlx = { version = "0.8.0", default-features = false, features = ["migrate", "sqlite", "runtime-tokio-rustls"] } tauri = { workspace = true } thiserror = "2.0.11" +tokio = "1.43.0" ts-rs = { workspace = true, features = ["chrono-impl", "serde-json-impl"] } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } diff --git a/src-tauri/yaak-models/bindings/gen_models.ts b/src-tauri/yaak-models/bindings/gen_models.ts index c42af1e8..6cb2a62a 100644 --- a/src-tauri/yaak-models/bindings/gen_models.ts +++ b/src-tauri/yaak-models/bindings/gen_models.ts @@ -1,6 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type AnyModel = CookieJar | Environment | Folder | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | Plugin | Settings | KeyValue | Workspace | WorkspaceMeta | WebsocketConnection | WebsocketEvent | WebsocketRequest; +export type AnyModel = CookieJar | Environment | Folder | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | KeyValue | Plugin | Settings | SyncState | WebsocketConnection | WebsocketEvent | WebsocketRequest | Workspace | WorkspaceMeta; export type Cookie = { raw_cookie: string, domain: CookieDomain, expires: CookieExpires, path: [string, boolean], }; @@ -44,7 +44,9 @@ export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, export type KeyValue = { model: "key_value", createdAt: string, updatedAt: string, key: string, namespace: string, value: string, }; -export type ModelPayload = { model: AnyModel, updateSource: UpdateSource, }; +export type ModelChangeEvent = { "type": "upsert" } | { "type": "delete" }; + +export type ModelPayload = { model: AnyModel, updateSource: UpdateSource, change: ModelChangeEvent, }; export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt: string, checkedAt: string | null, directory: string, enabled: boolean, url: string | null, }; @@ -56,8 +58,6 @@ export type ProxySettingAuth = { user: string, password: string, }; export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, theme: string, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, }; -export type SyncHistory = { model: "sync_history", id: string, workspaceId: string, createdAt: string, states: Array, checksum: string, relPath: string, syncDir: string, }; - export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, }; export type UpdateSource = { "type": "sync" } | { "type": "window", label: string, } | { "type": "plugin" } | { "type": "background" } | { "type": "import" }; diff --git a/src-tauri/yaak-models/build.rs b/src-tauri/yaak-models/build.rs new file mode 100644 index 00000000..12b6b524 --- /dev/null +++ b/src-tauri/yaak-models/build.rs @@ -0,0 +1,5 @@ +const COMMANDS: &[&str] = &["upsert", "delete"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS).build(); +} diff --git a/src-tauri/yaak-models/permissions/autogenerated/commands/delete.toml b/src-tauri/yaak-models/permissions/autogenerated/commands/delete.toml new file mode 100644 index 00000000..3d9d5234 --- /dev/null +++ b/src-tauri/yaak-models/permissions/autogenerated/commands/delete.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-delete" +description = "Enables the delete command without any pre-configured scope." +commands.allow = ["delete"] + +[[permission]] +identifier = "deny-delete" +description = "Denies the delete command without any pre-configured scope." +commands.deny = ["delete"] diff --git a/src-tauri/yaak-models/permissions/autogenerated/commands/delete_model.toml b/src-tauri/yaak-models/permissions/autogenerated/commands/delete_model.toml new file mode 100644 index 00000000..0e8a51fb --- /dev/null +++ b/src-tauri/yaak-models/permissions/autogenerated/commands/delete_model.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-delete-model" +description = "Enables the delete_model command without any pre-configured scope." +commands.allow = ["delete_model"] + +[[permission]] +identifier = "deny-delete-model" +description = "Denies the delete_model command without any pre-configured scope." +commands.deny = ["delete_model"] diff --git a/src-tauri/yaak-models/permissions/autogenerated/commands/upsert.toml b/src-tauri/yaak-models/permissions/autogenerated/commands/upsert.toml new file mode 100644 index 00000000..ea6fff09 --- /dev/null +++ b/src-tauri/yaak-models/permissions/autogenerated/commands/upsert.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-upsert" +description = "Enables the upsert command without any pre-configured scope." +commands.allow = ["upsert"] + +[[permission]] +identifier = "deny-upsert" +description = "Denies the upsert command without any pre-configured scope." +commands.deny = ["upsert"] diff --git a/src-tauri/yaak-models/permissions/autogenerated/commands/upsert_model.toml b/src-tauri/yaak-models/permissions/autogenerated/commands/upsert_model.toml new file mode 100644 index 00000000..369382e7 --- /dev/null +++ b/src-tauri/yaak-models/permissions/autogenerated/commands/upsert_model.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-upsert-model" +description = "Enables the upsert_model command without any pre-configured scope." +commands.allow = ["upsert_model"] + +[[permission]] +identifier = "deny-upsert-model" +description = "Denies the upsert_model command without any pre-configured scope." +commands.deny = ["upsert_model"] diff --git a/src-tauri/yaak-models/permissions/autogenerated/reference.md b/src-tauri/yaak-models/permissions/autogenerated/reference.md new file mode 100644 index 00000000..4b321b3a --- /dev/null +++ b/src-tauri/yaak-models/permissions/autogenerated/reference.md @@ -0,0 +1,120 @@ +## Default Permission + +Default permissions for the plugin + +- `allow-upsert` +- `allow-delete` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`yaak-models:allow-delete` + + + +Enables the delete command without any pre-configured scope. + +
+ +`yaak-models:deny-delete` + + + +Denies the delete command without any pre-configured scope. + +
+ +`yaak-models:allow-delete-model` + + + +Enables the delete_model command without any pre-configured scope. + +
+ +`yaak-models:deny-delete-model` + + + +Denies the delete_model command without any pre-configured scope. + +
+ +`yaak-models:allow-upsert` + + + +Enables the upsert command without any pre-configured scope. + +
+ +`yaak-models:deny-upsert` + + + +Denies the upsert command without any pre-configured scope. + +
+ +`yaak-models:allow-upsert-model` + + + +Enables the upsert_model command without any pre-configured scope. + +
+ +`yaak-models:deny-upsert-model` + + + +Denies the upsert_model command without any pre-configured scope. + +
diff --git a/src-tauri/yaak-models/permissions/default.toml b/src-tauri/yaak-models/permissions/default.toml new file mode 100644 index 00000000..5a93edfb --- /dev/null +++ b/src-tauri/yaak-models/permissions/default.toml @@ -0,0 +1,6 @@ +[default] +description = "Default permissions for the plugin" +permissions = [ + "allow-upsert", + "allow-delete", +] diff --git a/src-tauri/yaak-models/permissions/schemas/schema.json b/src-tauri/yaak-models/permissions/schemas/schema.json new file mode 100644 index 00000000..8a9f675e --- /dev/null +++ b/src-tauri/yaak-models/permissions/schemas/schema.json @@ -0,0 +1,345 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "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" + } + } + } + }, + "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" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the delete command without any pre-configured scope.", + "type": "string", + "const": "allow-delete" + }, + { + "description": "Denies the delete command without any pre-configured scope.", + "type": "string", + "const": "deny-delete" + }, + { + "description": "Enables the delete_model command without any pre-configured scope.", + "type": "string", + "const": "allow-delete-model" + }, + { + "description": "Denies the delete_model command without any pre-configured scope.", + "type": "string", + "const": "deny-delete-model" + }, + { + "description": "Enables the upsert command without any pre-configured scope.", + "type": "string", + "const": "allow-upsert" + }, + { + "description": "Denies the upsert command without any pre-configured scope.", + "type": "string", + "const": "deny-upsert" + }, + { + "description": "Enables the upsert_model command without any pre-configured scope.", + "type": "string", + "const": "allow-upsert-model" + }, + { + "description": "Denies the upsert_model command without any pre-configured scope.", + "type": "string", + "const": "deny-upsert-model" + }, + { + "description": "Default permissions for the plugin", + "type": "string", + "const": "default" + } + ] + } + } +} \ No newline at end of file diff --git a/src-tauri/yaak-models/src/commands.rs b/src-tauri/yaak-models/src/commands.rs new file mode 100644 index 00000000..a0d59750 --- /dev/null +++ b/src-tauri/yaak-models/src/commands.rs @@ -0,0 +1,24 @@ +use crate::error::Result; +use crate::manager::QueryManagerExt; +use crate::models::AnyModel; +use crate::queries_legacy::UpdateSource; +use tauri::{Runtime, WebviewWindow}; + +#[tauri::command] +pub(crate) async fn upsert( + window: WebviewWindow, + model: AnyModel, +) -> Result { + let queries = window.queries().connect().await?; + let id = match model { + AnyModel::HttpRequest(r) => queries.upsert(&r, &UpdateSource::from_window(&window))?.id, + _ => todo!(), + }; + + Ok(id) +} + +#[tauri::command] +pub(crate) fn delete() -> Result<()> { + Ok(()) +} diff --git a/src-tauri/yaak-models/src/error.rs b/src-tauri/yaak-models/src/error.rs index 27f693f9..4fc6e030 100644 --- a/src-tauri/yaak-models/src/error.rs +++ b/src-tauri/yaak-models/src/error.rs @@ -1,3 +1,4 @@ +use serde::{Serialize, Serializer}; use thiserror::Error; #[derive(Error, Debug)] @@ -11,11 +12,29 @@ pub enum Error { #[error("JSON error: {0}")] JsonError(#[from] serde_json::Error), - #[error("Model not found {0}")] + #[error("Model not found: {0}")] ModelNotFound(String), + #[error("Model serialization error: {0}")] + ModelSerializationError(String), + + #[error("Model error: {0}")] + GenericError(String), + + #[error("Row not found")] + RowNotFound, + #[error("unknown error")] Unknown, } +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + pub type Result = std::result::Result; diff --git a/src-tauri/yaak-models/src/lib.rs b/src-tauri/yaak-models/src/lib.rs index 1b4b1ee7..8d12790f 100644 --- a/src-tauri/yaak-models/src/lib.rs +++ b/src-tauri/yaak-models/src/lib.rs @@ -1,7 +1,98 @@ +use crate::commands::{delete, upsert}; +use crate::manager::QueryManager; +use crate::queries_legacy::ModelChangeEvent; +use log::info; +use r2d2::Pool; +use r2d2_sqlite::SqliteConnectionManager; +use sqlx::migrate::Migrator; +use sqlx::sqlite::SqliteConnectOptions; +use sqlx::SqlitePool; +use std::fs::create_dir_all; +use std::path::PathBuf; +use std::str::FromStr; +use std::time::Duration; +use tauri::async_runtime::Mutex; +use tauri::path::BaseDirectory; +use tauri::plugin::TauriPlugin; +use tauri::{generate_handler, AppHandle, Emitter, Manager, Runtime}; +use tokio::sync::mpsc; + +mod commands; + pub mod error; +pub mod manager; pub mod models; pub mod queries; - -pub mod plugin; +pub mod queries_legacy; pub mod render; -pub mod manager; + +pub struct SqliteConnection(pub Mutex>); + +impl SqliteConnection { + pub(crate) fn new(pool: Pool) -> Self { + Self(Mutex::new(pool)) + } +} + +pub fn init() -> TauriPlugin { + tauri::plugin::Builder::new("yaak_models") + .invoke_handler(generate_handler![upsert, delete]) + .setup(|app_handle, _api| { + let app_path = app_handle.path().app_data_dir().unwrap(); + create_dir_all(app_path.clone()).expect("Problem creating App directory!"); + + let db_file_path = app_path.join("db.sqlite"); + + { + let db_file_path = db_file_path.clone(); + tauri::async_runtime::block_on(async move { + must_migrate_db(app_handle.app_handle(), &db_file_path).await; + }); + }; + + let manager = SqliteConnectionManager::file(db_file_path); + let pool = Pool::builder() + .max_size(100) // Up from 10 (just in case) + .connection_timeout(Duration::from_secs(10)) // Down from 30 + .build(manager) + .unwrap(); + + app_handle.manage(SqliteConnection::new(pool.clone())); + + { + let (tx, mut rx) = mpsc::channel(128); + app_handle.manage(QueryManager::new(pool, tx)); + let app_handle = app_handle.clone(); + tauri::async_runtime::spawn(async move { + while let Some(p) = rx.recv().await { + let name = match p.change { + ModelChangeEvent::Upsert => "upserted_model", + ModelChangeEvent::Delete => "deleted_model", + }; + app_handle.emit(name, p).unwrap(); + } + }); + } + + Ok(()) + }) + .build() +} + +async fn must_migrate_db(app_handle: &AppHandle, sqlite_file_path: &PathBuf) { + info!("Connecting to database at {sqlite_file_path:?}"); + let sqlite_file_path = sqlite_file_path.to_str().unwrap().to_string(); + let opts = SqliteConnectOptions::from_str(&sqlite_file_path).unwrap().create_if_missing(true); + let pool = SqlitePool::connect_with(opts).await.expect("Failed to connect to database"); + let p = app_handle + .path() + .resolve("migrations", BaseDirectory::Resource) + .expect("failed to resolve resource"); + + info!("Running database migrations from: {}", p.to_string_lossy()); + let mut m = Migrator::new(p).await.expect("Failed to load migrations"); + m.set_ignore_missing(true); // So we can roll back versions and not crash + m.run(&pool).await.expect("Failed to run migrations"); + + info!("Database migrations complete"); +} diff --git a/src-tauri/yaak-models/src/manager.rs b/src-tauri/yaak-models/src/manager.rs index 4da71161..de0f7693 100644 --- a/src-tauri/yaak-models/src/manager.rs +++ b/src-tauri/yaak-models/src/manager.rs @@ -1,40 +1,108 @@ use crate::error::Result; -use crate::models::{Workspace, WorkspaceIden}; -use crate::plugin::SqliteConnection; +use crate::queries_legacy::ModelPayload; use r2d2::{Pool, PooledConnection}; use r2d2_sqlite::SqliteConnectionManager; -use rusqlite::Connection; -use sea_query::{Asterisk, Order, Query, SqliteQueryBuilder}; -use sea_query_rusqlite::RusqliteBinder; -use std::future::Future; -use std::ops::Deref; -use tauri::{AppHandle, Manager, Runtime}; +use rusqlite::{Connection, Statement, ToSql, Transaction, TransactionBehavior}; +use std::sync::Arc; +use tauri::{Manager, Runtime}; +use tokio::sync::{mpsc, Mutex}; -pub struct QueryManager { - pool: Pool, +pub trait QueryManagerExt<'a, R> { + fn queries(&'a self) -> &'a QueryManager; } -pub trait DBConnection { - fn connect( - &self, - ) -> impl Future>> + Send; -} - -impl DBConnection for AppHandle { - async fn connect(&self) -> Result> { - let dbm = &*self.state::(); - let db = dbm.0.lock().await.get()?; - Ok(db) +impl<'a, R: Runtime, T: Manager> QueryManagerExt<'a, R> for T { + fn queries(&'a self) -> &'a QueryManager { + let qm = self.state::(); + qm.inner() } } -pub async fn list_workspaces>(c: &T) -> Result> { - let (sql, params) = Query::select() - .from(WorkspaceIden::Table) - .column(Asterisk) - .order_by(WorkspaceIden::Name, Order::Asc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = c.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) +#[derive(Clone)] +pub struct QueryManager { + pool: Arc>>, + events_tx: mpsc::Sender, +} + +impl QueryManager { + pub(crate) fn new( + pool: Pool, + events_tx: mpsc::Sender, + ) -> Self { + QueryManager { + pool: Arc::new(Mutex::new(pool)), + events_tx, + } + } + + pub async fn connect(&self) -> Result { + let conn = self.pool.lock().await.get()?; + Ok(DbContext { + tx: self.events_tx.clone(), + conn: ConnectionOrTx::Connection(conn), + }) + } + + pub async fn with_conn(&self, func: F) -> Result + where + F: FnOnce(&DbContext) -> Result, + { + let conn = self.pool.lock().await.get()?; + let db_context = DbContext { + tx: self.events_tx.clone(), + conn: ConnectionOrTx::Connection(conn), + }; + func(&db_context) + } + + pub async fn with_tx(&self, func: F) -> Result + where + F: FnOnce(&DbContext) -> Result, + { + let mut conn = self.pool.lock().await.get()?; + let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?; + + let db_context = DbContext { + tx: self.events_tx.clone(), + conn: ConnectionOrTx::Transaction(&tx), + }; + + match func(&db_context) { + Ok(val) => { + tx.commit()?; + Ok(val) + } + Err(e) => { + tx.rollback()?; + Err(e) + } + } + } +} + +pub enum ConnectionOrTx<'a> { + Connection(PooledConnection), + Transaction(&'a Transaction<'a>), +} + +impl<'a> ConnectionOrTx<'a> { + pub(crate) fn resolve(&self) -> &Connection { + match self { + ConnectionOrTx::Connection(c) => c, + ConnectionOrTx::Transaction(c) => c, + } + } + + pub(crate) fn prepare(&self, sql: &str) -> rusqlite::Result> { + self.resolve().prepare(sql) + } + + pub(crate) fn execute(&self, sql: &str, params: &[&dyn ToSql]) -> rusqlite::Result { + self.resolve().execute(sql, params) + } +} + +pub struct DbContext<'a> { + pub(crate) tx: mpsc::Sender, + pub(crate) conn: ConnectionOrTx<'a>, } diff --git a/src-tauri/yaak-models/src/models.rs b/src-tauri/yaak-models/src/models.rs index 0d266e72..cd8cbdcd 100644 --- a/src-tauri/yaak-models/src/models.rs +++ b/src-tauri/yaak-models/src/models.rs @@ -1,6 +1,12 @@ -use chrono::NaiveDateTime; +use crate::error::Result; +use crate::models::HttpRequestIden::{ + Authentication, AuthenticationType, Body, BodyType, CreatedAt, Description, FolderId, Headers, + Method, Name, SortPriority, UpdatedAt, Url, UrlParameters, WorkspaceId, +}; +use crate::queries_legacy::{generate_model_id, UpdateSource}; +use chrono::{NaiveDateTime, Utc}; use rusqlite::Row; -use sea_query::Iden; +use sea_query::{enum_def, IntoIden, IntoTableRef, SimpleExpr}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use std::collections::BTreeMap; @@ -8,6 +14,17 @@ use std::fmt::Display; use std::str::FromStr; use ts_rs::TS; +#[macro_export] +macro_rules! impl_model { + ($t:ty, $variant:ident) => { + impl $crate::Model for $t { + fn into_any(self) -> $crate::AnyModel { + $crate::AnyModel::$variant(self) + } + } + }; +} + #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase", tag = "type")] #[ts(export, export_to = "gen_models.ts")] @@ -39,9 +56,9 @@ pub enum EditorKeymap { } impl FromStr for EditorKeymap { - type Err = (); + type Err = crate::error::Error; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { match s { "default" => Ok(Self::Default), "vscode" => Ok(Self::Vscode), @@ -73,6 +90,7 @@ impl Default for EditorKeymap { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "settings")] pub struct Settings { #[ts(type = "\"settings\"")] pub model: String, @@ -94,52 +112,87 @@ pub struct Settings { pub editor_keymap: EditorKeymap, } -#[derive(Iden)] -pub enum SettingsIden { - #[iden = "settings"] - Table, - Model, - Id, - CreatedAt, - UpdatedAt, +impl UpsertModelInfo for Settings { + fn table_name() -> impl IntoTableRef { + SettingsIden::Table + } - Appearance, - EditorFontSize, - EditorKeymap, - EditorSoftWrap, - InterfaceFontSize, - InterfaceScale, - OpenWorkspaceNewWindow, - Proxy, - Theme, - ThemeDark, - ThemeLight, - UpdateChannel, -} + fn id_column() -> impl IntoIden + Eq + Clone { + SettingsIden::Id + } -impl<'s> TryFrom<&Row<'s>> for Settings { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { - let proxy: Option = r.get("proxy")?; - let editor_keymap: String = r.get("editor_keymap")?; + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use SettingsIden::*; + let proxy = match self.proxy { + None => None, + Some(p) => Some(serde_json::to_string(&p)?), + }; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (Appearance, self.appearance.as_str().into()), + (EditorFontSize, self.editor_font_size.into()), + (EditorKeymap, self.editor_keymap.to_string().into()), + (EditorSoftWrap, self.editor_soft_wrap.into()), + (InterfaceFontSize, self.interface_font_size.into()), + (InterfaceScale, self.interface_scale.into()), + (OpenWorkspaceNewWindow, self.open_workspace_new_window.into()), + (Theme, self.theme.as_str().into()), + (ThemeDark, self.theme_dark.as_str().into()), + (ThemeLight, self.theme_light.as_str().into()), + (UpdateChannel, self.update_channel.into()), + (Proxy, proxy.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + SettingsIden::UpdatedAt, + SettingsIden::Appearance, + SettingsIden::EditorFontSize, + SettingsIden::EditorKeymap, + SettingsIden::EditorSoftWrap, + SettingsIden::InterfaceFontSize, + SettingsIden::InterfaceScale, + SettingsIden::OpenWorkspaceNewWindow, + SettingsIden::Proxy, + SettingsIden::Theme, + SettingsIden::ThemeDark, + SettingsIden::ThemeLight, + SettingsIden::UpdateChannel, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { + let proxy: Option = row.get("proxy")?; + let editor_keymap: String = row.get("editor_keymap")?; Ok(Self { - id: r.get("id")?, - model: r.get("model")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - appearance: r.get("appearance")?, - editor_font_size: r.get("editor_font_size")?, + id: row.get("id")?, + model: row.get("model")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + appearance: row.get("appearance")?, + editor_font_size: row.get("editor_font_size")?, editor_keymap: EditorKeymap::from_str(editor_keymap.as_str()).unwrap(), - editor_soft_wrap: r.get("editor_soft_wrap")?, - interface_font_size: r.get("interface_font_size")?, - interface_scale: r.get("interface_scale")?, - open_workspace_new_window: r.get("open_workspace_new_window")?, + editor_soft_wrap: row.get("editor_soft_wrap")?, + interface_font_size: row.get("interface_font_size")?, + interface_scale: row.get("interface_scale")?, + open_workspace_new_window: row.get("open_workspace_new_window")?, proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }), - theme: r.get("theme")?, - theme_dark: r.get("theme_dark")?, - theme_light: r.get("theme_light")?, - update_channel: r.get("update_channel")?, + theme: row.get("theme")?, + theme_dark: row.get("theme_dark")?, + theme_light: row.get("theme_light")?, + update_channel: row.get("update_channel")?, }) } } @@ -147,6 +200,7 @@ impl<'s> TryFrom<&Row<'s>> for Settings { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "workspaces")] pub struct Workspace { #[ts(type = "\"workspace\"")] pub model: String, @@ -164,36 +218,61 @@ pub struct Workspace { pub setting_request_timeout: i32, } -#[derive(Iden)] -pub enum WorkspaceIden { - #[iden = "workspaces"] - Table, - Model, - Id, - CreatedAt, - UpdatedAt, +impl UpsertModelInfo for Workspace { + fn table_name() -> impl IntoTableRef { + WorkspaceIden::Table + } - Description, - Name, - SettingFollowRedirects, - SettingRequestTimeout, - SettingValidateCertificates, -} + fn id_column() -> impl IntoIden + Eq + Clone { + WorkspaceIden::Id + } -impl<'s> TryFrom<&Row<'s>> for Workspace { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use WorkspaceIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (Name, self.name.trim().into()), + (Description, self.description.into()), + (SettingFollowRedirects, self.setting_follow_redirects.into()), + (SettingRequestTimeout, self.setting_request_timeout.into()), + (SettingValidateCertificates, self.setting_validate_certificates.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + WorkspaceIden::UpdatedAt, + WorkspaceIden::Name, + WorkspaceIden::Description, + WorkspaceIden::SettingRequestTimeout, + WorkspaceIden::SettingFollowRedirects, + WorkspaceIden::SettingRequestTimeout, + WorkspaceIden::SettingValidateCertificates, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { Ok(Self { - id: r.get("id")?, - model: r.get("model")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - name: r.get("name")?, - description: r.get("description")?, - setting_follow_redirects: r.get("setting_follow_redirects")?, - setting_request_timeout: r.get("setting_request_timeout")?, - setting_validate_certificates: r.get("setting_validate_certificates")?, + id: row.get("id")?, + model: row.get("model")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + name: row.get("name")?, + description: row.get("description")?, + setting_follow_redirects: row.get("setting_follow_redirects")?, + setting_request_timeout: row.get("setting_request_timeout")?, + setting_validate_certificates: row.get("setting_validate_certificates")?, }) } } @@ -213,6 +292,7 @@ impl Workspace { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "workspace_metas")] pub struct WorkspaceMeta { #[ts(type = "\"workspace_meta\"")] pub model: String, @@ -223,30 +303,50 @@ pub struct WorkspaceMeta { pub setting_sync_dir: Option, } -#[derive(Iden)] -pub enum WorkspaceMetaIden { - #[iden = "workspace_metas"] - Table, - Model, - Id, - WorkspaceId, - CreatedAt, - UpdatedAt, +impl UpsertModelInfo for WorkspaceMeta { + fn table_name() -> impl IntoTableRef { + WorkspaceMetaIden::Table + } - SettingSyncDir, -} + fn id_column() -> impl IntoIden + Eq + Clone { + WorkspaceMetaIden::Id + } -impl<'s> TryFrom<&Row<'s>> for WorkspaceMeta { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use WorkspaceMetaIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (WorkspaceId, self.workspace_id.into()), + (SettingSyncDir, self.setting_sync_dir.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + WorkspaceMetaIden::UpdatedAt, + WorkspaceMetaIden::SettingSyncDir, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { Ok(Self { - id: r.get("id")?, - workspace_id: r.get("workspace_id")?, - model: r.get("model")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - setting_sync_dir: r.get("setting_sync_dir")?, + id: row.get("id")?, + workspace_id: row.get("workspace_id")?, + model: row.get("model")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + setting_sync_dir: row.get("setting_sync_dir")?, }) } } @@ -279,6 +379,7 @@ pub struct Cookie { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "cookie_jars")] pub struct CookieJar { #[ts(type = "\"cookie_jar\"")] pub model: String, @@ -291,32 +392,53 @@ pub struct CookieJar { pub name: String, } -#[derive(Iden)] -pub enum CookieJarIden { - #[iden = "cookie_jars"] - Table, - Id, - Model, - WorkspaceId, - CreatedAt, - UpdatedAt, +impl UpsertModelInfo for CookieJar { + fn table_name() -> impl IntoTableRef { + CookieJarIden::Table + } - Cookies, - Name, -} + fn id_column() -> impl IntoIden + Eq + Clone { + CookieJarIden::Id + } -impl<'s> TryFrom<&Row<'s>> for CookieJar { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { - let cookies: String = r.get("cookies")?; + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use CookieJarIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (WorkspaceId, self.workspace_id.into()), + (Name, self.name.trim().into()), + (Cookies, serde_json::to_string(&self.cookies)?.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + CookieJarIden::UpdatedAt, + CookieJarIden::Name, + CookieJarIden::Cookies, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { + let cookies: String = row.get("cookies")?; Ok(Self { - id: r.get("id")?, - model: r.get("model")?, - workspace_id: r.get("workspace_id")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - name: r.get("name")?, + id: row.get("id")?, + model: row.get("model")?, + workspace_id: row.get("workspace_id")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + name: row.get("name")?, cookies: serde_json::from_str(cookies.as_str()).unwrap_or_default(), }) } @@ -325,6 +447,7 @@ impl<'s> TryFrom<&Row<'s>> for CookieJar { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "environments")] pub struct Environment { #[ts(type = "\"environment\"")] pub model: String, @@ -338,34 +461,55 @@ pub struct Environment { pub variables: Vec, } -#[derive(Iden)] -pub enum EnvironmentIden { - #[iden = "environments"] - Table, - Model, - Id, - CreatedAt, - UpdatedAt, - EnvironmentId, - WorkspaceId, +impl UpsertModelInfo for Environment { + fn table_name() -> impl IntoTableRef { + EnvironmentIden::Table + } - Name, - Variables, -} + fn id_column() -> impl IntoIden + Eq + Clone { + EnvironmentIden::Id + } -impl<'s> TryFrom<&Row<'s>> for Environment { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { - let variables: String = r.get("variables")?; + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use EnvironmentIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (EnvironmentId, self.environment_id.into()), + (WorkspaceId, self.workspace_id.into()), + (Name, self.name.trim().into()), + (Variables, serde_json::to_string(&self.variables)?.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + EnvironmentIden::UpdatedAt, + EnvironmentIden::Name, + EnvironmentIden::Variables, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { + let variables: String = row.get("variables")?; Ok(Self { - id: r.get("id")?, - model: r.get("model")?, - workspace_id: r.get("workspace_id")?, - environment_id: r.get("environment_id")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - name: r.get("name")?, + id: row.get("id")?, + model: row.get("model")?, + workspace_id: row.get("workspace_id")?, + environment_id: row.get("environment_id")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + name: row.get("name")?, variables: serde_json::from_str(variables.as_str()).unwrap_or_default(), }) } @@ -387,6 +531,7 @@ pub struct EnvironmentVariable { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "folders")] pub struct Folder { #[ts(type = "\"folder\"")] pub model: String, @@ -401,36 +546,59 @@ pub struct Folder { pub sort_priority: f32, } -#[derive(Iden)] -pub enum FolderIden { - #[iden = "folders"] - Table, - Id, - Model, - WorkspaceId, - FolderId, - CreatedAt, - UpdatedAt, +impl UpsertModelInfo for Folder { + fn table_name() -> impl IntoTableRef { + FolderIden::Table + } - Name, - Description, - SortPriority, -} + fn id_column() -> impl IntoIden + Eq + Clone { + FolderIden::Id + } -impl<'s> TryFrom<&Row<'s>> for Folder { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + generate_model_id(ModelType::TypeFolder) + } - fn try_from(r: &Row<'s>) -> Result { + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use FolderIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (WorkspaceId, self.workspace_id.into()), + (FolderId, self.folder_id.into()), + (Name, self.name.trim().into()), + (Description, self.description.into()), + (SortPriority, self.sort_priority.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + FolderIden::UpdatedAt, + FolderIden::Name, + FolderIden::Description, + FolderIden::FolderId, + FolderIden::SortPriority, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { Ok(Self { - id: r.get("id")?, - model: r.get("model")?, - sort_priority: r.get("sort_priority")?, - workspace_id: r.get("workspace_id")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - folder_id: r.get("folder_id")?, - name: r.get("name")?, - description: r.get("description")?, + id: row.get("id")?, + model: row.get("model")?, + sort_priority: row.get("sort_priority")?, + workspace_id: row.get("workspace_id")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + folder_id: row.get("folder_id")?, + name: row.get("name")?, + description: row.get("description")?, }) } } @@ -464,6 +632,7 @@ pub struct HttpUrlParameter { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "http_requests")] pub struct HttpRequest { #[ts(type = "\"http_request\"")] pub model: String, @@ -489,34 +658,62 @@ pub struct HttpRequest { pub url_parameters: Vec, } -#[derive(Iden)] -pub enum HttpRequestIden { - #[iden = "http_requests"] - Table, - Id, - Model, - CreatedAt, - UpdatedAt, - WorkspaceId, - FolderId, +impl UpsertModelInfo for HttpRequest { + fn table_name() -> impl IntoTableRef { + HttpRequestIden::Table + } - Authentication, - AuthenticationType, - Body, - BodyType, - Description, - Headers, - Method, - Name, - SortPriority, - Url, - UrlParameters, -} + fn id_column() -> impl IntoIden + Eq + Clone { + HttpRequestIden::Id + } -impl<'s> TryFrom<&Row<'s>> for HttpRequest { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.to_string() + } - fn try_from(r: &Row<'s>) -> Result { + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (WorkspaceId, self.workspace_id.into()), + (FolderId, self.folder_id.into()), + (Name, self.name.trim().into()), + (Description, self.description.into()), + (Url, self.url.into()), + (UrlParameters, serde_json::to_string(&self.url_parameters)?.into()), + (Method, self.method.into()), + (Body, serde_json::to_string(&self.body)?.into()), + (BodyType, self.body_type.into()), + (Authentication, serde_json::to_string(&self.authentication)?.into()), + (AuthenticationType, self.authentication_type.into()), + (Headers, serde_json::to_string(&self.headers)?.into()), + (SortPriority, self.sort_priority.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + UpdatedAt, + WorkspaceId, + Name, + Description, + FolderId, + Method, + Headers, + Body, + BodyType, + Authentication, + AuthenticationType, + Url, + UrlParameters, + SortPriority, + ] + } + + fn from_row(r: &Row) -> rusqlite::Result { let url_parameters: String = r.get("url_parameters")?; let body: String = r.get("body")?; let authentication: String = r.get("authentication")?; @@ -562,6 +759,7 @@ impl Default for WebsocketConnectionState { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "websocket_connections")] pub struct WebsocketConnection { #[ts(type = "\"websocket_connection\"")] pub model: String, @@ -579,44 +777,69 @@ pub struct WebsocketConnection { pub url: String, } -#[derive(Iden)] -pub enum WebsocketConnectionIden { - #[iden = "websocket_connections"] - Table, - Id, - Model, - CreatedAt, - UpdatedAt, - WorkspaceId, - RequestId, +impl UpsertModelInfo for WebsocketConnection { + fn table_name() -> impl IntoTableRef { + WebsocketConnectionIden::Table + } - Elapsed, - Error, - Headers, - State, - Status, - Url, -} + fn id_column() -> impl IntoIden + Eq + Clone { + WebsocketConnectionIden::Id + } -impl<'s> TryFrom<&Row<'s>> for WebsocketConnection { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { - let headers: String = r.get("headers")?; - let state: String = r.get("state")?; + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use WebsocketConnectionIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (WorkspaceId, self.workspace_id.into()), + (RequestId, self.request_id.into()), + (Elapsed, self.elapsed.into()), + (Error, self.error.into()), + (Headers, serde_json::to_string(&self.headers)?.into()), + (State, serde_json::to_value(&self.state)?.as_str().into()), + (Status, self.status.into()), + (Url, self.url.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + WebsocketConnectionIden::UpdatedAt, + WebsocketConnectionIden::Elapsed, + WebsocketConnectionIden::Error, + WebsocketConnectionIden::Headers, + WebsocketConnectionIden::State, + WebsocketConnectionIden::Status, + WebsocketConnectionIden::Url, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { + let headers: String = row.get("headers")?; + let state: String = row.get("state")?; Ok(Self { - id: r.get("id")?, - model: r.get("model")?, - workspace_id: r.get("workspace_id")?, - request_id: r.get("request_id")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - url: r.get("url")?, + id: row.get("id")?, + model: row.get("model")?, + workspace_id: row.get("workspace_id")?, + request_id: row.get("request_id")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + url: row.get("url")?, headers: serde_json::from_str(headers.as_str()).unwrap_or_default(), - elapsed: r.get("elapsed")?, - error: r.get("error")?, + elapsed: row.get("elapsed")?, + error: row.get("error")?, state: serde_json::from_str(format!(r#""{state}""#).as_str()).unwrap(), - status: r.get("status")?, + status: row.get("status")?, }) } } @@ -638,6 +861,7 @@ impl Default for WebsocketMessageType { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "websocket_requests")] pub struct WebsocketRequest { #[ts(type = "\"websocket_request\"")] pub model: String, @@ -659,51 +883,81 @@ pub struct WebsocketRequest { pub url_parameters: Vec, } -#[derive(Iden)] -pub enum WebsocketRequestIden { - #[iden = "websocket_requests"] - Table, - Id, - Model, - CreatedAt, - UpdatedAt, - WorkspaceId, - FolderId, +impl UpsertModelInfo for WebsocketRequest { + fn table_name() -> impl IntoTableRef { + WebsocketRequestIden::Table + } - Authentication, - AuthenticationType, - Message, - Description, - Headers, - Name, - SortPriority, - Url, - UrlParameters, -} + fn id_column() -> impl IntoIden + Eq + Clone { + WebsocketRequestIden::Id + } -impl<'s> TryFrom<&Row<'s>> for WebsocketRequest { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { - let url_parameters: String = r.get("url_parameters")?; - let authentication: String = r.get("authentication")?; - let headers: String = r.get("headers")?; + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use WebsocketRequestIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (WorkspaceId, self.workspace_id.into()), + (FolderId, self.folder_id.as_ref().map(|s| s.as_str()).into()), + (Authentication, serde_json::to_string(&self.authentication)?.into()), + (AuthenticationType, self.authentication_type.as_ref().map(|s| s.as_str()).into()), + (Description, self.description.into()), + (Headers, serde_json::to_string(&self.headers)?.into()), + (Message, self.message.into()), + (Name, self.name.trim().into()), + (SortPriority, self.sort_priority.into()), + (Url, self.url.into()), + (UrlParameters, serde_json::to_string(&self.url_parameters)?.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + WebsocketRequestIden::UpdatedAt, + WebsocketRequestIden::WorkspaceId, + WebsocketRequestIden::FolderId, + WebsocketRequestIden::Authentication, + WebsocketRequestIden::AuthenticationType, + WebsocketRequestIden::Description, + WebsocketRequestIden::Headers, + WebsocketRequestIden::Message, + WebsocketRequestIden::Name, + WebsocketRequestIden::SortPriority, + WebsocketRequestIden::Url, + WebsocketRequestIden::UrlParameters, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { + let url_parameters: String = row.get("url_parameters")?; + let authentication: String = row.get("authentication")?; + let headers: String = row.get("headers")?; Ok(Self { - id: r.get("id")?, - model: r.get("model")?, - sort_priority: r.get("sort_priority")?, - workspace_id: r.get("workspace_id")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - url: r.get("url")?, + id: row.get("id")?, + model: row.get("model")?, + sort_priority: row.get("sort_priority")?, + workspace_id: row.get("workspace_id")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + url: row.get("url")?, url_parameters: serde_json::from_str(url_parameters.as_str()).unwrap_or_default(), - message: r.get("message")?, - description: r.get("description")?, + message: row.get("message")?, + description: row.get("description")?, authentication: serde_json::from_str(authentication.as_str()).unwrap_or_default(), - authentication_type: r.get("authentication_type")?, + authentication_type: row.get("authentication_type")?, headers: serde_json::from_str(headers.as_str()).unwrap_or_default(), - folder_id: r.get("folder_id")?, - name: r.get("name")?, + folder_id: row.get("folder_id")?, + name: row.get("name")?, }) } } @@ -730,6 +984,7 @@ impl Default for WebsocketEventType { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "websocket_events")] pub struct WebsocketEvent { #[ts(type = "\"websocket_event\"")] pub model: String, @@ -745,38 +1000,60 @@ pub struct WebsocketEvent { pub message_type: WebsocketEventType, } -#[derive(Iden)] -pub enum WebsocketEventIden { - #[iden = "websocket_events"] - Table, - Model, - Id, - CreatedAt, - UpdatedAt, - WorkspaceId, - RequestId, - ConnectionId, - IsServer, +impl UpsertModelInfo for WebsocketEvent { + fn table_name() -> impl IntoTableRef { + WebsocketEventIden::Table + } - MessageType, - Message, -} + fn id_column() -> impl IntoIden + Eq + Clone { + WebsocketEventIden::Id + } -impl<'s> TryFrom<&Row<'s>> for WebsocketEvent { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { - let message_type: String = r.get("message_type")?; + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use WebsocketEventIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (WorkspaceId, self.workspace_id.into()), + (ConnectionId, self.connection_id.into()), + (RequestId, self.request_id.into()), + (MessageType, serde_json::to_string(&self.message_type)?.into()), + (IsServer, self.is_server.into()), + (Message, self.message.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + WebsocketEventIden::UpdatedAt, + WebsocketEventIden::MessageType, + WebsocketEventIden::IsServer, + WebsocketEventIden::Message, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { + let message_type: String = row.get("message_type")?; Ok(Self { - id: r.get("id")?, - model: r.get("model")?, - workspace_id: r.get("workspace_id")?, - request_id: r.get("request_id")?, - connection_id: r.get("connection_id")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - message: r.get("message")?, - is_server: r.get("is_server")?, + id: row.get("id")?, + model: row.get("model")?, + workspace_id: row.get("workspace_id")?, + request_id: row.get("request_id")?, + connection_id: row.get("connection_id")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + message: row.get("message")?, + is_server: row.get("is_server")?, message_type: serde_json::from_str(message_type.as_str()).unwrap_or_default(), }) } @@ -808,6 +1085,7 @@ impl Default for HttpResponseState { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "http_responses")] pub struct HttpResponse { #[ts(type = "\"http_response\"")] pub model: String, @@ -831,35 +1109,66 @@ pub struct HttpResponse { pub version: Option, } -#[derive(Iden)] -pub enum HttpResponseIden { - #[iden = "http_responses"] - Table, - Model, - Id, - CreatedAt, - UpdatedAt, - WorkspaceId, - RequestId, +impl UpsertModelInfo for HttpResponse { + fn table_name() -> impl IntoTableRef { + HttpResponseIden::Table + } - BodyPath, - ContentLength, - Elapsed, - ElapsedHeaders, - Error, - Headers, - RemoteAddr, - Status, - StatusReason, - State, - Url, - Version, -} + fn id_column() -> impl IntoIden + Eq + Clone { + HttpResponseIden::Id + } -impl<'s> TryFrom<&Row<'s>> for HttpResponse { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use HttpResponseIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (RequestId, self.request_id.into()), + (WorkspaceId, self.workspace_id.into()), + (BodyPath, self.body_path.into()), + (ContentLength, self.content_length.into()), + (Elapsed, self.elapsed.into()), + (ElapsedHeaders, self.elapsed_headers.into()), + (Error, self.error.into()), + (Headers, serde_json::to_string(&self.headers)?.into()), + (RemoteAddr, self.remote_addr.into()), + (State, serde_json::to_value(self.state)?.as_str().into()), + (Status, self.status.into()), + (StatusReason, self.status_reason.into()), + (Url, self.url.into()), + (Version, self.version.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + HttpResponseIden::UpdatedAt, + HttpResponseIden::BodyPath, + HttpResponseIden::ContentLength, + HttpResponseIden::Elapsed, + HttpResponseIden::ElapsedHeaders, + HttpResponseIden::Error, + HttpResponseIden::Headers, + HttpResponseIden::RemoteAddr, + HttpResponseIden::State, + HttpResponseIden::Status, + HttpResponseIden::StatusReason, + HttpResponseIden::Url, + HttpResponseIden::Version, + ] + } + + fn from_row(r: &Row) -> rusqlite::Result + where + Self: Sized, + { let headers: String = r.get("headers")?; let state: String = r.get("state")?; Ok(Self { @@ -885,15 +1194,6 @@ impl<'s> TryFrom<&Row<'s>> for HttpResponse { } } -impl HttpResponse { - pub fn new() -> Self { - Self { - model: "http_response".to_string(), - ..Default::default() - } - } -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] @@ -910,6 +1210,7 @@ pub struct GrpcMetadataEntry { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "grpc_requests")] pub struct GrpcRequest { #[ts(type = "\"grpc_request\"")] pub model: String, @@ -932,51 +1233,82 @@ pub struct GrpcRequest { pub url: String, } -#[derive(Iden)] -pub enum GrpcRequestIden { - #[iden = "grpc_requests"] - Table, - Id, - Model, - CreatedAt, - UpdatedAt, - WorkspaceId, - FolderId, +impl UpsertModelInfo for GrpcRequest { + fn table_name() -> impl IntoTableRef { + GrpcRequestIden::Table + } - Authentication, - AuthenticationType, - Description, - Message, - Metadata, - Method, - Name, - Service, - SortPriority, - Url, -} + fn id_column() -> impl IntoIden + Eq + Clone { + GrpcRequestIden::Id + } -impl<'s> TryFrom<&Row<'s>> for GrpcRequest { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { - let authentication: String = r.get("authentication")?; - let metadata: String = r.get("metadata")?; + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use GrpcRequestIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (Name, self.name.trim().into()), + (Description, self.description.into()), + (WorkspaceId, self.workspace_id.into()), + (FolderId, self.folder_id.into()), + (SortPriority, self.sort_priority.into()), + (Url, self.url.into()), + (Service, self.service.into()), + (Method, self.method.into()), + (Message, self.message.into()), + (AuthenticationType, self.authentication_type.into()), + (Authentication, serde_json::to_string(&self.authentication)?.into()), + (Metadata, serde_json::to_string(&self.metadata)?.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + GrpcRequestIden::UpdatedAt, + GrpcRequestIden::WorkspaceId, + GrpcRequestIden::Name, + GrpcRequestIden::Description, + GrpcRequestIden::FolderId, + GrpcRequestIden::SortPriority, + GrpcRequestIden::Url, + GrpcRequestIden::Service, + GrpcRequestIden::Method, + GrpcRequestIden::Message, + GrpcRequestIden::AuthenticationType, + GrpcRequestIden::Authentication, + GrpcRequestIden::Metadata, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { + let authentication: String = row.get("authentication")?; + let metadata: String = row.get("metadata")?; Ok(Self { - id: r.get("id")?, - model: r.get("model")?, - workspace_id: r.get("workspace_id")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - folder_id: r.get("folder_id")?, - name: r.get("name")?, - description: r.get("description")?, - service: r.get("service")?, - method: r.get("method")?, - message: r.get("message")?, - authentication_type: r.get("authentication_type")?, + id: row.get("id")?, + model: row.get("model")?, + workspace_id: row.get("workspace_id")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + folder_id: row.get("folder_id")?, + name: row.get("name")?, + description: row.get("description")?, + service: row.get("service")?, + method: row.get("method")?, + message: row.get("message")?, + authentication_type: row.get("authentication_type")?, authentication: serde_json::from_str(authentication.as_str()).unwrap_or_default(), - url: r.get("url")?, - sort_priority: r.get("sort_priority")?, + url: row.get("url")?, + sort_priority: row.get("sort_priority")?, metadata: serde_json::from_str(metadata.as_str()).unwrap_or_default(), }) } @@ -1000,6 +1332,7 @@ impl Default for GrpcConnectionState { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "grpc_connections")] pub struct GrpcConnection { #[ts(type = "\"grpc_connection\"")] pub model: String, @@ -1019,47 +1352,74 @@ pub struct GrpcConnection { pub url: String, } -#[derive(Iden)] -pub enum GrpcConnectionIden { - #[iden = "grpc_connections"] - Table, - Model, - Id, - CreatedAt, - UpdatedAt, - WorkspaceId, - RequestId, +impl UpsertModelInfo for GrpcConnection { + fn table_name() -> impl IntoTableRef { + GrpcConnectionIden::Table + } - Elapsed, - Error, - Method, - Service, - State, - Status, - Trailers, - Url, -} + fn id_column() -> impl IntoIden + Eq + Clone { + GrpcConnectionIden::Id + } -impl<'s> TryFrom<&Row<'s>> for GrpcConnection { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { - let trailers: String = r.get("trailers")?; - let state: String = r.get("state")?; + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use GrpcConnectionIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (WorkspaceId, self.workspace_id.into()), + (RequestId, self.request_id.into()), + (Service, self.service.into()), + (Method, self.method.into()), + (Elapsed, self.elapsed.into()), + (State, serde_json::to_value(&self.state)?.as_str().into()), + (Status, self.status.into()), + (Error, self.error.as_ref().map(|s| s.as_str()).into()), + (Trailers, serde_json::to_string(&self.trailers)?.into()), + (Url, self.url.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + GrpcConnectionIden::UpdatedAt, + GrpcConnectionIden::Service, + GrpcConnectionIden::Method, + GrpcConnectionIden::Elapsed, + GrpcConnectionIden::Status, + GrpcConnectionIden::State, + GrpcConnectionIden::Error, + GrpcConnectionIden::Trailers, + GrpcConnectionIden::Url, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { + let trailers: String = row.get("trailers")?; + let state: String = row.get("state")?; Ok(Self { - id: r.get("id")?, - model: r.get("model")?, - workspace_id: r.get("workspace_id")?, - request_id: r.get("request_id")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - service: r.get("service")?, - method: r.get("method")?, - elapsed: r.get("elapsed")?, + id: row.get("id")?, + model: row.get("model")?, + workspace_id: row.get("workspace_id")?, + request_id: row.get("request_id")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + service: row.get("service")?, + method: row.get("method")?, + elapsed: row.get("elapsed")?, state: serde_json::from_str(format!(r#""{state}""#).as_str()).unwrap(), - status: r.get("status")?, - url: r.get("url")?, - error: r.get("error")?, + status: row.get("status")?, + url: row.get("url")?, + error: row.get("error")?, trailers: serde_json::from_str(trailers.as_str()).unwrap_or_default(), }) } @@ -1086,6 +1446,7 @@ impl Default for GrpcEventType { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "grpc_events")] pub struct GrpcEvent { #[ts(type = "\"grpc_event\"")] pub model: String, @@ -1103,44 +1464,68 @@ pub struct GrpcEvent { pub status: Option, } -#[derive(Iden)] -pub enum GrpcEventIden { - #[iden = "grpc_events"] - Table, - Model, - Id, - CreatedAt, - UpdatedAt, - WorkspaceId, - RequestId, - ConnectionId, +impl UpsertModelInfo for GrpcEvent { + fn table_name() -> impl IntoTableRef { + GrpcEventIden::Table + } - Content, - Error, - EventType, - Metadata, - Status, -} + fn id_column() -> impl IntoIden + Eq + Clone { + GrpcEventIden::Id + } -impl<'s> TryFrom<&Row<'s>> for GrpcEvent { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { - let event_type: String = r.get("event_type")?; - let metadata: String = r.get("metadata")?; + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use GrpcEventIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (WorkspaceId, self.workspace_id.into()), + (RequestId, self.request_id.into()), + (ConnectionId, self.connection_id.into()), + (Content, self.content.into()), + (EventType, serde_json::to_string(&self.event_type)?.into()), + (Metadata, serde_json::to_string(&self.metadata)?.into()), + (Status, self.status.into()), + (Error, self.error.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + GrpcEventIden::UpdatedAt, + GrpcEventIden::Content, + GrpcEventIden::EventType, + GrpcEventIden::Metadata, + GrpcEventIden::Status, + GrpcEventIden::Error, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { + let event_type: String = row.get("event_type")?; + let metadata: String = row.get("metadata")?; Ok(Self { - id: r.get("id")?, - model: r.get("model")?, - workspace_id: r.get("workspace_id")?, - request_id: r.get("request_id")?, - connection_id: r.get("connection_id")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - content: r.get("content")?, + id: row.get("id")?, + model: row.get("model")?, + workspace_id: row.get("workspace_id")?, + request_id: row.get("request_id")?, + connection_id: row.get("connection_id")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + content: row.get("content")?, event_type: serde_json::from_str(event_type.as_str()).unwrap_or_default(), metadata: serde_json::from_str(metadata.as_str()).unwrap_or_default(), - status: r.get("status")?, - error: r.get("error")?, + status: row.get("status")?, + error: row.get("error")?, }) } } @@ -1148,6 +1533,7 @@ impl<'s> TryFrom<&Row<'s>> for GrpcEvent { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "plugins")] pub struct Plugin { #[ts(type = "\"plugin\"")] pub model: String, @@ -1161,34 +1547,57 @@ pub struct Plugin { pub url: Option, } -#[derive(Iden)] -pub enum PluginIden { - #[iden = "plugins"] - Table, - Model, - Id, - CreatedAt, - UpdatedAt, +impl UpsertModelInfo for Plugin { + fn table_name() -> impl IntoTableRef { + PluginIden::Table + } - CheckedAt, - Directory, - Enabled, - Url, -} + fn id_column() -> impl IntoIden + Eq + Clone { + PluginIden::Id + } -impl<'s> TryFrom<&Row<'s>> for Plugin { - type Error = rusqlite::Error; + fn get_id(&self) -> String { + self.id.clone() + } - fn try_from(r: &Row<'s>) -> Result { + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use PluginIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (CheckedAt, self.checked_at.into()), + (Directory, self.directory.into()), + (Url, self.url.into()), + (Enabled, self.enabled.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + PluginIden::UpdatedAt, + PluginIden::CheckedAt, + PluginIden::Directory, + PluginIden::Url, + PluginIden::Enabled, + ] + } + + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { Ok(Self { - id: r.get("id")?, - model: r.get("model")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - checked_at: r.get("checked_at")?, - url: r.get("url")?, - directory: r.get("directory")?, - enabled: r.get("enabled")?, + id: row.get("id")?, + model: row.get("model")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + checked_at: row.get("checked_at")?, + url: row.get("url")?, + directory: row.get("directory")?, + enabled: row.get("enabled")?, }) } } @@ -1196,6 +1605,7 @@ impl<'s> TryFrom<&Row<'s>> for Plugin { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "sync_states")] pub struct SyncState { #[ts(type = "\"sync_state\"")] pub model: String, @@ -1211,54 +1621,61 @@ pub struct SyncState { pub sync_dir: String, } -#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] -#[serde(default, rename_all = "camelCase")] -#[ts(export, export_to = "gen_models.ts")] -pub struct SyncHistory { - #[ts(type = "\"sync_history\"")] - pub model: String, - pub id: String, - pub workspace_id: String, - pub created_at: NaiveDateTime, +impl UpsertModelInfo for SyncState { + fn table_name() -> impl IntoTableRef { + SyncStateIden::Table + } - pub states: Vec, - pub checksum: String, - pub rel_path: String, - pub sync_dir: String, -} + fn id_column() -> impl IntoIden + Eq + Clone { + SyncStateIden::Id + } -#[derive(Iden)] -pub enum SyncStateIden { - #[iden = "sync_states"] - Table, - Model, - Id, - WorkspaceId, - CreatedAt, - UpdatedAt, + fn get_id(&self) -> String { + self.id.clone() + } - Checksum, - FlushedAt, - ModelId, - RelPath, - SyncDir, -} + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use SyncStateIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (WorkspaceId, self.workspace_id.into()), + (FlushedAt, self.flushed_at.into()), + (Checksum, self.checksum.into()), + (ModelId, self.model_id.into()), + (RelPath, self.rel_path.into()), + (SyncDir, self.sync_dir.into()), + ]) + } -impl<'s> TryFrom<&Row<'s>> for SyncState { - type Error = rusqlite::Error; + fn update_columns() -> Vec { + vec![ + SyncStateIden::UpdatedAt, + SyncStateIden::FlushedAt, + SyncStateIden::Checksum, + SyncStateIden::RelPath, + SyncStateIden::SyncDir, + ] + } - fn try_from(r: &Row<'s>) -> Result { + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized, + { Ok(Self { - id: r.get("id")?, - workspace_id: r.get("workspace_id")?, - model: r.get("model")?, - created_at: r.get("created_at")?, - updated_at: r.get("updated_at")?, - flushed_at: r.get("flushed_at")?, - checksum: r.get("checksum")?, - model_id: r.get("model_id")?, - sync_dir: r.get("sync_dir")?, - rel_path: r.get("rel_path")?, + id: row.get("id")?, + workspace_id: row.get("workspace_id")?, + model: row.get("model")?, + created_at: row.get("created_at")?, + updated_at: row.get("updated_at")?, + flushed_at: row.get("flushed_at")?, + checksum: row.get("checksum")?, + model_id: row.get("model_id")?, + sync_dir: row.get("sync_dir")?, + rel_path: row.get("rel_path")?, }) } } @@ -1266,6 +1683,7 @@ impl<'s> TryFrom<&Row<'s>> for SyncState { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "key_values")] pub struct KeyValue { #[ts(type = "\"key_value\"")] pub model: String, @@ -1277,23 +1695,10 @@ pub struct KeyValue { pub value: String, } -#[derive(Iden)] -pub enum KeyValueIden { - #[iden = "key_values"] - Table, - Model, - CreatedAt, - UpdatedAt, - - Key, - Namespace, - Value, -} - impl<'s> TryFrom<&Row<'s>> for KeyValue { type Error = rusqlite::Error; - fn try_from(r: &Row<'s>) -> Result { + fn try_from(r: &Row<'s>) -> std::result::Result { Ok(Self { model: r.get("model")?, created_at: r.get("created_at")?, @@ -1308,6 +1713,7 @@ impl<'s> TryFrom<&Row<'s>> for KeyValue { #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "plugin_key_values")] pub struct PluginKeyValue { #[ts(type = "\"plugin_key_value\"")] pub model: String, @@ -1319,23 +1725,10 @@ pub struct PluginKeyValue { pub value: String, } -#[derive(Iden)] -pub enum PluginKeyValueIden { - #[iden = "plugin_key_values"] - Table, - Model, - CreatedAt, - UpdatedAt, - - PluginName, - Key, - Value, -} - impl<'s> TryFrom<&Row<'s>> for PluginKeyValue { type Error = rusqlite::Error; - fn try_from(r: &Row<'s>) -> Result { + fn try_from(r: &Row<'s>) -> std::result::Result { Ok(Self { model: r.get("model")?, created_at: r.get("created_at")?, @@ -1396,30 +1789,62 @@ impl ModelType { } } -#[derive(Debug, Clone, Serialize, TS)] -#[serde(rename_all = "camelCase", untagged)] -#[ts(export, export_to = "gen_models.ts")] -pub enum AnyModel { - CookieJar(CookieJar), - Environment(Environment), - Folder(Folder), - GrpcConnection(GrpcConnection), - GrpcEvent(GrpcEvent), - GrpcRequest(GrpcRequest), - HttpRequest(HttpRequest), - HttpResponse(HttpResponse), - Plugin(Plugin), - Settings(Settings), - KeyValue(KeyValue), - Workspace(Workspace), - WorkspaceMeta(WorkspaceMeta), - WebsocketConnection(WebsocketConnection), - WebsocketEvent(WebsocketEvent), - WebsocketRequest(WebsocketRequest), +#[macro_export] +macro_rules! define_any_model { + ($($type:ident),* $(,)?) => { + #[derive(Debug, Clone, Serialize, TS)] + #[serde(rename_all = "camelCase", untagged)] + #[ts(export, export_to = "gen_models.ts")] + pub enum AnyModel { + $( + $type($type), + )* + } + + $( + impl From<$type> for AnyModel { + fn from(value: $type) -> Self { + AnyModel::$type(value) + } + } + + impl From for $type { + fn from(value: AnyModel) -> $type { + match value { + AnyModel::$type(inner) => inner, + _ => panic!( // Should never happen because this macro also generates the enum variant + "Tried to convert AnyModel into `{}`, but found a different variant", + stringify!($type) + ), + } + } + } + )* + }; +} + +define_any_model! { + CookieJar, + Environment, + Folder, + GrpcConnection, + GrpcEvent, + GrpcRequest, + HttpRequest, + HttpResponse, + KeyValue, + Plugin, + Settings, + SyncState, + WebsocketConnection, + WebsocketEvent, + WebsocketRequest, + Workspace, + WorkspaceMeta, } impl<'de> Deserialize<'de> for AnyModel { - fn deserialize(deserializer: D) -> Result + fn deserialize(deserializer: D) -> std::result::Result where D: Deserializer<'de>, { @@ -1464,3 +1889,62 @@ impl<'de> Deserialize<'de> for AnyModel { Ok(model) } } + +impl AnyModel { + pub fn resolved_name(&self) -> String { + let compute_name = |name: &str, url: &str, fallback: &str| -> String { + if !name.is_empty() { + return name.to_string(); + } + let without_variables = url.replace(r"\$\{\[\s*([^\]\s]+)\s*]}", "$1"); + if without_variables.is_empty() { + fallback.to_string() + } else { + without_variables + } + }; + + match self.clone() { + AnyModel::CookieJar(v) => v.name, + AnyModel::Environment(v) => v.name, + AnyModel::Folder(v) => v.name, + AnyModel::GrpcRequest(v) => compute_name(&v.name, &v.url, "gRPC Request"), + AnyModel::HttpRequest(v) => compute_name(&v.name, &v.url, "HTTP Request"), + AnyModel::WebsocketRequest(v) => compute_name(&v.name, &v.url, "WebSocket Request"), + AnyModel::Workspace(v) => v.name, + _ => "No Name".to_string(), + } + } +} + +pub trait UpsertModelInfo { + fn table_name() -> impl IntoTableRef; + fn id_column() -> impl IntoIden + Eq + Clone; + fn get_id(&self) -> String; + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>>; + fn update_columns() -> Vec; + fn from_row(row: &Row) -> rusqlite::Result + where + Self: Sized; +} + +// Generate the created_at or updated_at timestamps for an upsert operation, depending on the ID +// provided. +fn upsert_date(update_source: &UpdateSource, dt: NaiveDateTime) -> SimpleExpr { + match update_source { + // Sync and import operations always preserve timestamps + UpdateSource::Sync | UpdateSource::Import => { + if dt.and_utc().timestamp() == 0 { + // Sometimes data won't have timestamps (partial data) + Utc::now().naive_utc().into() + } else { + dt.into() + } + } + // Other sources will always update to the latest time + _ => Utc::now().naive_utc().into(), + } +} diff --git a/src-tauri/yaak-models/src/plugin.rs b/src-tauri/yaak-models/src/plugin.rs deleted file mode 100644 index 8b875a0d..00000000 --- a/src-tauri/yaak-models/src/plugin.rs +++ /dev/null @@ -1,81 +0,0 @@ -use log::info; -use r2d2::Pool; -use r2d2_sqlite::SqliteConnectionManager; -use serde::Deserialize; -use sqlx::migrate::Migrator; -use sqlx::sqlite::SqliteConnectOptions; -use sqlx::SqlitePool; -use std::fs::create_dir_all; -use std::path::PathBuf; -use std::str::FromStr; -use std::time::Duration; -use tauri::async_runtime::Mutex; -use tauri::path::BaseDirectory; -use tauri::plugin::TauriPlugin; -use tauri::{plugin, AppHandle, Manager, Runtime}; - -pub struct SqliteConnection(pub Mutex>); - -#[derive(Default, Deserialize)] -pub struct PluginConfig { - // Nothing yet (will be configurable in tauri.conf.json -} - -/// Tauri SQL plugin builder. -#[derive(Default)] -pub struct Builder { - // Nothing Yet -} - -impl Builder { - pub fn new() -> Self { - Self::default() - } - - pub fn build(&self) -> TauriPlugin> { - plugin::Builder::>::new("yaak_models") - .setup(|app, _api| { - let app_path = app.path().app_data_dir().unwrap(); - create_dir_all(app_path.clone()).expect("Problem creating App directory!"); - - let db_file_path = app_path.join("db.sqlite"); - - { - let db_file_path = db_file_path.clone(); - tauri::async_runtime::block_on(async move { - must_migrate_db(app.app_handle(), &db_file_path).await; - }); - }; - - let manager = SqliteConnectionManager::file(db_file_path); - let pool = Pool::builder() - .max_size(100) // Up from 10 (just in case) - .connection_timeout(Duration::from_secs(10)) // Down from 30 - .build(manager) - .unwrap(); - - app.manage(SqliteConnection(Mutex::new(pool))); - - Ok(()) - }) - .build() - } -} - -async fn must_migrate_db(app_handle: &AppHandle, sqlite_file_path: &PathBuf) { - info!("Connecting to database at {sqlite_file_path:?}"); - let sqlite_file_path = sqlite_file_path.to_str().unwrap().to_string(); - let opts = SqliteConnectOptions::from_str(&sqlite_file_path).unwrap().create_if_missing(true); - let pool = SqlitePool::connect_with(opts).await.expect("Failed to connect to database"); - let p = app_handle - .path() - .resolve("migrations", BaseDirectory::Resource) - .expect("failed to resolve resource"); - - info!("Running database migrations from: {}", p.to_string_lossy()); - let mut m = Migrator::new(p).await.expect("Failed to load migrations"); - m.set_ignore_missing(true); // So we can roll back versions and not crash - m.run(&pool).await.expect("Failed to run migrations"); - - info!("Database migrations complete"); -} diff --git a/src-tauri/yaak-models/src/queries.rs b/src-tauri/yaak-models/src/queries.rs deleted file mode 100644 index 71210bbc..00000000 --- a/src-tauri/yaak-models/src/queries.rs +++ /dev/null @@ -1,2761 +0,0 @@ -use crate::error::Error::ModelNotFound; -use crate::error::Result; -use crate::models::{ - AnyModel, CookieJar, CookieJarIden, Environment, EnvironmentIden, Folder, FolderIden, - GrpcConnection, GrpcConnectionIden, GrpcConnectionState, GrpcEvent, GrpcEventIden, GrpcRequest, - GrpcRequestIden, HttpRequest, HttpRequestIden, HttpResponse, HttpResponseHeader, - HttpResponseIden, HttpResponseState, KeyValue, KeyValueIden, ModelType, Plugin, PluginIden, - PluginKeyValue, PluginKeyValueIden, Settings, SettingsIden, SyncState, SyncStateIden, - WebsocketConnection, WebsocketConnectionIden, WebsocketConnectionState, WebsocketEvent, - WebsocketEventIden, WebsocketRequest, WebsocketRequestIden, Workspace, WorkspaceIden, - WorkspaceMeta, WorkspaceMetaIden, -}; -use crate::plugin::SqliteConnection; -use chrono::{NaiveDateTime, Utc}; -use log::{debug, error, info, warn}; -use nanoid::nanoid; -use rusqlite::OptionalExtension; -use sea_query::ColumnRef::Asterisk; -use sea_query::Keyword::CurrentTimestamp; -use sea_query::{Cond, Expr, OnConflict, Order, Query, SqliteQueryBuilder}; -use sea_query_rusqlite::RusqliteBinder; -use serde::{Deserialize, Serialize}; -use std::fs; -use std::path::Path; -use tauri::{AppHandle, Emitter, Listener, Manager, Runtime, WebviewWindow}; -use ts_rs::TS; - -const MAX_HISTORY_ITEMS: usize = 20; - -pub async fn set_key_value_string( - app_handle: &AppHandle, - namespace: &str, - key: &str, - value: &str, - update_source: &UpdateSource, -) -> (KeyValue, bool) { - let encoded = serde_json::to_string(value); - set_key_value_raw(app_handle, namespace, key, &encoded.unwrap(), update_source).await -} - -pub async fn set_key_value_int( - app_handle: &AppHandle, - namespace: &str, - key: &str, - value: i32, - update_source: &UpdateSource, -) -> (KeyValue, bool) { - let encoded = serde_json::to_string(&value); - set_key_value_raw(app_handle, namespace, key, &encoded.unwrap(), update_source).await -} - -pub async fn get_key_value_string( - app_handle: &AppHandle, - namespace: &str, - key: &str, - default: &str, -) -> String { - match get_key_value_raw(app_handle, namespace, key).await { - None => default.to_string(), - Some(v) => { - let result = serde_json::from_str(&v.value); - match result { - Ok(v) => v, - Err(e) => { - error!("Failed to parse string key value: {}", e); - default.to_string() - } - } - } - } -} - -pub async fn get_key_value_int( - app_handle: &AppHandle, - namespace: &str, - key: &str, - default: i32, -) -> i32 { - match get_key_value_raw(app_handle, namespace, key).await { - None => default.clone(), - Some(v) => { - let result = serde_json::from_str(&v.value); - match result { - Ok(v) => v, - Err(e) => { - error!("Failed to parse int key value: {}", e); - default.clone() - } - } - } - } -} - -pub async fn set_key_value_raw( - app_handle: &AppHandle, - namespace: &str, - key: &str, - value: &str, - update_source: &UpdateSource, -) -> (KeyValue, bool) { - let existing = get_key_value_raw(app_handle, namespace, key).await; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::insert() - .into_table(KeyValueIden::Table) - .columns([ - KeyValueIden::CreatedAt, - KeyValueIden::UpdatedAt, - KeyValueIden::Namespace, - KeyValueIden::Key, - KeyValueIden::Value, - ]) - .values_panic([ - CurrentTimestamp.into(), - CurrentTimestamp.into(), - namespace.into(), - key.into(), - value.into(), - ]) - .on_conflict( - OnConflict::new() - .update_columns([KeyValueIden::UpdatedAt, KeyValueIden::Value]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str()).expect("Failed to prepare KeyValue upsert"); - let m: KeyValue = stmt - .query_row(&*params.as_params(), |row| row.try_into()) - .expect("Failed to upsert KeyValue"); - emit_upserted_model(app_handle, &AnyModel::KeyValue(m.to_owned()), update_source); - (m, existing.is_none()) -} - -pub async fn delete_key_value( - app_handle: &AppHandle, - namespace: &str, - key: &str, - update_source: &UpdateSource, -) { - let kv = match get_key_value_raw(app_handle, namespace, key).await { - None => return, - Some(m) => m, - }; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::delete() - .from_table(KeyValueIden::Table) - .cond_where( - Cond::all() - .add(Expr::col(KeyValueIden::Namespace).eq(namespace)) - .add(Expr::col(KeyValueIden::Key).eq(key)), - ) - .build_rusqlite(SqliteQueryBuilder); - db.execute(sql.as_str(), &*params.as_params()).expect("Failed to delete PluginKeyValue"); - emit_deleted_model(app_handle, &AnyModel::KeyValue(kv.to_owned()), update_source); -} - -pub async fn list_key_values_raw(app_handle: &AppHandle) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(KeyValueIden::Table) - .column(Asterisk) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn get_key_value_raw( - app_handle: &AppHandle, - namespace: &str, - key: &str, -) -> Option { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(KeyValueIden::Table) - .column(Asterisk) - .cond_where( - Cond::all() - .add(Expr::col(KeyValueIden::Namespace).eq(namespace)) - .add(Expr::col(KeyValueIden::Key).eq(key)), - ) - .build_rusqlite(SqliteQueryBuilder); - - db.query_row(sql.as_str(), &*params.as_params(), |row| row.try_into()).ok() -} - -pub async fn get_plugin_key_value( - app_handle: &AppHandle, - plugin_name: &str, - key: &str, -) -> Option { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(PluginKeyValueIden::Table) - .column(Asterisk) - .cond_where( - Cond::all() - .add(Expr::col(PluginKeyValueIden::PluginName).eq(plugin_name)) - .add(Expr::col(PluginKeyValueIden::Key).eq(key)), - ) - .build_rusqlite(SqliteQueryBuilder); - - db.query_row(sql.as_str(), &*params.as_params(), |row| row.try_into()).ok() -} - -pub async fn set_plugin_key_value( - app_handle: &AppHandle, - plugin_name: &str, - key: &str, - value: &str, -) -> (PluginKeyValue, bool) { - let existing = get_plugin_key_value(app_handle, plugin_name, key).await; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::insert() - .into_table(PluginKeyValueIden::Table) - .columns([ - PluginKeyValueIden::CreatedAt, - PluginKeyValueIden::UpdatedAt, - PluginKeyValueIden::PluginName, - PluginKeyValueIden::Key, - PluginKeyValueIden::Value, - ]) - .values_panic([ - CurrentTimestamp.into(), - CurrentTimestamp.into(), - plugin_name.into(), - key.into(), - value.into(), - ]) - .on_conflict( - OnConflict::new() - .update_columns([PluginKeyValueIden::UpdatedAt, PluginKeyValueIden::Value]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str()).expect("Failed to prepare PluginKeyValue upsert"); - let m: PluginKeyValue = stmt - .query_row(&*params.as_params(), |row| row.try_into()) - .expect("Failed to upsert KeyValue"); - (m, existing.is_none()) -} - -pub async fn delete_plugin_key_value( - app_handle: &AppHandle, - plugin_name: &str, - key: &str, -) -> bool { - if let None = get_plugin_key_value(app_handle, plugin_name, key).await { - return false; - } - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::delete() - .from_table(PluginKeyValueIden::Table) - .cond_where( - Cond::all() - .add(Expr::col(PluginKeyValueIden::PluginName).eq(plugin_name)) - .add(Expr::col(PluginKeyValueIden::Key).eq(key)), - ) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str()).unwrap(); - stmt.execute(&*params.as_params()).expect("Failed to delete PluginKeyValue"); - true -} - -pub async fn list_workspaces(app_handle: &AppHandle) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(WorkspaceIden::Table) - .column(Asterisk) - .order_by(WorkspaceIden::Name, Order::Asc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn list_workspace_metas( - app_handle: &AppHandle, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(WorkspaceMetaIden::Table) - .column(Asterisk) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn get_workspace(app_handle: &AppHandle, id: &str) -> Result { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(WorkspaceIden::Table) - .column(Asterisk) - .cond_where(Expr::col(WorkspaceIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) -} - -pub async fn get_workspace_meta( - app_handle: &AppHandle, - workspace: &Workspace, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(WorkspaceMetaIden::Table) - .column(Asterisk) - .cond_where(Expr::col(WorkspaceMetaIden::WorkspaceId).eq(&workspace.id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into()).optional()?) -} - -pub async fn get_or_create_workspace_meta( - app_handle: &AppHandle, - workspace: &Workspace, - update_source: &UpdateSource, -) -> Result { - let workspace_meta = get_workspace_meta(app_handle, workspace).await?; - if let Some(m) = workspace_meta { - return Ok(m); - } - - upsert_workspace_meta( - app_handle, - WorkspaceMeta { - workspace_id: workspace.to_owned().id, - ..Default::default() - }, - update_source, - ) - .await -} - -pub async fn exists_workspace(app_handle: &AppHandle, id: &str) -> Result { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(WorkspaceIden::Table) - .column(Asterisk) - .cond_where(Expr::col(WorkspaceIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.exists(&*params.as_params())?) -} - -pub async fn upsert_workspace( - app_handle: &AppHandle, - workspace: Workspace, - update_source: &UpdateSource, -) -> Result { - let id = match workspace.id.as_str() { - "" => generate_model_id(ModelType::TypeWorkspace), - _ => workspace.id.to_string(), - }; - let trimmed_name = workspace.name.trim(); - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::insert() - .into_table(WorkspaceIden::Table) - .columns([ - WorkspaceIden::Id, - WorkspaceIden::CreatedAt, - WorkspaceIden::UpdatedAt, - WorkspaceIden::Name, - WorkspaceIden::Description, - WorkspaceIden::SettingFollowRedirects, - WorkspaceIden::SettingRequestTimeout, - WorkspaceIden::SettingValidateCertificates, - ]) - .values_panic([ - id.as_str().into(), - timestamp_for_upsert(update_source, workspace.created_at).into(), - timestamp_for_upsert(update_source, workspace.updated_at).into(), - trimmed_name.into(), - workspace.description.into(), - workspace.setting_follow_redirects.into(), - workspace.setting_request_timeout.into(), - workspace.setting_validate_certificates.into(), - ]) - .on_conflict( - OnConflict::column(GrpcRequestIden::Id) - .update_columns([ - WorkspaceIden::UpdatedAt, - WorkspaceIden::Name, - WorkspaceIden::Description, - WorkspaceIden::SettingRequestTimeout, - WorkspaceIden::SettingFollowRedirects, - WorkspaceIden::SettingRequestTimeout, - WorkspaceIden::SettingValidateCertificates, - ]) - .values([(WorkspaceIden::UpdatedAt, CurrentTimestamp.into())]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(&sql)?; - let m: Workspace = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::Workspace(m.to_owned()), update_source); - Ok(m) -} - -pub async fn upsert_workspace_meta( - app_handle: &AppHandle, - workspace_meta: WorkspaceMeta, - update_source: &UpdateSource, -) -> Result { - let id = match workspace_meta.id.as_str() { - "" => generate_model_id(ModelType::TypeWorkspaceMeta), - _ => workspace_meta.id.to_string(), - }; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::insert() - .into_table(WorkspaceMetaIden::Table) - .columns([ - WorkspaceMetaIden::Id, - WorkspaceMetaIden::WorkspaceId, - WorkspaceMetaIden::CreatedAt, - WorkspaceMetaIden::UpdatedAt, - WorkspaceMetaIden::SettingSyncDir, - ]) - .values_panic([ - id.as_str().into(), - workspace_meta.workspace_id.into(), - timestamp_for_upsert(update_source, workspace_meta.created_at).into(), - timestamp_for_upsert(update_source, workspace_meta.updated_at).into(), - workspace_meta.setting_sync_dir.into(), - ]) - .on_conflict( - OnConflict::column(GrpcRequestIden::Id) - .update_columns([ - WorkspaceMetaIden::UpdatedAt, - WorkspaceMetaIden::SettingSyncDir, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(&sql)?; - let m: WorkspaceMeta = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::WorkspaceMeta(m.to_owned()), update_source); - Ok(m) -} - -pub async fn delete_workspace( - app_handle: &AppHandle, - id: &str, - update_source: &UpdateSource, -) -> Result { - let workspace = get_workspace(app_handle, id).await?; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::delete() - .from_table(WorkspaceIden::Table) - .cond_where(Expr::col(WorkspaceIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - db.execute(sql.as_str(), &*params.as_params())?; - - for r in list_responses_by_workspace_id(app_handle, id).await? { - delete_http_response(app_handle, &r.id, update_source).await?; - } - - emit_deleted_model(app_handle, &AnyModel::Workspace(workspace.to_owned()), update_source); - Ok(workspace) -} - -pub async fn get_cookie_jar(app_handle: &AppHandle, id: &str) -> Result { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(CookieJarIden::Table) - .column(Asterisk) - .cond_where(Expr::col(CookieJarIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) -} - -pub async fn list_cookie_jars( - app_handle: &AppHandle, - workspace_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(CookieJarIden::Table) - .column(Asterisk) - .cond_where(Expr::col(CookieJarIden::WorkspaceId).eq(workspace_id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn delete_cookie_jar( - app_handle: &AppHandle, - id: &str, - update_source: &UpdateSource, -) -> Result { - let cookie_jar = get_cookie_jar(app_handle, id).await?; - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::delete() - .from_table(CookieJarIden::Table) - .cond_where(Expr::col(WorkspaceIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - db.execute(sql.as_str(), &*params.as_params())?; - - emit_deleted_model(app_handle, &AnyModel::CookieJar(cookie_jar.to_owned()), update_source); - Ok(cookie_jar) -} - -pub async fn duplicate_grpc_request( - app_handle: &AppHandle, - id: &str, - update_source: &UpdateSource, -) -> Result { - let mut request = match get_grpc_request(app_handle, id).await? { - Some(r) => r, - None => { - return Err(ModelNotFound(id.to_string())); - } - }; - request.sort_priority = request.sort_priority + 0.001; - request.id = "".to_string(); - upsert_grpc_request(app_handle, request, update_source).await -} - -pub async fn delete_grpc_request( - app_handle: &AppHandle, - id: &str, - update_source: &UpdateSource, -) -> Result { - let grpc_request = match get_grpc_request(app_handle, id).await? { - Some(r) => r, - None => { - return Err(ModelNotFound(id.to_string())); - } - }; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::delete() - .from_table(GrpcRequestIden::Table) - .cond_where(Expr::col(GrpcRequestIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - db.execute(sql.as_str(), &*params.as_params())?; - - emit_deleted_model(app_handle, &AnyModel::GrpcRequest(grpc_request.to_owned()), update_source); - Ok(grpc_request) -} - -pub async fn upsert_grpc_request( - app_handle: &AppHandle, - request: GrpcRequest, - update_source: &UpdateSource, -) -> Result { - let id = match request.id.as_str() { - "" => generate_model_id(ModelType::TypeGrpcRequest), - _ => request.id.to_string(), - }; - let trimmed_name = request.name.trim(); - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::insert() - .into_table(GrpcRequestIden::Table) - .columns([ - GrpcRequestIden::Id, - GrpcRequestIden::CreatedAt, - GrpcRequestIden::UpdatedAt, - GrpcRequestIden::Name, - GrpcRequestIden::Description, - GrpcRequestIden::WorkspaceId, - GrpcRequestIden::FolderId, - GrpcRequestIden::SortPriority, - GrpcRequestIden::Url, - GrpcRequestIden::Service, - GrpcRequestIden::Method, - GrpcRequestIden::Message, - GrpcRequestIden::AuthenticationType, - GrpcRequestIden::Authentication, - GrpcRequestIden::Metadata, - ]) - .values_panic([ - id.into(), - timestamp_for_upsert(update_source, request.created_at).into(), - timestamp_for_upsert(update_source, request.updated_at).into(), - trimmed_name.into(), - request.description.into(), - request.workspace_id.into(), - request.folder_id.as_ref().map(|s| s.as_str()).into(), - request.sort_priority.into(), - request.url.into(), - request.service.as_ref().map(|s| s.as_str()).into(), - request.method.as_ref().map(|s| s.as_str()).into(), - request.message.into(), - request.authentication_type.as_ref().map(|s| s.as_str()).into(), - serde_json::to_string(&request.authentication)?.into(), - serde_json::to_string(&request.metadata)?.into(), - ]) - .on_conflict( - OnConflict::column(GrpcRequestIden::Id) - .update_columns([ - GrpcRequestIden::UpdatedAt, - GrpcRequestIden::WorkspaceId, - GrpcRequestIden::Name, - GrpcRequestIden::Description, - GrpcRequestIden::FolderId, - GrpcRequestIden::SortPriority, - GrpcRequestIden::Url, - GrpcRequestIden::Service, - GrpcRequestIden::Method, - GrpcRequestIden::Message, - GrpcRequestIden::AuthenticationType, - GrpcRequestIden::Authentication, - GrpcRequestIden::Metadata, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: GrpcRequest = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::GrpcRequest(m.to_owned()), update_source); - Ok(m) -} - -pub async fn get_grpc_request( - app_handle: &AppHandle, - id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(GrpcRequestIden::Table) - .column(Asterisk) - .cond_where(Expr::col(GrpcRequestIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into()).optional()?) -} - -pub async fn list_grpc_requests( - app_handle: &AppHandle, - workspace_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(GrpcRequestIden::Table) - .cond_where(Expr::col(GrpcRequestIden::WorkspaceId).eq(workspace_id)) - .column(Asterisk) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn upsert_grpc_connection( - app_handle: &AppHandle, - connection: &GrpcConnection, - update_source: &UpdateSource, -) -> Result { - let connections = - list_grpc_connections_for_request(app_handle, connection.request_id.as_str()).await?; - for c in connections.iter().skip(MAX_HISTORY_ITEMS - 1) { - debug!("Deleting old grpc connection {}", c.id); - delete_grpc_connection(app_handle, c.id.as_str(), update_source).await?; - } - - let id = match connection.id.as_str() { - "" => generate_model_id(ModelType::TypeGrpcConnection), - _ => connection.id.to_string(), - }; - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::insert() - .into_table(GrpcConnectionIden::Table) - .columns([ - GrpcConnectionIden::Id, - GrpcConnectionIden::CreatedAt, - GrpcConnectionIden::UpdatedAt, - GrpcConnectionIden::WorkspaceId, - GrpcConnectionIden::RequestId, - GrpcConnectionIden::Service, - GrpcConnectionIden::Method, - GrpcConnectionIden::Elapsed, - GrpcConnectionIden::State, - GrpcConnectionIden::Status, - GrpcConnectionIden::Error, - GrpcConnectionIden::Trailers, - GrpcConnectionIden::Url, - ]) - .values_panic([ - id.as_str().into(), - timestamp_for_upsert(update_source, connection.created_at).into(), - timestamp_for_upsert(update_source, connection.updated_at).into(), - connection.workspace_id.as_str().into(), - connection.request_id.as_str().into(), - connection.service.as_str().into(), - connection.method.as_str().into(), - connection.elapsed.into(), - serde_json::to_value(&connection.state)?.as_str().into(), - connection.status.into(), - connection.error.as_ref().map(|s| s.as_str()).into(), - serde_json::to_string(&connection.trailers)?.into(), - connection.url.as_str().into(), - ]) - .on_conflict( - OnConflict::column(GrpcConnectionIden::Id) - .update_columns([ - GrpcConnectionIden::UpdatedAt, - GrpcConnectionIden::Service, - GrpcConnectionIden::Method, - GrpcConnectionIden::Elapsed, - GrpcConnectionIden::Status, - GrpcConnectionIden::State, - GrpcConnectionIden::Error, - GrpcConnectionIden::Trailers, - GrpcConnectionIden::Url, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: GrpcConnection = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::GrpcConnection(m.to_owned()), update_source); - Ok(m) -} - -pub async fn get_grpc_connection( - app_handle: &AppHandle, - id: &str, -) -> Result { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(GrpcConnectionIden::Table) - .column(Asterisk) - .cond_where(Expr::col(GrpcConnectionIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) -} - -pub async fn list_grpc_connections_for_workspace( - app_handle: &AppHandle, - workspace_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(GrpcConnectionIden::Table) - .cond_where(Expr::col(GrpcConnectionIden::WorkspaceId).eq(workspace_id)) - .column(Asterisk) - .order_by(GrpcConnectionIden::CreatedAt, Order::Desc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn list_grpc_connections_for_request( - app_handle: &AppHandle, - request_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(GrpcConnectionIden::Table) - .cond_where(Expr::col(GrpcConnectionIden::RequestId).eq(request_id)) - .column(Asterisk) - .order_by(GrpcConnectionIden::CreatedAt, Order::Desc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn delete_grpc_connection( - app_handle: &AppHandle, - id: &str, - update_source: &UpdateSource, -) -> Result { - let m = get_grpc_connection(app_handle, id).await?; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::delete() - .from_table(GrpcConnectionIden::Table) - .cond_where(Expr::col(GrpcConnectionIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - - db.execute(sql.as_str(), &*params.as_params())?; - emit_deleted_model(app_handle, &AnyModel::GrpcConnection(m.to_owned()), update_source); - Ok(m) -} - -pub async fn delete_all_grpc_connections( - app_handle: &AppHandle, - request_id: &str, - update_source: &UpdateSource, -) -> Result<()> { - for r in list_grpc_connections_for_request(app_handle, request_id).await? { - delete_grpc_connection(app_handle, &r.id, update_source).await?; - } - Ok(()) -} - -pub async fn delete_all_grpc_connections_for_workspace( - app_handle: &AppHandle, - workspace_id: &str, - update_source: &UpdateSource, -) -> Result<()> { - for r in list_grpc_connections_for_workspace(app_handle, workspace_id).await? { - delete_grpc_connection(app_handle, &r.id, update_source).await?; - } - Ok(()) -} - -pub async fn upsert_grpc_event( - app_handle: &AppHandle, - event: &GrpcEvent, - update_source: &UpdateSource, -) -> Result { - let id = match event.id.as_str() { - "" => generate_model_id(ModelType::TypeGrpcEvent), - _ => event.id.to_string(), - }; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::insert() - .into_table(GrpcEventIden::Table) - .columns([ - GrpcEventIden::Id, - GrpcEventIden::CreatedAt, - GrpcEventIden::UpdatedAt, - GrpcEventIden::WorkspaceId, - GrpcEventIden::RequestId, - GrpcEventIden::ConnectionId, - GrpcEventIden::Content, - GrpcEventIden::EventType, - GrpcEventIden::Metadata, - GrpcEventIden::Status, - GrpcEventIden::Error, - ]) - .values_panic([ - id.as_str().into(), - timestamp_for_upsert(update_source, event.created_at).into(), - timestamp_for_upsert(update_source, event.updated_at).into(), - event.workspace_id.as_str().into(), - event.request_id.as_str().into(), - event.connection_id.as_str().into(), - event.content.as_str().into(), - serde_json::to_string(&event.event_type)?.into(), - serde_json::to_string(&event.metadata)?.into(), - event.status.into(), - event.error.as_ref().map(|s| s.as_str()).into(), - ]) - .on_conflict( - OnConflict::column(GrpcEventIden::Id) - .update_columns([ - GrpcEventIden::UpdatedAt, - GrpcEventIden::Content, - GrpcEventIden::EventType, - GrpcEventIden::Metadata, - GrpcEventIden::Status, - GrpcEventIden::Error, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: GrpcEvent = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::GrpcEvent(m.to_owned()), update_source); - Ok(m) -} - -pub async fn get_grpc_event(app_handle: &AppHandle, id: &str) -> Result { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(GrpcEventIden::Table) - .column(Asterisk) - .cond_where(Expr::col(GrpcEventIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) -} - -pub async fn list_grpc_events( - app_handle: &AppHandle, - connection_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(GrpcEventIden::Table) - .cond_where(Expr::col(GrpcEventIden::ConnectionId).eq(connection_id)) - .column(Asterisk) - .order_by(GrpcEventIden::CreatedAt, Order::Asc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn delete_websocket_request( - app_handle: &AppHandle, - id: &str, - update_source: &UpdateSource, -) -> Result { - let request = match get_websocket_request(app_handle, id).await? { - Some(r) => r, - None => { - return Err(ModelNotFound(id.to_string())); - } - }; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::delete() - .from_table(WebsocketRequestIden::Table) - .cond_where(Expr::col(WebsocketRequestIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - db.execute(sql.as_str(), &*params.as_params())?; - - emit_deleted_model(app_handle, &AnyModel::WebsocketRequest(request.to_owned()), update_source); - Ok(request) -} - -pub async fn delete_websocket_connection( - app_handle: &AppHandle, - id: &str, - update_source: &UpdateSource, -) -> Result { - let m = get_websocket_connection(app_handle, id).await?; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::delete() - .from_table(WebsocketConnectionIden::Table) - .cond_where(Expr::col(WebsocketConnectionIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - db.execute(sql.as_str(), &*params.as_params())?; - - emit_deleted_model(app_handle, &AnyModel::WebsocketConnection(m.to_owned()), update_source); - Ok(m) -} - -pub async fn delete_all_websocket_connections( - app_handle: &AppHandle, - request_id: &str, - update_source: &UpdateSource, -) -> Result<()> { - for c in list_websocket_connections_for_request(app_handle, request_id).await? { - delete_websocket_connection(app_handle, &c.id, update_source).await?; - } - Ok(()) -} - -pub async fn delete_all_websocket_connections_for_workspace( - app_handle: &AppHandle, - workspace_id: &str, - update_source: &UpdateSource, -) -> Result<()> { - for c in list_websocket_connections_for_workspace(app_handle, workspace_id).await? { - delete_websocket_connection(app_handle, &c.id, update_source).await?; - } - Ok(()) -} - -pub async fn get_websocket_connection( - app_handle: &AppHandle, - id: &str, -) -> Result { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(WebsocketConnectionIden::Table) - .column(Asterisk) - .cond_where(Expr::col(WebsocketConnectionIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) -} - -pub async fn upsert_websocket_event( - app_handle: &AppHandle, - event: WebsocketEvent, - update_source: &UpdateSource, -) -> Result { - let id = match event.id.as_str() { - "" => generate_model_id(ModelType::TypeWebSocketEvent), - _ => event.id.to_string(), - }; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::insert() - .into_table(WebsocketEventIden::Table) - .columns([ - WebsocketEventIden::Id, - WebsocketEventIden::CreatedAt, - WebsocketEventIden::UpdatedAt, - WebsocketEventIden::WorkspaceId, - WebsocketEventIden::ConnectionId, - WebsocketEventIden::RequestId, - WebsocketEventIden::MessageType, - WebsocketEventIden::IsServer, - WebsocketEventIden::Message, - ]) - .values_panic([ - id.into(), - timestamp_for_upsert(update_source, event.created_at).into(), - timestamp_for_upsert(update_source, event.updated_at).into(), - event.workspace_id.into(), - event.connection_id.into(), - event.request_id.into(), - serde_json::to_string(&event.message_type)?.into(), - event.is_server.into(), - event.message.into(), - ]) - .on_conflict( - OnConflict::column(WebsocketEventIden::Id) - .update_columns([ - WebsocketEventIden::UpdatedAt, - WebsocketEventIden::MessageType, - WebsocketEventIden::IsServer, - WebsocketEventIden::Message, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: WebsocketEvent = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::WebsocketEvent(m.to_owned()), update_source); - Ok(m) -} - -pub async fn duplicate_websocket_request( - app_handle: &AppHandle, - id: &str, - update_source: &UpdateSource, -) -> Result { - let mut request = match get_websocket_request(app_handle, id).await? { - None => return Err(ModelNotFound(id.to_string())), - Some(r) => r, - }; - request.id = "".to_string(); - request.sort_priority = request.sort_priority + 0.001; - upsert_websocket_request(app_handle, request, update_source).await -} - -pub async fn upsert_websocket_request( - app_handle: &AppHandle, - request: WebsocketRequest, - update_source: &UpdateSource, -) -> Result { - let id = match request.id.as_str() { - "" => generate_model_id(ModelType::TypeWebsocketRequest), - _ => request.id.to_string(), - }; - let trimmed_name = request.name.trim(); - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::insert() - .into_table(WebsocketRequestIden::Table) - .columns([ - WebsocketRequestIden::Id, - WebsocketRequestIden::CreatedAt, - WebsocketRequestIden::UpdatedAt, - WebsocketRequestIden::WorkspaceId, - WebsocketRequestIden::FolderId, - WebsocketRequestIden::Authentication, - WebsocketRequestIden::AuthenticationType, - WebsocketRequestIden::Description, - WebsocketRequestIden::Headers, - WebsocketRequestIden::Message, - WebsocketRequestIden::Name, - WebsocketRequestIden::SortPriority, - WebsocketRequestIden::Url, - WebsocketRequestIden::UrlParameters, - ]) - .values_panic([ - id.into(), - timestamp_for_upsert(update_source, request.created_at).into(), - timestamp_for_upsert(update_source, request.updated_at).into(), - request.workspace_id.into(), - request.folder_id.as_ref().map(|s| s.as_str()).into(), - serde_json::to_string(&request.authentication)?.into(), - request.authentication_type.as_ref().map(|s| s.as_str()).into(), - request.description.into(), - serde_json::to_string(&request.headers)?.into(), - request.message.into(), - trimmed_name.into(), - request.sort_priority.into(), - request.url.into(), - serde_json::to_string(&request.url_parameters)?.into(), - ]) - .on_conflict( - OnConflict::column(WebsocketRequestIden::Id) - .update_columns([ - WebsocketRequestIden::UpdatedAt, - WebsocketRequestIden::WorkspaceId, - WebsocketRequestIden::FolderId, - WebsocketRequestIden::Authentication, - WebsocketRequestIden::AuthenticationType, - WebsocketRequestIden::Description, - WebsocketRequestIden::Headers, - WebsocketRequestIden::Message, - WebsocketRequestIden::Name, - WebsocketRequestIden::SortPriority, - WebsocketRequestIden::Url, - WebsocketRequestIden::UrlParameters, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: WebsocketRequest = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::WebsocketRequest(m.to_owned()), update_source); - Ok(m) -} - -pub async fn list_websocket_connections_for_workspace( - app_handle: &AppHandle, - workspace_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(WebsocketConnectionIden::Table) - .cond_where(Expr::col(WebsocketConnectionIden::WorkspaceId).eq(workspace_id)) - .column(Asterisk) - .order_by(WebsocketConnectionIden::CreatedAt, Order::Desc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn list_websocket_connections_for_request( - app_handle: &AppHandle, - request_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(WebsocketConnectionIden::Table) - .cond_where(Expr::col(WebsocketConnectionIden::RequestId).eq(request_id)) - .column(Asterisk) - .order_by(WebsocketConnectionIden::CreatedAt, Order::Desc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn upsert_websocket_connection( - app_handle: &AppHandle, - connection: &WebsocketConnection, - update_source: &UpdateSource, -) -> Result { - let connections = - list_websocket_connections_for_request(app_handle, connection.request_id.as_str()).await?; - for c in connections.iter().skip(MAX_HISTORY_ITEMS - 1) { - debug!("Deleting old websocket connection {}", c.id); - delete_websocket_connection(app_handle, c.id.as_str(), update_source).await?; - } - - let id = match connection.id.as_str() { - "" => generate_model_id(ModelType::TypeWebSocketConnection), - _ => connection.id.to_string(), - }; - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::insert() - .into_table(WebsocketConnectionIden::Table) - .columns([ - WebsocketConnectionIden::Id, - WebsocketConnectionIden::CreatedAt, - WebsocketConnectionIden::UpdatedAt, - WebsocketConnectionIden::WorkspaceId, - WebsocketConnectionIden::RequestId, - WebsocketConnectionIden::Elapsed, - WebsocketConnectionIden::Error, - WebsocketConnectionIden::Headers, - WebsocketConnectionIden::State, - WebsocketConnectionIden::Status, - WebsocketConnectionIden::Url, - ]) - .values_panic([ - id.as_str().into(), - timestamp_for_upsert(update_source, connection.created_at).into(), - timestamp_for_upsert(update_source, connection.updated_at).into(), - connection.workspace_id.as_str().into(), - connection.request_id.as_str().into(), - connection.elapsed.into(), - connection.error.as_ref().map(|s| s.as_str()).into(), - serde_json::to_string(&connection.headers)?.into(), - serde_json::to_value(&connection.state)?.as_str().into(), - connection.status.into(), - connection.url.as_str().into(), - ]) - .on_conflict( - OnConflict::column(WebsocketConnectionIden::Id) - .update_columns([ - WebsocketConnectionIden::UpdatedAt, - WebsocketConnectionIden::Elapsed, - WebsocketConnectionIden::Error, - WebsocketConnectionIden::Headers, - WebsocketConnectionIden::State, - WebsocketConnectionIden::Status, - WebsocketConnectionIden::Url, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: WebsocketConnection = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::WebsocketConnection(m.to_owned()), update_source); - Ok(m) -} - -pub async fn get_websocket_request( - app_handle: &AppHandle, - id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(WebsocketRequestIden::Table) - .column(Asterisk) - .cond_where(Expr::col(WebsocketRequestIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into()).optional()?) -} - -pub async fn list_websocket_requests( - app_handle: &AppHandle, - workspace_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(WebsocketRequestIden::Table) - .cond_where(Expr::col(WebsocketRequestIden::WorkspaceId).eq(workspace_id)) - .column(Asterisk) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn list_websocket_events( - app_handle: &AppHandle, - connection_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(WebsocketEventIden::Table) - .cond_where(Expr::col(WebsocketEventIden::ConnectionId).eq(connection_id)) - .column(Asterisk) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn upsert_cookie_jar( - app_handle: &AppHandle, - cookie_jar: &CookieJar, - update_source: &UpdateSource, -) -> Result { - let id = match cookie_jar.id.as_str() { - "" => generate_model_id(ModelType::TypeCookieJar), - _ => cookie_jar.id.to_string(), - }; - let trimmed_name = cookie_jar.name.trim(); - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::insert() - .into_table(CookieJarIden::Table) - .columns([ - CookieJarIden::Id, - CookieJarIden::CreatedAt, - CookieJarIden::UpdatedAt, - CookieJarIden::WorkspaceId, - CookieJarIden::Name, - CookieJarIden::Cookies, - ]) - .values_panic([ - id.as_str().into(), - timestamp_for_upsert(update_source, cookie_jar.created_at).into(), - timestamp_for_upsert(update_source, cookie_jar.updated_at).into(), - cookie_jar.workspace_id.as_str().into(), - trimmed_name.into(), - serde_json::to_string(&cookie_jar.cookies)?.into(), - ]) - .on_conflict( - OnConflict::column(GrpcEventIden::Id) - .update_columns([ - CookieJarIden::UpdatedAt, - CookieJarIden::Name, - CookieJarIden::Cookies, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: CookieJar = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::CookieJar(m.to_owned()), update_source); - Ok(m) -} - -pub async fn ensure_base_environment( - app_handle: &AppHandle, - workspace_id: &str, -) -> Result<()> { - let environments = list_environments(app_handle, workspace_id).await?; - let base_environment = - environments.iter().find(|e| e.environment_id == None && e.workspace_id == workspace_id); - - if let None = base_environment { - info!("Creating base environment for {workspace_id}"); - upsert_environment( - app_handle, - Environment { - workspace_id: workspace_id.to_string(), - name: "Global Variables".to_string(), - ..Default::default() - }, - &UpdateSource::Background, - ) - .await?; - } - - Ok(()) -} - -pub async fn list_environments( - app_handle: &AppHandle, - workspace_id: &str, -) -> Result> { - let environments: Vec = { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(EnvironmentIden::Table) - .cond_where(Expr::col(EnvironmentIden::WorkspaceId).eq(workspace_id)) - .column(Asterisk) - .order_by(EnvironmentIden::Name, Order::Asc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - items.map(|v| v.unwrap()).collect() - }; - - Ok(environments) -} - -pub async fn delete_environment( - app_handle: &AppHandle, - id: &str, - update_source: &UpdateSource, -) -> Result { - let env = get_environment(app_handle, id).await?; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::delete() - .from_table(EnvironmentIden::Table) - .cond_where(Expr::col(EnvironmentIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - - db.execute(sql.as_str(), &*params.as_params())?; - - emit_deleted_model(app_handle, &AnyModel::Environment(env.to_owned()), update_source); - Ok(env) -} - -const SETTINGS_ID: &str = "default"; - -async fn get_settings(app_handle: &AppHandle) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(SettingsIden::Table) - .column(Asterisk) - .cond_where(Expr::col(SettingsIden::Id).eq(SETTINGS_ID)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into()).optional()?) -} - -pub async fn get_or_create_settings(app_handle: &AppHandle) -> Settings { - match get_settings(app_handle).await { - Ok(Some(settings)) => return settings, - Err(e) => panic!("Failed to get settings {e:?}"), - Ok(None) => {} - }; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::insert() - .into_table(SettingsIden::Table) - .columns([SettingsIden::Id]) - .values_panic([SETTINGS_ID.into()]) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str()).expect("Failed to prepare Settings insert"); - stmt.query_row(&*params.as_params(), |row| row.try_into()).expect("Failed to insert Settings") -} - -pub async fn update_settings( - app_handle: &AppHandle, - settings: Settings, - update_source: &UpdateSource, -) -> Result { - // Correct for the bug where created_at was being updated by mistake - let created_at = if settings.created_at > settings.updated_at { - settings.updated_at - } else { - settings.created_at - }; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::update() - .table(SettingsIden::Table) - .cond_where(Expr::col(SettingsIden::Id).eq("default")) - .values([ - (SettingsIden::Id, "default".into()), - (SettingsIden::CreatedAt, created_at.into()), - (SettingsIden::UpdatedAt, CurrentTimestamp.into()), - (SettingsIden::Appearance, settings.appearance.as_str().into()), - (SettingsIden::ThemeDark, settings.theme_dark.as_str().into()), - (SettingsIden::ThemeLight, settings.theme_light.as_str().into()), - (SettingsIden::UpdateChannel, settings.update_channel.into()), - (SettingsIden::InterfaceFontSize, settings.interface_font_size.into()), - (SettingsIden::InterfaceScale, settings.interface_scale.into()), - (SettingsIden::EditorFontSize, settings.editor_font_size.into()), - (SettingsIden::EditorKeymap, settings.editor_keymap.to_string().into()), - (SettingsIden::EditorSoftWrap, settings.editor_soft_wrap.into()), - (SettingsIden::OpenWorkspaceNewWindow, settings.open_workspace_new_window.into()), - ( - SettingsIden::Proxy, - (match settings.proxy { - None => None, - Some(p) => Some(serde_json::to_string(&p)?), - }) - .into(), - ), - ]) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: Settings = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::Settings(m.to_owned()), update_source); - Ok(m) -} - -pub async fn upsert_environment( - app_handle: &AppHandle, - environment: Environment, - update_source: &UpdateSource, -) -> Result { - let id = match environment.id.as_str() { - "" => generate_model_id(ModelType::TypeEnvironment), - _ => environment.id.to_string(), - }; - let trimmed_name = environment.name.trim(); - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::insert() - .into_table(EnvironmentIden::Table) - .columns([ - EnvironmentIden::Id, - EnvironmentIden::CreatedAt, - EnvironmentIden::UpdatedAt, - EnvironmentIden::EnvironmentId, - EnvironmentIden::WorkspaceId, - EnvironmentIden::Name, - EnvironmentIden::Variables, - ]) - .values_panic([ - id.as_str().into(), - timestamp_for_upsert(update_source, environment.created_at).into(), - timestamp_for_upsert(update_source, environment.updated_at).into(), - environment.environment_id.into(), - environment.workspace_id.into(), - trimmed_name.into(), - serde_json::to_string(&environment.variables)?.into(), - ]) - .on_conflict( - OnConflict::column(EnvironmentIden::Id) - .update_columns([ - EnvironmentIden::UpdatedAt, - EnvironmentIden::Name, - EnvironmentIden::Variables, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: Environment = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::Environment(m.to_owned()), update_source); - Ok(m) -} - -pub async fn get_environment( - app_handle: &AppHandle, - id: &str, -) -> Result { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(EnvironmentIden::Table) - .column(Asterisk) - .cond_where(Expr::col(EnvironmentIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) -} - -pub async fn get_base_environment( - app_handle: &AppHandle, - workspace_id: &str, -) -> Result { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(EnvironmentIden::Table) - .column(Asterisk) - .cond_where( - Cond::all() - .add(Expr::col(EnvironmentIden::WorkspaceId).eq(workspace_id)) - .add(Expr::col(EnvironmentIden::EnvironmentId).is_null()), - ) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) -} - -pub async fn get_plugin(app_handle: &AppHandle, id: &str) -> Result { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(PluginIden::Table) - .column(Asterisk) - .cond_where(Expr::col(EnvironmentIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) -} - -pub async fn list_plugins(app_handle: &AppHandle) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(PluginIden::Table) - .column(Asterisk) - .order_by(PluginIden::CreatedAt, Order::Desc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn upsert_plugin( - app_handle: &AppHandle, - plugin: Plugin, - update_source: &UpdateSource, -) -> Result { - let id = match plugin.id.as_str() { - "" => generate_model_id(ModelType::TypePlugin), - _ => plugin.id.to_string(), - }; - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::insert() - .into_table(PluginIden::Table) - .columns([ - PluginIden::Id, - PluginIden::CreatedAt, - PluginIden::UpdatedAt, - PluginIden::CheckedAt, - PluginIden::Directory, - PluginIden::Url, - PluginIden::Enabled, - ]) - .values_panic([ - id.as_str().into(), - timestamp_for_upsert(update_source, plugin.created_at).into(), - timestamp_for_upsert(update_source, plugin.updated_at).into(), - plugin.checked_at.into(), - plugin.directory.into(), - plugin.url.into(), - plugin.enabled.into(), - ]) - .on_conflict( - OnConflict::column(PluginIden::Id) - .update_columns([ - PluginIden::UpdatedAt, - PluginIden::CheckedAt, - PluginIden::Directory, - PluginIden::Url, - PluginIden::Enabled, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: Plugin = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::Plugin(m.to_owned()), update_source); - Ok(m) -} - -pub async fn delete_plugin( - app_handle: &AppHandle, - id: &str, - - update_source: &UpdateSource, -) -> Result { - let plugin = get_plugin(app_handle, id).await?; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::delete() - .from_table(PluginIden::Table) - .cond_where(Expr::col(PluginIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - db.execute(sql.as_str(), &*params.as_params())?; - - emit_deleted_model(app_handle, &AnyModel::Plugin(plugin.to_owned()), update_source); - Ok(plugin) -} - -pub async fn get_folder(app_handle: &AppHandle, id: &str) -> Result { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(FolderIden::Table) - .column(Asterisk) - .cond_where(Expr::col(FolderIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) -} - -pub async fn list_folders( - app_handle: &AppHandle, - workspace_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(FolderIden::Table) - .cond_where(Expr::col(FolderIden::WorkspaceId).eq(workspace_id)) - .column(Asterisk) - .order_by(FolderIden::CreatedAt, Order::Desc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn delete_folder( - app_handle: &AppHandle, - id: &str, - - update_source: &UpdateSource, -) -> Result { - let folder = get_folder(app_handle, id).await?; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::delete() - .from_table(FolderIden::Table) - .cond_where(Expr::col(FolderIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - db.execute(sql.as_str(), &*params.as_params())?; - - emit_deleted_model(app_handle, &AnyModel::Folder(folder.to_owned()), update_source); - Ok(folder) -} - -pub async fn upsert_folder( - app_handle: &AppHandle, - folder: Folder, - - update_source: &UpdateSource, -) -> Result { - let id = match folder.id.as_str() { - "" => generate_model_id(ModelType::TypeFolder), - _ => folder.id.to_string(), - }; - let trimmed_name = folder.name.trim(); - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::insert() - .into_table(FolderIden::Table) - .columns([ - FolderIden::Id, - FolderIden::CreatedAt, - FolderIden::UpdatedAt, - FolderIden::WorkspaceId, - FolderIden::FolderId, - FolderIden::Name, - FolderIden::Description, - FolderIden::SortPriority, - ]) - .values_panic([ - id.as_str().into(), - timestamp_for_upsert(update_source, folder.created_at).into(), - timestamp_for_upsert(update_source, folder.updated_at).into(), - folder.workspace_id.as_str().into(), - folder.folder_id.as_ref().map(|s| s.as_str()).into(), - trimmed_name.into(), - folder.description.into(), - folder.sort_priority.into(), - ]) - .on_conflict( - OnConflict::column(GrpcEventIden::Id) - .update_columns([ - FolderIden::UpdatedAt, - FolderIden::Name, - FolderIden::Description, - FolderIden::FolderId, - FolderIden::SortPriority, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: Folder = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::Folder(m.to_owned()), update_source); - Ok(m) -} - -pub async fn duplicate_http_request( - app_handle: &AppHandle, - id: &str, - update_source: &UpdateSource, -) -> Result { - let mut request = match get_http_request(app_handle, id).await? { - None => return Err(ModelNotFound(id.to_string())), - Some(r) => r, - }; - request.id = "".to_string(); - request.sort_priority = request.sort_priority + 0.001; - upsert_http_request(app_handle, request, update_source).await -} - -pub async fn duplicate_folder( - app_handle: &AppHandle, - src_folder: &Folder, - update_source: &UpdateSource, -) -> Result<()> { - let workspace_id = src_folder.workspace_id.as_str(); - - let http_requests = list_http_requests(app_handle, workspace_id) - .await? - .into_iter() - .filter(|m| m.folder_id.as_ref() == Some(&src_folder.id)); - - let grpc_requests = list_grpc_requests(app_handle, workspace_id) - .await? - .into_iter() - .filter(|m| m.folder_id.as_ref() == Some(&src_folder.id)); - - let folders = list_folders(app_handle, workspace_id) - .await? - .into_iter() - .filter(|m| m.folder_id.as_ref() == Some(&src_folder.id)); - - let new_folder = upsert_folder( - app_handle, - Folder { - id: "".into(), - sort_priority: src_folder.sort_priority + 0.001, - ..src_folder.clone() - }, - update_source, - ) - .await?; - - for m in http_requests { - upsert_http_request( - app_handle, - HttpRequest { - id: "".into(), - folder_id: Some(new_folder.id.clone()), - sort_priority: m.sort_priority + 0.001, - ..m - }, - update_source, - ) - .await?; - } - for m in grpc_requests { - upsert_grpc_request( - app_handle, - GrpcRequest { - id: "".into(), - folder_id: Some(new_folder.id.clone()), - sort_priority: m.sort_priority + 0.001, - ..m - }, - update_source, - ) - .await?; - } - for m in folders { - // Recurse down - Box::pin(duplicate_folder( - app_handle, - &Folder { - folder_id: Some(new_folder.id.clone()), - ..m - }, - update_source, - )) - .await?; - } - Ok(()) -} - -pub async fn upsert_http_request( - app_handle: &AppHandle, - request: HttpRequest, - update_source: &UpdateSource, -) -> Result { - let id = match request.id.as_str() { - "" => generate_model_id(ModelType::TypeHttpRequest), - _ => request.id.to_string(), - }; - let trimmed_name = request.name.trim(); - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::insert() - .into_table(HttpRequestIden::Table) - .columns([ - HttpRequestIden::Id, - HttpRequestIden::CreatedAt, - HttpRequestIden::UpdatedAt, - HttpRequestIden::WorkspaceId, - HttpRequestIden::FolderId, - HttpRequestIden::Name, - HttpRequestIden::Description, - HttpRequestIden::Url, - HttpRequestIden::UrlParameters, - HttpRequestIden::Method, - HttpRequestIden::Body, - HttpRequestIden::BodyType, - HttpRequestIden::Authentication, - HttpRequestIden::AuthenticationType, - HttpRequestIden::Headers, - HttpRequestIden::SortPriority, - ]) - .values_panic([ - id.as_str().into(), - timestamp_for_upsert(update_source, request.created_at).into(), - timestamp_for_upsert(update_source, request.updated_at).into(), - request.workspace_id.into(), - request.folder_id.as_ref().map(|s| s.as_str()).into(), - trimmed_name.into(), - request.description.into(), - request.url.into(), - serde_json::to_string(&request.url_parameters)?.into(), - request.method.into(), - serde_json::to_string(&request.body)?.into(), - request.body_type.as_ref().map(|s| s.as_str()).into(), - serde_json::to_string(&request.authentication)?.into(), - request.authentication_type.as_ref().map(|s| s.as_str()).into(), - serde_json::to_string(&request.headers)?.into(), - request.sort_priority.into(), - ]) - .on_conflict( - OnConflict::column(GrpcEventIden::Id) - .update_columns([ - HttpRequestIden::UpdatedAt, - HttpRequestIden::WorkspaceId, - HttpRequestIden::Name, - HttpRequestIden::Description, - HttpRequestIden::FolderId, - HttpRequestIden::Method, - HttpRequestIden::Headers, - HttpRequestIden::Body, - HttpRequestIden::BodyType, - HttpRequestIden::Authentication, - HttpRequestIden::AuthenticationType, - HttpRequestIden::Url, - HttpRequestIden::UrlParameters, - HttpRequestIden::SortPriority, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: HttpRequest = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::HttpRequest(m.to_owned()), update_source); - Ok(m) -} - -pub async fn list_http_requests( - app_handle: &AppHandle, - workspace_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(HttpRequestIden::Table) - .cond_where(Expr::col(HttpRequestIden::WorkspaceId).eq(workspace_id)) - .column(Asterisk) - .order_by(HttpRequestIden::CreatedAt, Order::Desc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn get_http_request( - app_handle: &AppHandle, - id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::select() - .from(HttpRequestIden::Table) - .column(Asterisk) - .cond_where(Expr::col(HttpRequestIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into()).optional()?) -} - -pub async fn delete_http_request( - app_handle: &AppHandle, - id: &str, - update_source: &UpdateSource, -) -> Result { - let req = match get_http_request(app_handle, id).await? { - None => return Err(ModelNotFound(id.to_string())), - Some(r) => r, - }; - - // DB deletes will cascade but this will delete the files - delete_all_http_responses_for_request(app_handle, id, update_source).await?; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::delete() - .from_table(HttpRequestIden::Table) - .cond_where(Expr::col(HttpRequestIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - db.execute(sql.as_str(), &*params.as_params())?; - - emit_deleted_model(app_handle, &AnyModel::HttpRequest(req.to_owned()), update_source); - Ok(req) -} - -pub async fn create_default_http_response( - app_handle: &AppHandle, - request_id: &str, - update_source: &UpdateSource, -) -> Result { - create_http_response( - &app_handle, - request_id, - 0, - 0, - "", - HttpResponseState::Initialized, - 0, - None, - None, - None, - vec![], - None, - None, - update_source, - ) - .await -} - -#[allow(clippy::too_many_arguments)] -pub async fn create_http_response( - app_handle: &AppHandle, - request_id: &str, - elapsed: i64, - elapsed_headers: i64, - url: &str, - state: HttpResponseState, - status: i64, - status_reason: Option<&str>, - content_length: Option, - body_path: Option<&str>, - headers: Vec, - version: Option<&str>, - remote_addr: Option<&str>, - update_source: &UpdateSource, -) -> Result { - let responses = list_http_responses_for_request(app_handle, request_id, None).await?; - for response in responses.iter().skip(MAX_HISTORY_ITEMS - 1) { - debug!("Deleting old response {}", response.id); - delete_http_response(app_handle, response.id.as_str(), update_source).await?; - } - - let req = match get_http_request(app_handle, request_id).await? { - None => return Err(ModelNotFound(request_id.to_string())), - Some(r) => r, - }; - let id = generate_model_id(ModelType::TypeHttpResponse); - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::insert() - .into_table(HttpResponseIden::Table) - .columns([ - HttpResponseIden::Id, - HttpResponseIden::CreatedAt, - HttpResponseIden::UpdatedAt, - HttpResponseIden::RequestId, - HttpResponseIden::WorkspaceId, - HttpResponseIden::Elapsed, - HttpResponseIden::ElapsedHeaders, - HttpResponseIden::Url, - HttpResponseIden::State, - HttpResponseIden::Status, - HttpResponseIden::StatusReason, - HttpResponseIden::ContentLength, - HttpResponseIden::BodyPath, - HttpResponseIden::Headers, - HttpResponseIden::Version, - HttpResponseIden::RemoteAddr, - ]) - .values_panic([ - id.as_str().into(), - CurrentTimestamp.into(), - CurrentTimestamp.into(), - req.id.as_str().into(), - req.workspace_id.as_str().into(), - elapsed.into(), - elapsed_headers.into(), - url.into(), - serde_json::to_value(state)?.as_str().unwrap_or_default().into(), - status.into(), - status_reason.into(), - content_length.into(), - body_path.into(), - serde_json::to_string(&headers)?.into(), - version.into(), - remote_addr.into(), - ]) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: HttpResponse = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::HttpResponse(m.to_owned()), update_source); - Ok(m) -} - -pub async fn cancel_pending_websocket_connections( - app_handle: &AppHandle, -) -> Result<()> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let closed = serde_json::to_value(&WebsocketConnectionState::Closed)?; - let (sql, params) = Query::update() - .table(WebsocketConnectionIden::Table) - .values([(WebsocketConnectionIden::State, closed.as_str().into())]) - .cond_where(Expr::col(WebsocketConnectionIden::State).ne(closed.as_str())) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - stmt.execute(&*params.as_params())?; - Ok(()) -} - -pub async fn cancel_pending_grpc_connections(app: &AppHandle) -> Result<()> { - let dbm = &*app.app_handle().state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let closed = serde_json::to_value(&GrpcConnectionState::Closed)?; - let (sql, params) = Query::update() - .table(GrpcConnectionIden::Table) - .values([(GrpcConnectionIden::State, closed.as_str().into())]) - .cond_where(Expr::col(GrpcConnectionIden::State).ne(closed.as_str())) - .build_rusqlite(SqliteQueryBuilder); - - db.execute(sql.as_str(), &*params.as_params())?; - Ok(()) -} - -pub async fn cancel_pending_http_responses(app: &AppHandle) -> Result<()> { - let dbm = &*app.app_handle().state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let closed = serde_json::to_value(&GrpcConnectionState::Closed)?; - let (sql, params) = Query::update() - .table(HttpResponseIden::Table) - .values([ - (HttpResponseIden::State, closed.as_str().into()), - (HttpResponseIden::StatusReason, "Cancelled".into()), - ]) - .cond_where(Expr::col(HttpResponseIden::State).ne(closed.as_str())) - .build_rusqlite(SqliteQueryBuilder); - - db.execute(sql.as_str(), &*params.as_params())?; - Ok(()) -} - -pub async fn update_response_if_id( - app_handle: &AppHandle, - response: &HttpResponse, - update_source: &UpdateSource, -) -> Result { - if response.id.is_empty() { - Ok(response.clone()) - } else { - update_http_response(app_handle, response, update_source).await - } -} - -pub async fn update_http_response( - app_handle: &AppHandle, - response: &HttpResponse, - update_source: &UpdateSource, -) -> Result { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::update() - .table(HttpResponseIden::Table) - .cond_where(Expr::col(HttpResponseIden::Id).eq(response.clone().id)) - .values([ - (HttpResponseIden::UpdatedAt, CurrentTimestamp.into()), - (HttpResponseIden::Elapsed, response.elapsed.into()), - (HttpResponseIden::Url, response.url.as_str().into()), - (HttpResponseIden::Status, response.status.into()), - ( - HttpResponseIden::StatusReason, - response.status_reason.as_ref().map(|s| s.as_str()).into(), - ), - (HttpResponseIden::ContentLength, response.content_length.into()), - (HttpResponseIden::BodyPath, response.body_path.as_ref().map(|s| s.as_str()).into()), - (HttpResponseIden::Error, response.error.as_ref().map(|s| s.as_str()).into()), - ( - HttpResponseIden::Headers, - serde_json::to_string(&response.headers).unwrap_or_default().into(), - ), - (HttpResponseIden::Version, response.version.as_ref().map(|s| s.as_str()).into()), - (HttpResponseIden::State, serde_json::to_value(&response.state)?.as_str().into()), - ( - HttpResponseIden::RemoteAddr, - response.remote_addr.as_ref().map(|s| s.as_str()).into(), - ), - ]) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: HttpResponse = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - emit_upserted_model(app_handle, &AnyModel::HttpResponse(m.to_owned()), update_source); - Ok(m) -} - -pub async fn get_http_response( - app_handle: &AppHandle, - id: &str, -) -> Result { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(HttpResponseIden::Table) - .column(Asterisk) - .cond_where(Expr::col(HttpResponseIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) -} - -pub async fn delete_http_response( - app_handle: &AppHandle, - id: &str, - update_source: &UpdateSource, -) -> Result { - let resp = get_http_response(app_handle, id).await?; - - // Delete the body file if it exists - if let Some(p) = resp.body_path.clone() { - if let Err(e) = fs::remove_file(p) { - error!("Failed to delete body file: {}", e); - }; - } - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::delete() - .from_table(HttpResponseIden::Table) - .cond_where(Expr::col(HttpResponseIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - db.execute(sql.as_str(), &*params.as_params())?; - - emit_deleted_model(app_handle, &AnyModel::HttpResponse(resp.to_owned()), update_source); - Ok(resp) -} - -pub async fn delete_all_http_responses_for_request( - app_handle: &AppHandle, - request_id: &str, - update_source: &UpdateSource, -) -> Result<()> { - for r in list_http_responses_for_request(app_handle, request_id, None).await? { - delete_http_response(app_handle, &r.id, update_source).await?; - } - Ok(()) -} - -pub async fn delete_all_http_responses_for_workspace( - app_handle: &AppHandle, - workspace_id: &str, - update_source: &UpdateSource, -) -> Result<()> { - for r in list_http_responses_for_workspace(app_handle, workspace_id, None).await? { - delete_http_response(app_handle, &r.id, update_source).await?; - } - Ok(()) -} - -pub async fn list_http_responses_for_workspace( - app_handle: &AppHandle, - workspace_id: &str, - limit: Option, -) -> Result> { - let limit_unwrapped = limit.unwrap_or_else(|| i64::MAX); - let dbm = app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(HttpResponseIden::Table) - .cond_where(Expr::col(HttpResponseIden::WorkspaceId).eq(workspace_id)) - .column(Asterisk) - .order_by(HttpResponseIden::CreatedAt, Order::Desc) - .limit(limit_unwrapped as u64) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn list_http_responses_for_request( - app_handle: &AppHandle, - request_id: &str, - limit: Option, -) -> Result> { - let limit_unwrapped = limit.unwrap_or_else(|| i64::MAX); - let dbm = app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(HttpResponseIden::Table) - .cond_where(Expr::col(HttpResponseIden::RequestId).eq(request_id)) - .column(Asterisk) - .order_by(HttpResponseIden::CreatedAt, Order::Desc) - .limit(limit_unwrapped as u64) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn list_responses_by_workspace_id( - app_handle: &AppHandle, - workspace_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(HttpResponseIden::Table) - .cond_where(Expr::col(HttpResponseIden::WorkspaceId).eq(workspace_id)) - .column(Asterisk) - .order_by(HttpResponseIden::CreatedAt, Order::Desc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn get_sync_state_for_model( - app_handle: &AppHandle, - workspace_id: &str, - model_id: &str, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(SyncStateIden::Table) - .column(Asterisk) - .cond_where( - Cond::all() - .add(Expr::col(SyncStateIden::ModelId).eq(model_id)) - .add(Expr::col(SyncStateIden::WorkspaceId).eq(workspace_id)), - ) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - Ok(stmt.query_row(&*params.as_params(), |row| row.try_into()).optional()?) -} - -pub async fn list_sync_states_for_workspace( - app_handle: &AppHandle, - workspace_id: &str, - sync_dir: &Path, -) -> Result> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - let (sql, params) = Query::select() - .from(SyncStateIden::Table) - .column(Asterisk) - .cond_where( - Cond::all() - .add(Expr::col(SyncStateIden::WorkspaceId).eq(workspace_id)) - .add(Expr::col(SyncStateIden::SyncDir).eq(sync_dir.to_string_lossy())), - ) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) -} - -pub async fn upsert_sync_state( - app_handle: &AppHandle, - sync_state: SyncState, -) -> Result { - let id = match sync_state.id.as_str() { - "" => generate_model_id(ModelType::TypeSyncState), - _ => sync_state.id.to_string(), - }; - - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::insert() - .into_table(SyncStateIden::Table) - .columns([ - SyncStateIden::Id, - SyncStateIden::WorkspaceId, - SyncStateIden::CreatedAt, - SyncStateIden::UpdatedAt, - SyncStateIden::FlushedAt, - SyncStateIden::Checksum, - SyncStateIden::ModelId, - SyncStateIden::RelPath, - SyncStateIden::SyncDir, - ]) - .values_panic([ - id.as_str().into(), - sync_state.workspace_id.into(), - CurrentTimestamp.into(), - CurrentTimestamp.into(), - sync_state.flushed_at.into(), - sync_state.checksum.into(), - sync_state.model_id.into(), - sync_state.rel_path.into(), - sync_state.sync_dir.into(), - ]) - .on_conflict( - OnConflict::columns(vec![SyncStateIden::WorkspaceId, SyncStateIden::ModelId]) - .update_columns([ - SyncStateIden::UpdatedAt, - SyncStateIden::FlushedAt, - SyncStateIden::Checksum, - SyncStateIden::RelPath, - SyncStateIden::SyncDir, - ]) - .to_owned(), - ) - .returning_all() - .build_rusqlite(SqliteQueryBuilder); - - let mut stmt = db.prepare(sql.as_str())?; - let m: SyncState = stmt.query_row(&*params.as_params(), |row| row.try_into())?; - Ok(m) -} - -pub async fn delete_sync_state(app_handle: &AppHandle, id: &str) -> Result<()> { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await.get().unwrap(); - - let (sql, params) = Query::delete() - .from_table(SyncStateIden::Table) - .cond_where(Expr::col(SyncStateIden::Id).eq(id)) - .build_rusqlite(SqliteQueryBuilder); - db.execute(sql.as_str(), &*params.as_params())?; - Ok(()) -} - -pub async fn debug_pool(app_handle: &AppHandle) { - let dbm = &*app_handle.state::(); - let db = dbm.0.lock().await; - debug!("Debug database state: {:?}", db.state()); -} - -pub fn generate_model_id(model: ModelType) -> String { - let id = generate_id(); - format!("{}_{}", model.id_prefix(), id) -} - -pub fn generate_id() -> String { - let alphabet: [char; 57] = [ - '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', - 'k', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', - 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', - 'X', 'Y', 'Z', - ]; - - nanoid!(10, &alphabet) -} - -#[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export, export_to = "gen_models.ts")] -pub struct ModelPayload { - pub model: AnyModel, - pub update_source: UpdateSource, -} - -#[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[serde(rename_all = "snake_case", tag = "type")] -#[ts(export, export_to = "gen_models.ts")] -pub enum UpdateSource { - Sync, - Window { label: String }, - Plugin, - Background, - Import, -} - -impl UpdateSource { - pub fn from_window(window: &WebviewWindow) -> Self { - Self::Window { - label: window.label().to_string(), - } - } -} - -fn emit_upserted_model( - app_handle: &AppHandle, - model: &AnyModel, - update_source: &UpdateSource, -) { - let payload = ModelPayload { - model: model.to_owned(), - update_source: update_source.to_owned(), - }; - - app_handle.emit("upserted_model", payload).unwrap(); -} - -fn emit_deleted_model( - app_handle: &AppHandle, - model: &AnyModel, - update_source: &UpdateSource, -) { - let payload = ModelPayload { - model: model.to_owned(), - update_source: update_source.to_owned(), - }; - app_handle.emit("deleted_model", payload).unwrap(); -} - -pub fn listen_to_model_delete(app_handle: &AppHandle, handler: F) -where - F: Fn(ModelPayload) + Send + 'static, - R: Runtime, -{ - app_handle.listen_any("deleted_model", move |e| { - match serde_json::from_str(e.payload()) { - Ok(payload) => handler(payload), - Err(e) => { - warn!("Failed to deserialize deleted model {}", e); - return; - } - }; - }); -} - -pub fn listen_to_model_upsert(app_handle: &AppHandle, handler: F) -where - F: Fn(ModelPayload) + Send + 'static, - R: Runtime, -{ - app_handle.listen_any("upserted_model", move |e| { - match serde_json::from_str(e.payload()) { - Ok(payload) => handler(payload), - Err(e) => { - warn!("Failed to deserialize upserted model {}", e); - return; - } - }; - }); -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(default, rename_all = "camelCase")] -pub struct WorkspaceExport { - pub yaak_version: String, - pub yaak_schema: i64, - pub timestamp: NaiveDateTime, - pub resources: BatchUpsertResult, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -#[serde(default, rename_all = "camelCase")] -pub struct BatchUpsertResult { - pub workspaces: Vec, - pub environments: Vec, - pub folders: Vec, - pub http_requests: Vec, - pub grpc_requests: Vec, - pub websocket_requests: Vec, -} - -pub async fn batch_upsert( - app_handle: &AppHandle, - workspaces: Vec, - environments: Vec, - folders: Vec, - http_requests: Vec, - grpc_requests: Vec, - websocket_requests: Vec, - update_source: &UpdateSource, -) -> Result { - let mut imported_resources = BatchUpsertResult::default(); - - if workspaces.len() > 0 { - info!("Batch inserting {} workspaces", workspaces.len()); - for v in workspaces { - let x = upsert_workspace(&app_handle, v, update_source).await?; - imported_resources.workspaces.push(x.clone()); - } - } - - if environments.len() > 0 { - while imported_resources.environments.len() < environments.len() { - for v in environments.clone() { - if let Some(id) = v.environment_id.clone() { - let has_parent_to_import = environments.iter().find(|m| m.id == id).is_some(); - let imported_parent = - imported_resources.environments.iter().find(|m| m.id == id); - // If there's also a parent to upsert, wait for that one - if imported_parent.is_none() && has_parent_to_import { - continue; - } - } - if let Some(_) = imported_resources.environments.iter().find(|f| f.id == v.id) { - continue; - } - let x = upsert_environment(&app_handle, v, update_source).await?; - imported_resources.environments.push(x.clone()); - } - } - info!("Imported {} environments", imported_resources.environments.len()); - } - - if folders.len() > 0 { - while imported_resources.folders.len() < folders.len() { - for v in folders.clone() { - if let Some(id) = v.folder_id.clone() { - let has_parent_to_import = folders.iter().find(|m| m.id == id).is_some(); - let imported_parent = imported_resources.folders.iter().find(|m| m.id == id); - // If there's also a parent to upsert, wait for that one - if imported_parent.is_none() && has_parent_to_import { - continue; - } - } - if let Some(_) = imported_resources.folders.iter().find(|f| f.id == v.id) { - continue; - } - let x = upsert_folder(&app_handle, v, update_source).await?; - imported_resources.folders.push(x.clone()); - } - } - info!("Imported {} folders", imported_resources.folders.len()); - } - - if http_requests.len() > 0 { - for v in http_requests { - let x = upsert_http_request(&app_handle, v, update_source).await?; - imported_resources.http_requests.push(x.clone()); - } - info!("Imported {} http_requests", imported_resources.http_requests.len()); - } - - if grpc_requests.len() > 0 { - for v in grpc_requests { - let x = upsert_grpc_request(&app_handle, v, update_source).await?; - imported_resources.grpc_requests.push(x.clone()); - } - info!("Imported {} grpc_requests", imported_resources.grpc_requests.len()); - } - - if websocket_requests.len() > 0 { - for v in websocket_requests { - let x = upsert_websocket_request(&app_handle, v, update_source).await?; - imported_resources.websocket_requests.push(x.clone()); - } - info!("Imported {} websocket_requests", imported_resources.websocket_requests.len()); - } - - Ok(imported_resources) -} - -pub async fn get_workspace_export_resources( - app_handle: &AppHandle, - workspace_ids: Vec<&str>, - include_environments: bool, -) -> Result { - let mut data = WorkspaceExport { - yaak_version: app_handle.package_info().version.clone().to_string(), - yaak_schema: 3, - timestamp: Utc::now().naive_utc(), - resources: BatchUpsertResult { - workspaces: Vec::new(), - environments: Vec::new(), - folders: Vec::new(), - http_requests: Vec::new(), - grpc_requests: Vec::new(), - websocket_requests: Vec::new(), - }, - }; - - for workspace_id in workspace_ids { - data.resources.workspaces.push(get_workspace(app_handle, workspace_id).await?); - data.resources.environments.append(&mut list_environments(app_handle, workspace_id).await?); - data.resources.folders.append(&mut list_folders(app_handle, workspace_id).await?); - data.resources - .http_requests - .append(&mut list_http_requests(app_handle, workspace_id).await?); - data.resources - .grpc_requests - .append(&mut list_grpc_requests(app_handle, workspace_id).await?); - data.resources - .websocket_requests - .append(&mut list_websocket_requests(app_handle, workspace_id).await?); - } - - // Nuke environments if we don't want them - if !include_environments { - data.resources.environments.clear(); - } - - Ok(data) -} - -// Generate the created_at or updated_at timestamps for an upsert operation, depending on the ID -// provided. -fn timestamp_for_upsert(update_source: &UpdateSource, dt: NaiveDateTime) -> NaiveDateTime { - match update_source { - // Sync and import operations always preserve timestamps - UpdateSource::Sync | UpdateSource::Import => { - if dt.and_utc().timestamp() == 0 { - // Sometimes data won't have timestamps (partial data) - Utc::now().naive_utc() - } else { - dt - } - } - // Other sources will always update to the latest time - _ => Utc::now().naive_utc(), - } -} diff --git a/src-tauri/yaak-models/src/queries/base.rs b/src-tauri/yaak-models/src/queries/base.rs new file mode 100644 index 00000000..c6df8ae7 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/base.rs @@ -0,0 +1,169 @@ +use crate::error::Error::RowNotFound; +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{AnyModel, ModelType, UpsertModelInfo}; +use crate::queries_legacy::{generate_model_id, ModelChangeEvent, ModelPayload, UpdateSource}; +use rusqlite::OptionalExtension; +use sea_query::{ + Asterisk, Expr, IntoColumnRef, IntoIden, IntoTableRef, OnConflict, Query, SimpleExpr, + SqliteQueryBuilder, +}; +use sea_query_rusqlite::RusqliteBinder; + +pub(crate) const MAX_HISTORY_ITEMS: usize = 20; + +impl<'a> DbContext<'a> { + pub(crate) fn find_one<'s, M>( + &self, + col: impl IntoColumnRef, + value: impl Into, + ) -> Result + where + M: Into + Clone + UpsertModelInfo, + { + match self.find_optional::(col, value) { + Ok(Some(v)) => Ok(v), + Ok(None) => Err(RowNotFound), + Err(e) => Err(e), + } + } + + pub fn find_optional<'s, M>( + &self, + col: impl IntoColumnRef, + value: impl Into, + ) -> Result> + where + M: Into + Clone + UpsertModelInfo, + { + let (sql, params) = Query::select() + .from(M::table_name()) + .column(Asterisk) + .cond_where(Expr::col(col).eq(value)) + .build_rusqlite(SqliteQueryBuilder); + let mut stmt = self.conn.prepare(sql.as_str())?; + Ok(stmt.query_row(&*params.as_params(), M::from_row).optional()?) + } + + pub fn find_all<'s, M>(&self) -> Result> + where + M: Into + Clone + UpsertModelInfo, + { + let (sql, params) = Query::select() + .from(M::table_name()) + .column(Asterisk) + .build_rusqlite(SqliteQueryBuilder); + let mut stmt = self.conn.resolve().prepare(sql.as_str())?; + let items = stmt.query_map(&*params.as_params(), M::from_row)?; + Ok(items.map(|v| v.unwrap()).collect()) + } + + pub fn find_many<'s, M>( + &self, + col: impl IntoColumnRef, + value: impl Into, + limit: Option, + ) -> Result> + where + M: Into + Clone + UpsertModelInfo, + { + // TODO: Figure out how to do this conditional builder better + let (sql, params) = if let Some(limit) = limit { + Query::select() + .from(M::table_name()) + .column(Asterisk) + .cond_where(Expr::col(col).eq(value)) + .limit(limit) + .build_rusqlite(SqliteQueryBuilder) + } else { + Query::select() + .from(M::table_name()) + .column(Asterisk) + .cond_where(Expr::col(col).eq(value)) + .build_rusqlite(SqliteQueryBuilder) + }; + + let mut stmt = self.conn.resolve().prepare(sql.as_str())?; + let items = stmt.query_map(&*params.as_params(), M::from_row)?; + Ok(items.map(|v| v.unwrap()).collect()) + } + + pub fn upsert(&self, model: &M, source: &UpdateSource) -> Result + where + M: Into + From + UpsertModelInfo + Clone, + { + self.upsert_one( + M::table_name(), + M::id_column(), + model.get_id().as_str(), + || generate_model_id(ModelType::TypeEnvironment), + model.clone().insert_values(source)?, + M::update_columns(), + source, + ) + } + + fn upsert_one( + &self, + table: impl IntoTableRef, + id_col: impl IntoIden + Eq + Clone, + id_val: &str, + gen_id: fn() -> String, + other_values: Vec<(impl IntoIden + Eq, impl Into)>, + update_columns: Vec, + source: &UpdateSource, + ) -> Result + where + M: Into + From + UpsertModelInfo + Clone, + { + let id_iden = id_col.into_iden(); + let mut column_vec = vec![id_iden.clone()]; + let mut value_vec = vec![if id_val == "" { gen_id().into() } else { id_val.into() }]; + + for (col, val) in other_values { + value_vec.push(val.into()); + column_vec.push(col.into_iden()); + } + + let on_conflict = OnConflict::column(id_iden).update_columns(update_columns).to_owned(); + let (sql, params) = Query::insert() + .into_table(table) + .columns(column_vec) + .values_panic(value_vec) + .on_conflict(on_conflict) + .returning_all() + .build_rusqlite(SqliteQueryBuilder); + + let mut stmt = self.conn.resolve().prepare(sql.as_str())?; + let m: M = stmt.query_row(&*params.as_params(), |row| M::from_row(row))?; + + let payload = ModelPayload { + model: m.clone().into(), + update_source: source.clone(), + change: ModelChangeEvent::Upsert, + }; + self.tx.try_send(payload).unwrap(); + + Ok(m) + } + + pub(crate) fn delete<'s, M>(&self, m: &M, update_source: &UpdateSource) -> Result + where + M: Into + Clone + UpsertModelInfo, + { + let (sql, params) = Query::delete() + .from_table(M::table_name()) + .cond_where(Expr::col(M::id_column().into_iden()).eq(m.get_id())) + .build_rusqlite(SqliteQueryBuilder); + self.conn.execute(sql.as_str(), &*params.as_params())?; + + let payload = ModelPayload { + model: m.clone().into(), + update_source: update_source.clone(), + change: ModelChangeEvent::Delete, + }; + + self.tx.try_send(payload).unwrap(); + Ok(m.clone()) + } +} diff --git a/src-tauri/yaak-models/src/queries/batch.rs b/src-tauri/yaak-models/src/queries/batch.rs new file mode 100644 index 00000000..92135b8f --- /dev/null +++ b/src-tauri/yaak-models/src/queries/batch.rs @@ -0,0 +1,99 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{Environment, Folder, GrpcRequest, HttpRequest, WebsocketRequest, Workspace}; +use crate::queries_legacy::{BatchUpsertResult, UpdateSource}; +use log::info; + +impl<'a> DbContext<'a> { + pub fn batch_upsert( + &self, + workspaces: Vec, + environments: Vec, + folders: Vec, + http_requests: Vec, + grpc_requests: Vec, + websocket_requests: Vec, + source: &UpdateSource, + ) -> Result { + let mut imported_resources = BatchUpsertResult::default(); + + if workspaces.len() > 0 { + info!("Batch inserting {} workspaces", workspaces.len()); + for v in workspaces { + let x = self.upsert_workspace(&v, source)?; + imported_resources.workspaces.push(x.clone()); + } + } + + if environments.len() > 0 { + while imported_resources.environments.len() < environments.len() { + for v in environments.clone() { + if let Some(id) = v.environment_id.clone() { + let has_parent_to_import = + environments.iter().find(|m| m.id == id).is_some(); + let imported_parent = + imported_resources.environments.iter().find(|m| m.id == id); + // If there's also a parent to upsert, wait for that one + if imported_parent.is_none() && has_parent_to_import { + continue; + } + } + if let Some(_) = imported_resources.environments.iter().find(|f| f.id == v.id) { + continue; + } + let x = self.upsert_environment(&v, source)?; + imported_resources.environments.push(x.clone()); + } + } + info!("Imported {} environments", imported_resources.environments.len()); + } + + if folders.len() > 0 { + while imported_resources.folders.len() < folders.len() { + for v in folders.clone() { + if let Some(id) = v.folder_id.clone() { + let has_parent_to_import = folders.iter().find(|m| m.id == id).is_some(); + let imported_parent = + imported_resources.folders.iter().find(|m| m.id == id); + // If there's also a parent to upsert, wait for that one + if imported_parent.is_none() && has_parent_to_import { + continue; + } + } + if let Some(_) = imported_resources.folders.iter().find(|f| f.id == v.id) { + continue; + } + let x = self.upsert_folder(&v, source)?; + imported_resources.folders.push(x.clone()); + } + } + info!("Imported {} folders", imported_resources.folders.len()); + } + + if http_requests.len() > 0 { + for v in http_requests { + let x = self.upsert(&v, source)?; + imported_resources.http_requests.push(x.clone()); + } + info!("Imported {} http_requests", imported_resources.http_requests.len()); + } + + if grpc_requests.len() > 0 { + for v in grpc_requests { + let x = self.upsert_grpc_request(&v, source)?; + imported_resources.grpc_requests.push(x.clone()); + } + info!("Imported {} grpc_requests", imported_resources.grpc_requests.len()); + } + + if websocket_requests.len() > 0 { + for v in websocket_requests { + let x = self.upsert_websocket_request(&v, source)?; + imported_resources.websocket_requests.push(x.clone()); + } + info!("Imported {} websocket_requests", imported_resources.websocket_requests.len()); + } + + Ok(imported_resources) + } +} diff --git a/src-tauri/yaak-models/src/queries/cookie_jars.rs b/src-tauri/yaak-models/src/queries/cookie_jars.rs new file mode 100644 index 00000000..aefdf7c3 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/cookie_jars.rs @@ -0,0 +1,35 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{CookieJar, CookieJarIden}; +use crate::queries_legacy::UpdateSource; + +impl<'a> DbContext<'a> { + pub fn get_cookie_jar(&self, id: &str) -> Result { + self.find_one(CookieJarIden::Id, id) + } + + pub fn list_cookie_jars(&self, workspace_id: &str) -> Result> { + self.find_many(CookieJarIden::WorkspaceId, workspace_id, None) + } + + pub fn delete_cookie_jar( + &self, + cookie_jar: &CookieJar, + source: &UpdateSource, + ) -> Result { + self.delete(cookie_jar, source) + } + + pub fn delete_cookie_jar_by_id(&self, id: &str, source: &UpdateSource) -> Result { + let cookie_jar = self.get_cookie_jar(id)?; + self.delete_cookie_jar(&cookie_jar, source) + } + + pub fn upsert_cookie_jar( + &self, + cookie_jar: &CookieJar, + source: &UpdateSource, + ) -> Result { + self.upsert(cookie_jar, source) + } +} diff --git a/src-tauri/yaak-models/src/queries/environments.rs b/src-tauri/yaak-models/src/queries/environments.rs new file mode 100644 index 00000000..8a8baf06 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/environments.rs @@ -0,0 +1,79 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{Environment, EnvironmentIden, UpsertModelInfo}; +use crate::queries_legacy::UpdateSource; +use log::info; +use sea_query::ColumnRef::Asterisk; +use sea_query::{Cond, Expr, Query, SqliteQueryBuilder}; +use sea_query_rusqlite::RusqliteBinder; + +impl<'a> DbContext<'a> { + pub fn get_environment(&self, id: &str) -> Result { + self.find_one(EnvironmentIden::Id, id) + } + + pub fn get_base_environment(&self, workspace_id: &str) -> Result { + let (sql, params) = Query::select() + .from(EnvironmentIden::Table) + .column(Asterisk) + .cond_where( + Cond::all() + .add(Expr::col(EnvironmentIden::WorkspaceId).eq(workspace_id)) + .add(Expr::col(EnvironmentIden::EnvironmentId).is_null()), + ) + .build_rusqlite(SqliteQueryBuilder); + let mut stmt = self.conn.prepare(sql.as_str())?; + Ok(stmt.query_row(&*params.as_params(), Environment::from_row)?) + } + + pub fn ensure_base_environment(&self, workspace_id: &str) -> Result<()> { + let environments = self.list_environments(workspace_id)?; + let base_environment = environments + .iter() + .find(|e| e.environment_id == None && e.workspace_id == workspace_id); + + if let None = base_environment { + info!("Creating base environment for {workspace_id}"); + self.upsert_environment( + &Environment { + workspace_id: workspace_id.to_string(), + name: "Global Variables".to_string(), + ..Default::default() + }, + &UpdateSource::Background, + )?; + } + + Ok(()) + } + + pub fn list_environments(&self, workspace_id: &str) -> Result> { + self.find_many(EnvironmentIden::WorkspaceId, workspace_id, None) + } + + pub fn delete_environment( + &self, + environment: &Environment, + source: &UpdateSource, + ) -> Result { + for environment in + self.find_many::(EnvironmentIden::EnvironmentId, &environment.id, None)? + { + self.delete_environment(&environment, source)?; + } + self.delete(environment, source) + } + + pub fn delete_environment_by_id(&self, id: &str, source: &UpdateSource) -> Result { + let environment = self.get_environment(id)?; + self.delete_environment(&environment, source) + } + + pub fn upsert_environment( + &self, + environment: &Environment, + source: &UpdateSource, + ) -> Result { + self.upsert(environment, source) + } +} diff --git a/src-tauri/yaak-models/src/queries/folders.rs b/src-tauri/yaak-models/src/queries/folders.rs new file mode 100644 index 00000000..156b7338 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/folders.rs @@ -0,0 +1,106 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{ + Folder, FolderIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestIden, + WebsocketRequest, WebsocketRequestIden, +}; +use crate::queries_legacy::UpdateSource; + +impl<'a> DbContext<'a> { + pub fn get_folder(&self, id: &str) -> Result { + self.find_one(FolderIden::Id, id) + } + + pub fn list_folders(&self, workspace_id: &str) -> Result> { + self.find_many(FolderIden::WorkspaceId, workspace_id, None) + } + + pub fn delete_folder(&self, folder: &Folder, source: &UpdateSource) -> Result { + for folder in self.find_many::(FolderIden::FolderId, &folder.id, None)? { + self.delete_folder(&folder, source)?; + } + for request in self.find_many::(HttpRequestIden::FolderId, &folder.id, None)? { + self.delete_http_request(&request, source)?; + } + for request in self.find_many::(GrpcRequestIden::FolderId, &folder.id, None)? { + self.delete_grpc_request(&request, source)?; + } + for request in + self.find_many::(WebsocketRequestIden::FolderId, &folder.id, None)? + { + self.delete_websocket_request(&request, source)?; + } + self.delete(folder, source) + } + + pub fn delete_folder_by_id(&self, id: &str, source: &UpdateSource) -> Result { + let folder = self.get_folder(id)?; + self.delete_folder(&folder, source) + } + + pub fn upsert_folder(&self, folder: &Folder, source: &UpdateSource) -> Result { + self.upsert(folder, source) + } + + pub fn duplicate_folder(&self, src_folder: &Folder, source: &UpdateSource) -> Result { + let workspace_id = src_folder.workspace_id.as_str(); + + let http_requests = self + .find_many::(HttpRequestIden::WorkspaceId, workspace_id, None)? + .into_iter() + .filter(|m| m.folder_id.as_ref() == Some(&src_folder.id)); + + let grpc_requests = self + .find_many::(GrpcRequestIden::WorkspaceId, workspace_id, None)? + .into_iter() + .filter(|m| m.folder_id.as_ref() == Some(&src_folder.id)); + + let folders = self + .find_many::(FolderIden::WorkspaceId, workspace_id, None)? + .into_iter() + .filter(|m| m.folder_id.as_ref() == Some(&src_folder.id)); + + let new_folder = self.upsert_folder( + &Folder { + id: "".into(), + sort_priority: src_folder.sort_priority + 0.001, + ..src_folder.clone() + }, + source, + )?; + + for m in http_requests { + self.upsert_http_request( + &HttpRequest { + id: "".into(), + folder_id: Some(new_folder.id.clone()), + sort_priority: m.sort_priority + 0.001, + ..m + }, + source, + )?; + } + for m in grpc_requests { + self.upsert_grpc_request( + &GrpcRequest { + id: "".into(), + folder_id: Some(new_folder.id.clone()), + sort_priority: m.sort_priority + 0.001, + ..m + }, + source, + )?; + } + for m in folders { + // Recurse down + self.duplicate_folder( + &Folder { + folder_id: Some(new_folder.id.clone()), + ..m + }, + source, + )?; + } + Ok(new_folder) + } +} diff --git a/src-tauri/yaak-models/src/queries/grpc_connections.rs b/src-tauri/yaak-models/src/queries/grpc_connections.rs new file mode 100644 index 00000000..4d442853 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/grpc_connections.rs @@ -0,0 +1,98 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{GrpcConnection, GrpcConnectionIden, GrpcConnectionState}; +use crate::queries::base::MAX_HISTORY_ITEMS; +use crate::queries_legacy::UpdateSource; +use log::debug; +use sea_query::{Expr, Query, SqliteQueryBuilder}; +use sea_query_rusqlite::RusqliteBinder; + +impl<'a> DbContext<'a> { + pub fn get_grpc_connection(&self, id: &str) -> Result { + self.find_one(GrpcConnectionIden::Id, id) + } + + pub fn delete_all_grpc_connections_for_request( + &self, + request_id: &str, + source: &UpdateSource, + ) -> Result<()> { + let responses = self.list_grpc_connections_for_request(request_id, None)?; + for m in responses { + self.delete(&m, source)?; + } + Ok(()) + } + + pub fn delete_all_grpc_connections_for_workspace( + &self, + workspace_id: &str, + source: &UpdateSource, + ) -> Result<()> { + for m in self.list_grpc_connections_for_workspace(workspace_id, None)? { + self.delete(&m, source)?; + } + Ok(()) + } + + pub fn delete_grpc_connection( + &self, + m: &GrpcConnection, + source: &UpdateSource, + ) -> Result { + self.delete(m, source) + } + + pub fn delete_grpc_connection_by_id( + &self, + id: &str, + source: &UpdateSource, + ) -> Result { + let grpc_connection = self.get_grpc_connection(id)?; + self.delete_grpc_connection(&grpc_connection, source) + } + + pub fn list_grpc_connections_for_request( + &self, + request_id: &str, + limit: Option, + ) -> Result> { + self.find_many(GrpcConnectionIden::RequestId, request_id, limit) + } + + pub fn list_grpc_connections_for_workspace( + &self, + workspace_id: &str, + limit: Option, + ) -> Result> { + self.find_many(GrpcConnectionIden::WorkspaceId, workspace_id, limit) + } + + pub fn cancel_pending_grpc_connections(&self) -> Result<()> { + let closed = serde_json::to_value(&GrpcConnectionState::Closed)?; + let (sql, params) = Query::update() + .table(GrpcConnectionIden::Table) + .values([(GrpcConnectionIden::State, closed.as_str().into())]) + .cond_where(Expr::col(GrpcConnectionIden::State).ne(closed.as_str())) + .build_rusqlite(SqliteQueryBuilder); + let mut stmt = self.conn.prepare(sql.as_str())?; + stmt.execute(&*params.as_params())?; + Ok(()) + } + + pub fn upsert_grpc_connection( + &self, + grpc_connection: &GrpcConnection, + source: &UpdateSource, + ) -> Result { + let connections = + self.list_grpc_connections_for_request(grpc_connection.request_id.as_str(), None)?; + + for m in connections.iter().skip(MAX_HISTORY_ITEMS - 1) { + debug!("Deleting old gRPC connection {}", grpc_connection.id); + self.delete_grpc_connection(&m, source)?; + } + + self.upsert(grpc_connection, source) + } +} diff --git a/src-tauri/yaak-models/src/queries/grpc_events.rs b/src-tauri/yaak-models/src/queries/grpc_events.rs new file mode 100644 index 00000000..4b68ba4f --- /dev/null +++ b/src-tauri/yaak-models/src/queries/grpc_events.rs @@ -0,0 +1,22 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{GrpcEvent, GrpcEventIden}; +use crate::queries_legacy::UpdateSource; + +impl<'a> DbContext<'a> { + pub fn get_grpc_events(&self, id: &str) -> Result { + self.find_one(GrpcEventIden::Id, id) + } + + pub fn list_grpc_events(&self, connection_id: &str) -> Result> { + self.find_many(GrpcEventIden::ConnectionId, connection_id, None) + } + + pub fn upsert_grpc_event( + &self, + grpc_event: &GrpcEvent, + source: &UpdateSource, + ) -> Result { + self.upsert(grpc_event, source) + } +} diff --git a/src-tauri/yaak-models/src/queries/grpc_requests.rs b/src-tauri/yaak-models/src/queries/grpc_requests.rs new file mode 100644 index 00000000..377ca25e --- /dev/null +++ b/src-tauri/yaak-models/src/queries/grpc_requests.rs @@ -0,0 +1,51 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{GrpcRequest, GrpcRequestIden}; +use crate::queries_legacy::UpdateSource; + +impl<'a> DbContext<'a> { + pub fn get_grpc_request(&self, id: &str) -> Result> { + self.find_optional(GrpcRequestIden::Id, id) + } + + pub fn list_grpc_requests(&self, workspace_id: &str) -> Result> { + self.find_many(GrpcRequestIden::WorkspaceId, workspace_id, None) + } + + pub fn delete_grpc_request( + &self, + m: &GrpcRequest, + source: &UpdateSource, + ) -> Result { + self.delete_all_grpc_connections_for_request(m.id.as_str(), source)?; + self.delete(m, source) + } + + pub fn delete_grpc_request_by_id( + &self, + id: &str, + source: &UpdateSource, + ) -> Result { + let request = self.get_grpc_request(id)?.unwrap(); + self.delete_grpc_request(&request, source) + } + + pub fn duplicate_grpc_request( + &self, + grpc_request: &GrpcRequest, + source: &UpdateSource, + ) -> Result { + let mut request = grpc_request.clone(); + request.id = "".to_string(); + request.sort_priority = request.sort_priority + 0.001; + self.upsert(&request, source) + } + + pub fn upsert_grpc_request( + &self, + grpc_request: &GrpcRequest, + source: &UpdateSource, + ) -> Result { + self.upsert(grpc_request, source) + } +} diff --git a/src-tauri/yaak-models/src/queries/http_requests.rs b/src-tauri/yaak-models/src/queries/http_requests.rs new file mode 100644 index 00000000..95c7c200 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/http_requests.rs @@ -0,0 +1,51 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{HttpRequest, HttpRequestIden}; +use crate::queries_legacy::UpdateSource; + +impl<'a> DbContext<'a> { + pub fn get_http_request(&self, id: &str) -> Result> { + self.find_optional(HttpRequestIden::Id, id) + } + + pub fn list_http_requests(&self, workspace_id: &str) -> Result> { + self.find_many(HttpRequestIden::WorkspaceId, workspace_id, None) + } + + pub fn delete_http_request( + &self, + m: &HttpRequest, + source: &UpdateSource, + ) -> Result { + self.delete_all_http_responses_for_request(m.id.as_str(), source)?; + self.delete(m, source) + } + + pub fn delete_http_request_by_id( + &self, + id: &str, + source: &UpdateSource, + ) -> Result { + let http_request = self.get_http_request(id)?.unwrap(); + self.delete_http_request(&http_request, source) + } + + pub fn duplicate_http_request( + &self, + http_request: &HttpRequest, + source: &UpdateSource, + ) -> Result { + let mut http_request = http_request.clone(); + http_request.id = "".to_string(); + http_request.sort_priority = http_request.sort_priority + 0.001; + self.upsert(&http_request, source) + } + + pub fn upsert_http_request( + &self, + http_request: &HttpRequest, + source: &UpdateSource, + ) -> Result { + self.upsert(http_request, source) + } +} diff --git a/src-tauri/yaak-models/src/queries/http_responses.rs b/src-tauri/yaak-models/src/queries/http_responses.rs new file mode 100644 index 00000000..860a9707 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/http_responses.rs @@ -0,0 +1,110 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{HttpResponse, HttpResponseIden, HttpResponseState}; +use crate::queries::base::MAX_HISTORY_ITEMS; +use crate::queries_legacy::UpdateSource; +use log::{debug, error}; +use sea_query::{Expr, Query, SqliteQueryBuilder}; +use sea_query_rusqlite::RusqliteBinder; +use std::fs; + +impl<'a> DbContext<'a> { + pub fn get_http_response(&self, id: &str) -> Result { + self.find_one(HttpResponseIden::Id, id) + } + + pub fn list_http_responses_for_request( + &self, + request_id: &str, + limit: Option, + ) -> Result> { + self.find_many(HttpResponseIden::RequestId, request_id, limit) + } + + pub fn list_http_responses_for_workspace( + &self, + workspace_id: &str, + limit: Option, + ) -> Result> { + self.find_many(HttpResponseIden::WorkspaceId, workspace_id, limit) + } + + pub fn delete_all_http_responses_for_request( + &self, + request_id: &str, + source: &UpdateSource, + ) -> Result<()> { + let responses = self.list_http_responses_for_request(request_id, None)?; + for m in responses { + self.delete(&m, source)?; + } + Ok(()) + } + + pub fn delete_all_http_responses_for_workspace( + &self, + workspace_id: &str, + source: &UpdateSource, + ) -> Result<()> { + let responses = + self.find_many::(HttpResponseIden::WorkspaceId, workspace_id, None)?; + for m in responses { + self.delete(&m, source)?; + } + Ok(()) + } + + pub fn delete_http_response( + &self, + http_response: &HttpResponse, + source: &UpdateSource, + ) -> Result { + // Delete the body file if it exists + if let Some(p) = http_response.body_path.clone() { + if let Err(e) = fs::remove_file(p) { + error!("Failed to delete body file: {}", e); + }; + } + + Ok(self.delete(http_response, source)?) + } + + pub fn upsert_http_response( + &self, + http_response: &HttpResponse, + source: &UpdateSource, + ) -> Result { + let responses = self.list_http_responses_for_request(&http_response.request_id, None)?; + + for m in responses.iter().skip(MAX_HISTORY_ITEMS - 1) { + debug!("Deleting old HTTP response {}", http_response.id); + self.delete_http_response(&m, source)?; + } + + self.upsert(http_response, source) + } + + pub fn cancel_pending_http_responses(&self) -> Result<()> { + let closed = serde_json::to_value(&HttpResponseState::Closed)?; + let (sql, params) = Query::update() + .table(HttpResponseIden::Table) + .values([(HttpResponseIden::State, closed.as_str().into())]) + .cond_where(Expr::col(HttpResponseIden::State).ne(closed.as_str())) + .build_rusqlite(SqliteQueryBuilder); + let mut stmt = self.conn.prepare(sql.as_str())?; + stmt.execute(&*params.as_params())?; + Ok(()) + } + + pub fn update_http_response_if_id( + &self, + response: &HttpResponse, + source: &UpdateSource, + ) -> Result { + if response.id.is_empty() { + Ok(response.clone()) + } else { + self.upsert(response, source) + } + } +} diff --git a/src-tauri/yaak-models/src/queries/key_values.rs b/src-tauri/yaak-models/src/queries/key_values.rs new file mode 100644 index 00000000..f601a4a1 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/key_values.rs @@ -0,0 +1,164 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{KeyValue, KeyValueIden}; +use crate::queries_legacy::{ModelChangeEvent, ModelPayload, UpdateSource}; +use log::error; +use sea_query::Keyword::CurrentTimestamp; +use sea_query::{Asterisk, Cond, Expr, OnConflict, Query, SqliteQueryBuilder}; +use sea_query_rusqlite::RusqliteBinder; + +impl<'a> DbContext<'a> { + pub fn list_key_values_raw(&self) -> Result> { + let (sql, params) = Query::select() + .from(KeyValueIden::Table) + .column(Asterisk) + .build_rusqlite(SqliteQueryBuilder); + let mut stmt = self.conn.prepare(sql.as_str())?; + let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; + Ok(items.map(|v| v.unwrap()).collect()) + } + + pub fn get_key_value_string(&self, namespace: &str, key: &str, default: &str) -> String { + match self.get_key_value_raw(namespace, key) { + None => default.to_string(), + Some(v) => { + let result = serde_json::from_str(&v.value); + match result { + Ok(v) => v, + Err(e) => { + error!("Failed to parse string key value: {}", e); + default.to_string() + } + } + } + } + } + + pub fn get_key_value_int(&self, namespace: &str, key: &str, default: i32) -> i32 { + match self.get_key_value_raw(namespace, key) { + None => default.clone(), + Some(v) => { + let result = serde_json::from_str(&v.value); + match result { + Ok(v) => v, + Err(e) => { + error!("Failed to parse int key value: {}", e); + default.clone() + } + } + } + } + } + + pub fn get_key_value_raw(&self, namespace: &str, key: &str) -> Option { + let (sql, params) = Query::select() + .from(KeyValueIden::Table) + .column(Asterisk) + .cond_where( + Cond::all() + .add(Expr::col(KeyValueIden::Namespace).eq(namespace)) + .add(Expr::col(KeyValueIden::Key).eq(key)), + ) + .build_rusqlite(SqliteQueryBuilder); + self.conn.resolve().query_row(sql.as_str(), &*params.as_params(), |row| row.try_into()).ok() + } + + pub fn set_key_value_string( + &self, + namespace: &str, + key: &str, + value: &str, + source: &UpdateSource, + ) -> (KeyValue, bool) { + let encoded = serde_json::to_string(&value).unwrap(); + self.set_key_value_raw(namespace, key, &encoded, source) + } + + pub fn set_key_value_int( + &self, + namespace: &str, + key: &str, + value: i32, + source: &UpdateSource, + ) -> (KeyValue, bool) { + let encoded = serde_json::to_string(&value).unwrap(); + self.set_key_value_raw(namespace, key, &encoded, source) + } + + pub fn set_key_value_raw( + &self, + namespace: &str, + key: &str, + value: &str, + source: &UpdateSource, + ) -> (KeyValue, bool) { + let existing = self.get_key_value_raw(namespace, key); + + let (sql, params) = Query::insert() + .into_table(KeyValueIden::Table) + .columns([ + KeyValueIden::CreatedAt, + KeyValueIden::UpdatedAt, + KeyValueIden::Namespace, + KeyValueIden::Key, + KeyValueIden::Value, + ]) + .values_panic([ + CurrentTimestamp.into(), + CurrentTimestamp.into(), + namespace.into(), + key.into(), + value.into(), + ]) + .on_conflict( + OnConflict::new() + .update_columns([KeyValueIden::UpdatedAt, KeyValueIden::Value]) + .to_owned(), + ) + .returning_all() + .build_rusqlite(SqliteQueryBuilder); + + let mut stmt = self.conn.prepare(sql.as_str()).expect("Failed to prepare KeyValue upsert"); + let m: KeyValue = stmt + .query_row(&*params.as_params(), |row| row.try_into()) + .expect("Failed to upsert KeyValue"); + + let payload = ModelPayload { + model: m.clone().into(), + update_source: source.clone(), + change: ModelChangeEvent::Upsert, + }; + self.tx.try_send(payload).unwrap(); + + (m, existing.is_none()) + } + + pub fn delete_key_value( + &self, + namespace: &str, + key: &str, + source: &UpdateSource, + ) -> Result<()> { + let kv = match self.get_key_value_raw(namespace, key) { + None => return Ok(()), + Some(m) => m, + }; + + let (sql, params) = Query::delete() + .from_table(KeyValueIden::Table) + .cond_where( + Cond::all() + .add(Expr::col(KeyValueIden::Namespace).eq(namespace)) + .add(Expr::col(KeyValueIden::Key).eq(key)), + ) + .build_rusqlite(SqliteQueryBuilder); + self.conn.execute(sql.as_str(), &*params.as_params())?; + let payload = ModelPayload { + model: kv.clone().into(), + update_source: source.clone(), + change: ModelChangeEvent::Delete, + }; + self.tx.try_send(payload).unwrap(); + Ok(()) + } +} diff --git a/src-tauri/yaak-models/src/queries/mod.rs b/src-tauri/yaak-models/src/queries/mod.rs new file mode 100644 index 00000000..a86d1e18 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/mod.rs @@ -0,0 +1,20 @@ +mod base; +mod batch; +mod cookie_jars; +mod environments; +mod folders; +mod grpc_connections; +mod grpc_events; +mod grpc_requests; +mod http_requests; +mod http_responses; +mod key_values; +mod plugin_key_values; +mod plugins; +mod settings; +mod sync_states; +mod websocket_connections; +mod websocket_events; +mod websocket_requests; +mod workspace_metas; +mod workspaces; diff --git a/src-tauri/yaak-models/src/queries/plugin_key_values.rs b/src-tauri/yaak-models/src/queries/plugin_key_values.rs new file mode 100644 index 00000000..37d8760d --- /dev/null +++ b/src-tauri/yaak-models/src/queries/plugin_key_values.rs @@ -0,0 +1,79 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{PluginKeyValue, PluginKeyValueIden}; +use sea_query::Keyword::CurrentTimestamp; +use sea_query::{Asterisk, Cond, Expr, OnConflict, Query, SqliteQueryBuilder}; +use sea_query_rusqlite::RusqliteBinder; + +impl<'a> DbContext<'a> { + pub fn get_plugin_key_value(&self, plugin_name: &str, key: &str) -> Option { + let (sql, params) = Query::select() + .from(PluginKeyValueIden::Table) + .column(Asterisk) + .cond_where( + Cond::all() + .add(Expr::col(PluginKeyValueIden::PluginName).eq(plugin_name)) + .add(Expr::col(PluginKeyValueIden::Key).eq(key)), + ) + .build_rusqlite(SqliteQueryBuilder); + self.conn.resolve().query_row(sql.as_str(), &*params.as_params(), |row| row.try_into()).ok() + } + + pub fn set_plugin_key_value( + &self, + plugin_name: &str, + key: &str, + value: &str, + ) -> (PluginKeyValue, bool) { + let existing = self.get_plugin_key_value(plugin_name, key); + + let (sql, params) = Query::insert() + .into_table(PluginKeyValueIden::Table) + .columns([ + PluginKeyValueIden::CreatedAt, + PluginKeyValueIden::UpdatedAt, + PluginKeyValueIden::PluginName, + PluginKeyValueIden::Key, + PluginKeyValueIden::Value, + ]) + .values_panic([ + CurrentTimestamp.into(), + CurrentTimestamp.into(), + plugin_name.into(), + key.into(), + value.into(), + ]) + .on_conflict( + OnConflict::new() + .update_columns([PluginKeyValueIden::UpdatedAt, PluginKeyValueIden::Value]) + .to_owned(), + ) + .returning_all() + .build_rusqlite(SqliteQueryBuilder); + + let mut stmt = + self.conn.prepare(sql.as_str()).expect("Failed to prepare PluginKeyValue upsert"); + let m: PluginKeyValue = stmt + .query_row(&*params.as_params(), |row| row.try_into()) + .expect("Failed to upsert KeyValue"); + + (m, existing.is_none()) + } + + pub fn delete_plugin_key_value(&self, namespace: &str, key: &str) -> Result { + if let None = self.get_plugin_key_value(namespace, key) { + return Ok(false); + }; + + let (sql, params) = Query::delete() + .from_table(PluginKeyValueIden::Table) + .cond_where( + Cond::all() + .add(Expr::col(PluginKeyValueIden::PluginName).eq(namespace)) + .add(Expr::col(PluginKeyValueIden::Key).eq(key)), + ) + .build_rusqlite(SqliteQueryBuilder); + self.conn.execute(sql.as_str(), &*params.as_params())?; + Ok(true) + } +} diff --git a/src-tauri/yaak-models/src/queries/plugins.rs b/src-tauri/yaak-models/src/queries/plugins.rs new file mode 100644 index 00000000..830c0f58 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/plugins.rs @@ -0,0 +1,27 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{Plugin, PluginIden}; +use crate::queries_legacy::UpdateSource; + +impl<'a> DbContext<'a> { + pub fn get_plugin(&self, id: &str) -> Result { + self.find_one(PluginIden::Id, id) + } + + pub fn list_plugins(&self) -> Result> { + self.find_all() + } + + pub fn delete_plugin(&self, plugin: &Plugin, source: &UpdateSource) -> Result { + self.delete(plugin, source) + } + + pub fn delete_plugin_by_id(&self, id: &str, source: &UpdateSource) -> Result { + let plugin = self.get_plugin(id)?; + self.delete_plugin(&plugin, source) + } + + pub fn upsert_plugin(&self, plugin: &Plugin, source: &UpdateSource) -> Result { + self.upsert(plugin, source) + } +} diff --git a/src-tauri/yaak-models/src/queries/settings.rs b/src-tauri/yaak-models/src/queries/settings.rs new file mode 100644 index 00000000..63d8f6cd --- /dev/null +++ b/src-tauri/yaak-models/src/queries/settings.rs @@ -0,0 +1,25 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{Settings, SettingsIden}; +use crate::queries_legacy::UpdateSource; + +impl<'a> DbContext<'a> { + pub fn get_or_create_settings(&self, source: &UpdateSource) -> Result { + let id = "default"; + if let Some(s) = self.find_optional::(SettingsIden::Id, id)? { + return Ok(s); + }; + + self.upsert( + &Settings { + id: id.to_string(), + ..Default::default() + }, + source, + ) + } + + pub fn upsert_settings(&self, settings: &Settings, source: &UpdateSource) -> Result { + self.upsert(settings, source) + } +} diff --git a/src-tauri/yaak-models/src/queries/sync_states.rs b/src-tauri/yaak-models/src/queries/sync_states.rs new file mode 100644 index 00000000..fb84c5d3 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/sync_states.rs @@ -0,0 +1,45 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{SyncState, SyncStateIden, UpsertModelInfo}; +use crate::queries_legacy::UpdateSource; +use sea_query::{Asterisk, Cond, Expr, Query, SqliteQueryBuilder}; +use sea_query_rusqlite::RusqliteBinder; +use std::path::Path; + +impl<'a> DbContext<'a> { + pub fn get_sync_state(&self, id: &str) -> Result { + self.find_one(SyncStateIden::Id, id) + } + + pub fn upsert_sync_state(&self, sync_state: &SyncState) -> Result { + self.upsert(sync_state, &UpdateSource::Sync) + } + + pub fn list_sync_states_for_workspace( + &self, + workspace_id: &str, + sync_dir: &Path, + ) -> Result> { + let (sql, params) = Query::select() + .from(SyncStateIden::Table) + .column(Asterisk) + .cond_where( + Cond::all() + .add(Expr::col(SyncStateIden::WorkspaceId).eq(workspace_id)) + .add(Expr::col(SyncStateIden::SyncDir).eq(sync_dir.to_string_lossy())), + ) + .build_rusqlite(SqliteQueryBuilder); + let mut stmt = self.conn.prepare(sql.as_str())?; + let items = stmt.query_map(&*params.as_params(), SyncState::from_row)?; + Ok(items.map(|v| v.unwrap()).collect()) + } + + pub fn delete_sync_state(&self, sync_state: &SyncState) -> Result { + self.delete(sync_state, &UpdateSource::Sync) + } + + pub fn delete_sync_state_by_id(&self, id: &str) -> Result { + let sync_state = self.get_sync_state(id)?; + self.delete_sync_state(&sync_state) + } +} diff --git a/src-tauri/yaak-models/src/queries/websocket_connections.rs b/src-tauri/yaak-models/src/queries/websocket_connections.rs new file mode 100644 index 00000000..4bcbd58e --- /dev/null +++ b/src-tauri/yaak-models/src/queries/websocket_connections.rs @@ -0,0 +1,97 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{WebsocketConnection, WebsocketConnectionIden, WebsocketConnectionState}; +use crate::queries::base::MAX_HISTORY_ITEMS; +use crate::queries_legacy::UpdateSource; +use log::debug; +use sea_query::{Expr, Query, SqliteQueryBuilder}; +use sea_query_rusqlite::RusqliteBinder; + +impl<'a> DbContext<'a> { + pub fn get_websocket_connection(&self, id: &str) -> Result { + self.find_one(WebsocketConnectionIden::Id, id) + } + + pub fn delete_all_websocket_connections_for_request( + &self, + request_id: &str, + source: &UpdateSource, + ) -> Result<()> { + let responses = self.list_websocket_connections_for_request(request_id)?; + for m in responses { + self.delete(&m, source)?; + } + Ok(()) + } + + pub fn delete_all_websocket_connections_for_workspace( + &self, + workspace_id: &str, + source: &UpdateSource, + ) -> Result<()> { + let responses = self.list_websocket_connections_for_workspace(workspace_id)?; + for m in responses { + self.delete(&m, source)?; + } + Ok(()) + } + + pub fn list_websocket_connections_for_workspace( + &self, + workspace_id: &str, + ) -> Result> { + self.find_many(WebsocketConnectionIden::WorkspaceId, workspace_id, None) + } + + pub fn list_websocket_connections_for_request( + &self, + request_id: &str, + ) -> Result> { + self.find_many(WebsocketConnectionIden::RequestId, request_id, None) + } + + pub fn delete_websocket_connection( + &self, + websocket_connection: &WebsocketConnection, + source: &UpdateSource, + ) -> Result { + self.delete(websocket_connection, source) + } + + pub fn delete_websocket_connection_by_id( + &self, + id: &str, + source: &UpdateSource, + ) -> Result { + let websocket_connection = self.get_websocket_connection(id)?; + self.delete_websocket_connection(&websocket_connection, source) + } + + pub fn upsert_websocket_connection( + &self, + websocket_connection: &WebsocketConnection, + source: &UpdateSource, + ) -> Result { + let connections = + self.list_websocket_connections_for_request(&websocket_connection.request_id)?; + + for m in connections.iter().skip(MAX_HISTORY_ITEMS - 1) { + debug!("Deleting old websocket connection {}", websocket_connection.id); + self.delete_websocket_connection(&m, source)?; + } + + self.upsert(websocket_connection, source) + } + + pub fn cancel_pending_websocket_connections(&self) -> Result<()> { + let closed = serde_json::to_value(&WebsocketConnectionState::Closed)?; + let (sql, params) = Query::update() + .table(WebsocketConnectionIden::Table) + .values([(WebsocketConnectionIden::State, closed.as_str().into())]) + .cond_where(Expr::col(WebsocketConnectionIden::State).ne(closed.as_str())) + .build_rusqlite(SqliteQueryBuilder); + let mut stmt = self.conn.prepare(sql.as_str())?; + stmt.execute(&*params.as_params())?; + Ok(()) + } +} diff --git a/src-tauri/yaak-models/src/queries/websocket_events.rs b/src-tauri/yaak-models/src/queries/websocket_events.rs new file mode 100644 index 00000000..08fee311 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/websocket_events.rs @@ -0,0 +1,25 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{ + WebsocketEvent, + WebsocketEventIden, +}; +use crate::queries_legacy::UpdateSource; + +impl<'a> DbContext<'a> { + pub fn get_websocket_event(&self, id: &str) -> Result { + self.find_one(WebsocketEventIden::Id, id) + } + + pub fn list_websocket_events(&self, connection_id: &str) -> Result> { + self.find_many(WebsocketEventIden::ConnectionId, connection_id, None) + } + + pub fn upsert_websocket_event( + &self, + websocket_event: &WebsocketEvent, + source: &UpdateSource, + ) -> Result { + self.upsert(websocket_event, source) + } +} diff --git a/src-tauri/yaak-models/src/queries/websocket_requests.rs b/src-tauri/yaak-models/src/queries/websocket_requests.rs new file mode 100644 index 00000000..dd3ffa23 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/websocket_requests.rs @@ -0,0 +1,51 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{WebsocketRequest, WebsocketRequestIden}; +use crate::queries_legacy::UpdateSource; + +impl<'a> DbContext<'a> { + pub fn get_websocket_request(&self, id: &str) -> Result> { + self.find_optional(WebsocketRequestIden::Id, id) + } + + pub fn list_websocket_requests(&self, workspace_id: &str) -> Result> { + self.find_many(WebsocketRequestIden::WorkspaceId, workspace_id, None) + } + + pub fn delete_websocket_request( + &self, + websocket_request: &WebsocketRequest, + source: &UpdateSource, + ) -> Result { + self.delete_all_websocket_connections_for_request(websocket_request.id.as_str(), source)?; + self.delete(websocket_request, source) + } + + pub fn delete_websocket_request_by_id( + &self, + id: &str, + source: &UpdateSource, + ) -> Result { + let request = self.get_websocket_request(id)?.unwrap(); + self.delete_websocket_request(&request, source) + } + + pub fn duplicate_websocket_request( + &self, + websocket_request: &WebsocketRequest, + source: &UpdateSource, + ) -> Result { + let mut websocket_request = websocket_request.clone(); + websocket_request.id = "".to_string(); + websocket_request.sort_priority = websocket_request.sort_priority + 0.001; + self.upsert(&websocket_request, source) + } + + pub fn upsert_websocket_request( + &self, + websocket_request: &WebsocketRequest, + source: &UpdateSource, + ) -> Result { + self.upsert(websocket_request, source) + } +} diff --git a/src-tauri/yaak-models/src/queries/workspace_metas.rs b/src-tauri/yaak-models/src/queries/workspace_metas.rs new file mode 100644 index 00000000..c9c2415a --- /dev/null +++ b/src-tauri/yaak-models/src/queries/workspace_metas.rs @@ -0,0 +1,36 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{Workspace, WorkspaceMeta, WorkspaceMetaIden}; +use crate::queries_legacy::UpdateSource; + +impl<'a> DbContext<'a> { + pub fn get_workspace_meta(&self, workspace: &Workspace) -> Result> { + self.find_optional(WorkspaceMetaIden::WorkspaceId, &workspace.id) + } + + pub fn get_or_create_workspace_meta( + &self, + workspace: &Workspace, + source: &UpdateSource, + ) -> Result { + let workspace_meta = self.get_workspace_meta(workspace)?; + if let Some(workspace_meta) = workspace_meta { + return Ok(workspace_meta); + } + + let workspace_meta = WorkspaceMeta { + workspace_id: workspace.to_owned().id, + ..Default::default() + }; + + self.upsert_workspace_meta(&workspace_meta, source) + } + + pub fn upsert_workspace_meta( + &self, + workspace_meta: &WorkspaceMeta, + source: &UpdateSource, + ) -> Result { + self.upsert(workspace_meta, source) + } +} diff --git a/src-tauri/yaak-models/src/queries/workspaces.rs b/src-tauri/yaak-models/src/queries/workspaces.rs new file mode 100644 index 00000000..743366ff --- /dev/null +++ b/src-tauri/yaak-models/src/queries/workspaces.rs @@ -0,0 +1,52 @@ +use crate::error::Result; +use crate::manager::DbContext; +use crate::models::{ + Folder, FolderIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestIden, + WebsocketRequest, WebsocketRequestIden, Workspace, WorkspaceIden, +}; +use crate::queries_legacy::UpdateSource; + +impl<'a> DbContext<'a> { + pub fn get_workspace(&self, id: &str) -> Result { + self.find_one(WorkspaceIden::Id, id) + } + + pub fn list_workspaces(&self) -> Result> { + self.find_all() + } + + pub fn delete_workspace( + &self, + workspace: &Workspace, + source: &UpdateSource, + ) -> Result { + for folder in self.find_many::(FolderIden::WorkspaceId, &workspace.id, None)? { + self.delete_folder(&folder, source)?; + } + for request in + self.find_many::(HttpRequestIden::WorkspaceId, &workspace.id, None)? + { + self.delete_http_request(&request, source)?; + } + for request in + self.find_many::(GrpcRequestIden::WorkspaceId, &workspace.id, None)? + { + self.delete_grpc_request(&request, source)?; + } + for request in + self.find_many::(WebsocketRequestIden::FolderId, &workspace.id, None)? + { + self.delete_websocket_request(&request, source)?; + } + self.delete(workspace, source) + } + + pub fn delete_workspace_by_id(&self, id: &str, source: &UpdateSource) -> Result { + let workspace = self.get_workspace(id)?; + self.delete_workspace(&workspace, source) + } + + pub fn upsert_workspace(&self, w: &Workspace, source: &UpdateSource) -> Result { + self.upsert(w, source) + } +} diff --git a/src-tauri/yaak-models/src/queries_legacy.rs b/src-tauri/yaak-models/src/queries_legacy.rs new file mode 100644 index 00000000..f0b256fb --- /dev/null +++ b/src-tauri/yaak-models/src/queries_legacy.rs @@ -0,0 +1,150 @@ +use crate::error::Result; +use crate::manager::QueryManagerExt; +use crate::models::{AnyModel, Environment, Folder, GrpcRequest, HttpRequest, ModelType, WebsocketRequest, Workspace, WorkspaceIden}; +use chrono::{NaiveDateTime, Utc}; +use log::warn; +use nanoid::nanoid; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Listener, Runtime, WebviewWindow}; +use ts_rs::TS; + +pub fn generate_model_id(model: ModelType) -> String { + let id = generate_id(); + format!("{}_{}", model.id_prefix(), id) +} + +pub fn generate_id() -> String { + let alphabet: [char; 57] = [ + '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', + 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', + ]; + + nanoid!(10, &alphabet) +} + +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export, export_to = "gen_models.ts")] +pub struct ModelPayload { + pub model: AnyModel, + pub update_source: UpdateSource, + pub change: ModelChangeEvent, +} + +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "snake_case", tag = "type")] +#[ts(export, export_to = "gen_models.ts")] +pub enum ModelChangeEvent { + Upsert, + Delete, +} + +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "snake_case", tag = "type")] +#[ts(export, export_to = "gen_models.ts")] +pub enum UpdateSource { + Sync, + Window { label: String }, + Plugin, + Background, + Import, +} + +impl UpdateSource { + pub fn from_window(window: &WebviewWindow) -> Self { + Self::Window { + label: window.label().to_string(), + } + } +} + +pub fn listen_to_model_delete(app_handle: &AppHandle, handler: F) +where + F: Fn(ModelPayload) + Send + 'static, + R: Runtime, +{ + app_handle.listen_any("deleted_model", move |e| { + match serde_json::from_str(e.payload()) { + Ok(payload) => handler(payload), + Err(e) => { + warn!("Failed to deserialize deleted model {}", e); + return; + } + }; + }); +} + +pub fn listen_to_model_upsert(app_handle: &AppHandle, handler: F) +where + F: Fn(ModelPayload) + Send + 'static, + R: Runtime, +{ + app_handle.listen_any("upserted_model", move |e| { + match serde_json::from_str(e.payload()) { + Ok(payload) => handler(payload), + Err(e) => { + warn!("Failed to deserialize upserted model {}", e); + return; + } + }; + }); +} + +#[derive(Default, Debug, Deserialize, Serialize)] +#[serde(default, rename_all = "camelCase")] +pub struct WorkspaceExport { + pub yaak_version: String, + pub yaak_schema: i64, + pub timestamp: NaiveDateTime, + pub resources: BatchUpsertResult, +} + +#[derive(Default, Debug, Deserialize, Serialize)] +#[serde(default, rename_all = "camelCase")] +pub struct BatchUpsertResult { + pub workspaces: Vec, + pub environments: Vec, + pub folders: Vec, + pub http_requests: Vec, + pub grpc_requests: Vec, + pub websocket_requests: Vec, +} + +pub async fn get_workspace_export_resources( + app_handle: &AppHandle, + workspace_ids: Vec<&str>, + include_environments: bool, +) -> Result { + let mut data = WorkspaceExport { + yaak_version: app_handle.package_info().version.clone().to_string(), + yaak_schema: 3, + timestamp: Utc::now().naive_utc(), + resources: BatchUpsertResult { + workspaces: Vec::new(), + environments: Vec::new(), + folders: Vec::new(), + http_requests: Vec::new(), + grpc_requests: Vec::new(), + websocket_requests: Vec::new(), + }, + }; + + let db = app_handle.queries().connect().await?; + for workspace_id in workspace_ids { + data.resources.workspaces.push(db.find_one(WorkspaceIden::Id, workspace_id)?); + data.resources.environments.append(&mut db.list_environments(workspace_id)?); + data.resources.folders.append(&mut db.list_folders(workspace_id)?); + data.resources.http_requests.append(&mut db.list_http_requests(workspace_id)?); + data.resources.grpc_requests.append(&mut db.list_grpc_requests(workspace_id)?); + data.resources.websocket_requests.append(&mut db.list_websocket_requests(workspace_id)?); + } + + // Nuke environments if we don't want them + if !include_environments { + data.resources.environments.clear(); + } + + Ok(data) +} diff --git a/src-tauri/yaak-plugins/src/manager.rs b/src-tauri/yaak-plugins/src/manager.rs index ed8a7ffd..6c8c5c0b 100644 --- a/src-tauri/yaak-plugins/src/manager.rs +++ b/src-tauri/yaak-plugins/src/manager.rs @@ -14,7 +14,7 @@ use crate::events::{ use crate::nodejs::start_nodejs_plugin_runtime; use crate::plugin_handle::PluginHandle; use crate::server_ws::PluginRuntimeServerWebsocket; -use log::{info, warn}; +use log::{error, info, warn}; use std::collections::HashMap; use std::env; use std::path::PathBuf; @@ -25,9 +25,11 @@ use tauri::{AppHandle, Manager, Runtime, WebviewWindow}; use tokio::fs::read_dir; use tokio::net::TcpListener; use tokio::sync::{mpsc, Mutex}; -use tokio::time::timeout; -use yaak_models::queries::{generate_id, list_plugins}; +use tokio::time::{timeout, Instant}; +use yaak_models::manager::QueryManagerExt; +use yaak_models::queries_legacy::generate_id; use yaak_templates::error::Error::RenderError; +use yaak_templates::error::Result as TemplateResult; #[derive(Clone)] pub struct PluginManager { @@ -157,7 +159,8 @@ impl PluginManager { }) .collect(); - let plugins = list_plugins(app_handle).await.unwrap_or_default(); + let plugins = + app_handle.queries().connect().await.unwrap().list_plugins().unwrap_or_default(); let installed_plugin_dirs: Vec = plugins .iter() .map(|p| PluginCandidate { @@ -208,7 +211,7 @@ impl PluginManager { // Boot the plugin let event = timeout( - Duration::from_secs(2), + Duration::from_secs(5), self.send_to_plugin_and_wait( window_context, &plugin_handle, @@ -239,12 +242,14 @@ impl PluginManager { app_handle: &AppHandle, window_context: &WindowContext, ) -> Result<()> { + let start = Instant::now(); let candidates = self.list_plugin_dirs(app_handle).await; for candidate in candidates.clone() { // First remove the plugin if it exists if let Some(plugin) = self.get_plugin_by_dir(candidate.dir.as_str()).await { if let Err(e) = self.remove_plugin(window_context, &plugin).await { - warn!("Failed to remove plugin {} {e:?}", candidate.dir); + error!("Failed to remove plugin {} {e:?}", candidate.dir); + continue; } } if let Err(e) = self @@ -255,15 +260,13 @@ impl PluginManager { } } + let plugins = self.plugins.lock().await; + let names = plugins.iter().map(|p| p.dir.to_string()).collect::>(); info!( - "Initialized all plugins:\n - {}", - self.plugins - .lock() - .await - .iter() - .map(|p| p.dir.to_string()) - .collect::>() - .join("\n - "), + "Initialized {} plugins in {:?}:\n - {}", + plugins.len(), + start.elapsed(), + names.join("\n - "), ); Ok(()) @@ -598,7 +601,7 @@ impl PluginManager { fn_name: &str, args: HashMap, purpose: RenderPurpose, - ) -> yaak_templates::error::Result { + ) -> TemplateResult { let req = CallTemplateFunctionRequest { name: fn_name.to_string(), args: CallTemplateFunctionArgs { @@ -615,13 +618,14 @@ impl PluginManager { let value = events.into_iter().find_map(|e| match e.payload { InternalEventPayload::CallTemplateFunctionResponse(CallTemplateFunctionResponse { value, - }) => value, + }) => Some(value), _ => None, }); match value { - None => Err(RenderError(format!("Template function not found {fn_name}"))), - Some(v) => Ok(v), + None => Err(RenderError(format!("Template function {fn_name}(…) not found "))), + Some(Some(v)) => Ok(v), // Plugin returned string + Some(None) => Ok("".to_string()), // Plugin returned null } } diff --git a/src-tauri/yaak-sync/permissions/schemas/schema.json b/src-tauri/yaak-sync/permissions/schemas/schema.json index df0342cc..d72d12bf 100644 --- a/src-tauri/yaak-sync/permissions/schemas/schema.json +++ b/src-tauri/yaak-sync/permissions/schemas/schema.json @@ -49,7 +49,7 @@ "minimum": 1.0 }, "description": { - "description": "Human-readable description of what the permission does. Tauri convention is to use

headings in markdown content for Tauri documentation generation purposes.", + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", "type": [ "string", "null" @@ -111,7 +111,7 @@ "type": "string" }, "description": { - "description": "Human-readable description of what the permission does. Tauri internal convention is to use

headings in markdown content for Tauri documentation generation purposes.", + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", "type": [ "string", "null" diff --git a/src-tauri/yaak-sync/src/models.rs b/src-tauri/yaak-sync/src/models.rs index e1c20a12..50f4c09d 100644 --- a/src-tauri/yaak-sync/src/models.rs +++ b/src-tauri/yaak-sync/src/models.rs @@ -126,7 +126,8 @@ impl TryFrom for SyncModel { AnyModel::WebsocketConnection(m) => return Err(UnknownModel(m.model)), AnyModel::WebsocketEvent(m) => return Err(UnknownModel(m.model)), AnyModel::WorkspaceMeta(m) => return Err(UnknownModel(m.model)), + AnyModel::SyncState(m) => return Err(UnknownModel(m.model)), }; Ok(m) } -} +} \ No newline at end of file diff --git a/src-tauri/yaak-sync/src/sync.rs b/src-tauri/yaak-sync/src/sync.rs index b7482c8d..111d0de7 100644 --- a/src-tauri/yaak-sync/src/sync.rs +++ b/src-tauri/yaak-sync/src/sync.rs @@ -11,13 +11,9 @@ use tokio::fs; use tokio::fs::File; use tokio::io::AsyncWriteExt; use ts_rs::TS; +use yaak_models::manager::QueryManagerExt; use yaak_models::models::{SyncState, WorkspaceMeta}; -use yaak_models::queries::{ - batch_upsert, delete_environment, delete_folder, delete_grpc_request, delete_http_request, - delete_sync_state, delete_websocket_request, delete_workspace, get_workspace_export_resources, - get_workspace_meta, list_sync_states_for_workspace, upsert_sync_state, upsert_workspace_meta, - UpdateSource, -}; +use yaak_models::queries_legacy::{get_workspace_export_resources, UpdateSource}; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase", tag = "type")] @@ -115,12 +111,14 @@ pub(crate) async fn get_db_candidates( .into_iter() .map(|m| (m.id(), m)) .collect(); - let sync_states: HashMap<_, _> = - list_sync_states_for_workspace(app_handle, workspace_id, sync_dir) - .await? - .into_iter() - .map(|s| (s.model_id.clone(), s)) - .collect(); + let sync_states: HashMap<_, _> = app_handle + .queries() + .connect() + .await? + .list_sync_states_for_workspace(workspace_id, sync_dir)? + .into_iter() + .map(|s| (s.model_id.clone(), s)) + .collect(); // 1. Add candidates for models (created/modified/unmodified) let mut candidates: Vec = models @@ -442,41 +440,47 @@ pub(crate) async fn apply_sync_ops( }); } - let upserted_models = batch_upsert( - app_handle, - workspaces_to_upsert, - environments_to_upsert, - folders_to_upsert, - http_requests_to_upsert, - grpc_requests_to_upsert, - websocket_requests_to_upsert, - &UpdateSource::Sync, - ) - .await?; + let upserted_models = app_handle + .queries() + .with_tx(|tx| { + tx.batch_upsert( + workspaces_to_upsert, + environments_to_upsert, + folders_to_upsert, + http_requests_to_upsert, + grpc_requests_to_upsert, + websocket_requests_to_upsert, + &UpdateSource::Sync, + ) + }) + .await?; // Ensure we creat WorkspaceMeta models for each new workspace, with the appropriate sync dir let sync_dir_string = sync_dir.to_string_lossy().to_string(); + let db = app_handle.queries().connect().await?; for workspace in upserted_models.workspaces { - let r = match get_workspace_meta(app_handle, &workspace).await { + let r = match db.get_workspace_meta(&workspace) { Ok(Some(m)) => { if m.setting_sync_dir == Some(sync_dir_string.clone()) { // We don't need to update if unchanged continue; } - let wm = WorkspaceMeta { - setting_sync_dir: Some(sync_dir.to_string_lossy().to_string()), - ..m - }; - upsert_workspace_meta(app_handle, wm, &UpdateSource::Sync).await + db.upsert_workspace_meta( + &WorkspaceMeta { + setting_sync_dir: Some(sync_dir.to_string_lossy().to_string()), + ..m + }, + &UpdateSource::Sync, + ) } - Ok(None) => { - let wm = WorkspaceMeta { + Ok(None) => db.upsert_workspace_meta( + &WorkspaceMeta { workspace_id: workspace_id.to_string(), setting_sync_dir: Some(sync_dir.to_string_lossy().to_string()), ..Default::default() - }; - upsert_workspace_meta(app_handle, wm, &UpdateSource::Sync).await - } + }, + &UpdateSource::Sync, + ), Err(e) => Err(e), }; @@ -527,7 +531,7 @@ pub(crate) async fn apply_sync_state_ops( flushed_at: Utc::now().naive_utc(), ..Default::default() }; - upsert_sync_state(app_handle, sync_state).await?; + app_handle.queries().connect().await?.upsert_sync_state(&sync_state)?; } SyncStateOp::Update { state: sync_state, @@ -541,10 +545,10 @@ pub(crate) async fn apply_sync_state_ops( flushed_at: Utc::now().naive_utc(), ..sync_state }; - upsert_sync_state(app_handle, sync_state).await?; + app_handle.queries().connect().await?.upsert_sync_state(&sync_state)?; } SyncStateOp::Delete { state } => { - delete_sync_state(app_handle, state.id.as_str()).await?; + app_handle.queries().connect().await?.delete_sync_state(&state)?; } } } @@ -557,24 +561,25 @@ fn derive_model_filename(m: &SyncModel) -> PathBuf { } async fn delete_model(app_handle: &AppHandle, model: &SyncModel) -> Result<()> { + let db = app_handle.queries().connect().await?; match model { SyncModel::Workspace(m) => { - delete_workspace(app_handle, m.id.as_str(), &UpdateSource::Sync).await?; + db.delete_workspace(&m, &UpdateSource::Sync)?; } SyncModel::Environment(m) => { - delete_environment(app_handle, m.id.as_str(), &UpdateSource::Sync).await?; + db.delete_environment(&m, &UpdateSource::Sync)?; } SyncModel::Folder(m) => { - delete_folder(app_handle, m.id.as_str(), &UpdateSource::Sync).await?; + db.delete_folder(&m, &UpdateSource::Sync)?; } SyncModel::HttpRequest(m) => { - delete_http_request(app_handle, m.id.as_str(), &UpdateSource::Sync).await?; + db.delete_http_request(&m, &UpdateSource::Sync)?; } SyncModel::GrpcRequest(m) => { - delete_grpc_request(app_handle, m.id.as_str(), &UpdateSource::Sync).await?; + db.delete_grpc_request(&m, &UpdateSource::Sync)?; } SyncModel::WebsocketRequest(m) => { - delete_websocket_request(app_handle, m.id.as_str(), &UpdateSource::Sync).await?; + db.delete_websocket_request(&m, &UpdateSource::Sync)?; } }; Ok(()) diff --git a/src-tauri/yaak-ws/permissions/schemas/schema.json b/src-tauri/yaak-ws/permissions/schemas/schema.json index 9aea6c8a..c11c3eee 100644 --- a/src-tauri/yaak-ws/permissions/schemas/schema.json +++ b/src-tauri/yaak-ws/permissions/schemas/schema.json @@ -49,7 +49,7 @@ "minimum": 1.0 }, "description": { - "description": "Human-readable description of what the permission does. Tauri convention is to use

headings in markdown content for Tauri documentation generation purposes.", + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", "type": [ "string", "null" @@ -111,7 +111,7 @@ "type": "string" }, "description": { - "description": "Human-readable description of what the permission does. Tauri internal convention is to use

headings in markdown content for Tauri documentation generation purposes.", + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", "type": [ "string", "null" diff --git a/src-tauri/yaak-ws/src/commands.rs b/src-tauri/yaak-ws/src/commands.rs index b8e533fe..9e7afa4f 100644 --- a/src-tauri/yaak-ws/src/commands.rs +++ b/src-tauri/yaak-ws/src/commands.rs @@ -10,15 +10,12 @@ use tokio::sync::{mpsc, Mutex}; use tokio_tungstenite::tungstenite::http::HeaderValue; use tokio_tungstenite::tungstenite::Message; use yaak_http::apply_path_placeholders; +use yaak_models::manager::QueryManagerExt; use yaak_models::models::{ HttpResponseHeader, WebsocketConnection, WebsocketConnectionState, WebsocketEvent, WebsocketEventType, WebsocketRequest, }; -use yaak_models::queries; -use yaak_models::queries::{ - get_base_environment, get_cookie_jar, get_environment, get_websocket_connection, - get_websocket_request, upsert_websocket_connection, upsert_websocket_event, UpdateSource, -}; +use yaak_models::queries_legacy::UpdateSource; use yaak_plugins::events::{ CallHttpAuthenticationRequest, HttpHeader, RenderPurpose, WindowContext, }; @@ -31,8 +28,11 @@ pub(crate) async fn upsert_request( app_handle: AppHandle, window: WebviewWindow, ) -> Result { - Ok(queries::upsert_websocket_request(&app_handle, request, &UpdateSource::from_window(&window)) - .await?) + Ok(app_handle + .queries() + .connect() + .await? + .upsert_websocket_request(&request, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -41,12 +41,9 @@ pub(crate) async fn duplicate_request( app_handle: AppHandle, window: WebviewWindow, ) -> Result { - Ok(queries::duplicate_websocket_request( - &app_handle, - request_id, - &UpdateSource::from_window(&window), - ) - .await?) + let db = app_handle.queries().connect().await?; + let request = db.get_websocket_request(request_id)?.unwrap(); + Ok(db.duplicate_websocket_request(&request, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -55,7 +52,11 @@ pub(crate) async fn delete_request( app_handle: AppHandle, window: WebviewWindow, ) -> Result { - Ok(queries::delete_websocket_request(&app_handle, request_id, &UpdateSource::from_window(&window)).await?) + Ok(app_handle + .queries() + .connect() + .await? + .delete_websocket_request_by_id(request_id, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -64,8 +65,11 @@ pub(crate) async fn delete_connection( app_handle: AppHandle, window: WebviewWindow, ) -> Result { - Ok(queries::delete_websocket_connection(&app_handle, connection_id, &UpdateSource::from_window(&window)) - .await?) + Ok(app_handle + .queries() + .connect() + .await? + .delete_websocket_connection_by_id(connection_id, &UpdateSource::from_window(&window))?) } #[tauri::command] @@ -74,8 +78,10 @@ pub(crate) async fn delete_connections( app_handle: AppHandle, window: WebviewWindow, ) -> Result<()> { - Ok(queries::delete_all_websocket_connections(&app_handle, request_id, &UpdateSource::from_window(&window)) - .await?) + Ok(app_handle.queries().connect().await?.delete_all_websocket_connections_for_request( + request_id, + &UpdateSource::from_window(&window), + )?) } #[tauri::command] @@ -83,7 +89,7 @@ pub(crate) async fn list_events( connection_id: &str, app_handle: AppHandle, ) -> Result> { - Ok(queries::list_websocket_events(&app_handle, connection_id).await?) + Ok(app_handle.queries().connect().await?.list_websocket_events(connection_id)?) } #[tauri::command] @@ -91,7 +97,7 @@ pub(crate) async fn list_requests( workspace_id: &str, app_handle: AppHandle, ) -> Result> { - Ok(queries::list_websocket_requests(&app_handle, workspace_id).await?) + Ok(app_handle.queries().connect().await?.list_websocket_requests(workspace_id)?) } #[tauri::command] @@ -99,7 +105,11 @@ pub(crate) async fn list_connections( workspace_id: &str, app_handle: AppHandle, ) -> Result> { - Ok(queries::list_websocket_connections_for_workspace(&app_handle, workspace_id).await?) + Ok(app_handle + .queries() + .connect() + .await? + .list_websocket_connections_for_workspace(workspace_id)?) } #[tauri::command] @@ -110,16 +120,23 @@ pub(crate) async fn send( window: WebviewWindow, ws_manager: State<'_, Mutex>, ) -> Result { - let connection = get_websocket_connection(&app_handle, connection_id).await?; - let unrendered_request = get_websocket_request(&app_handle, &connection.request_id) - .await? - .ok_or(GenericError("WebSocket Request not found".to_string()))?; + let (connection, unrendered_request) = { + let db = app_handle.queries().connect().await?; + let connection = db.get_websocket_connection(connection_id)?; + let unrendered_request = db + .get_websocket_request(&connection.request_id)? + .ok_or(GenericError("WebSocket Request not found".to_string()))?; + (connection, unrendered_request) + }; let environment = match environment_id { - Some(id) => Some(get_environment(&app_handle, id).await?), + Some(id) => Some(app_handle.queries().connect().await?.get_environment(id)?), None => None, }; - let base_environment = - get_base_environment(&app_handle, &unrendered_request.workspace_id).await?; + let base_environment = app_handle + .queries() + .connect() + .await? + .get_base_environment(&unrendered_request.workspace_id)?; let request = render_request( &unrendered_request, &base_environment, @@ -135,9 +152,8 @@ pub(crate) async fn send( let mut ws_manager = ws_manager.lock().await; ws_manager.send(&connection.id, Message::Text(request.message.clone().into())).await?; - upsert_websocket_event( - &app_handle, - WebsocketEvent { + app_handle.queries().connect().await?.upsert_websocket_event( + &WebsocketEvent { connection_id: connection.id.clone(), request_id: request.id.clone(), workspace_id: connection.workspace_id.clone(), @@ -147,9 +163,7 @@ pub(crate) async fn send( ..Default::default() }, &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + )?; Ok(connection) } @@ -161,17 +175,17 @@ pub(crate) async fn close( window: WebviewWindow, ws_manager: State<'_, Mutex>, ) -> Result { - let connection = get_websocket_connection(&app_handle, connection_id).await?; - let connection = upsert_websocket_connection( - &app_handle, - &WebsocketConnection { - state: WebsocketConnectionState::Closing, - ..connection - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + let connection = { + let db = app_handle.queries().connect().await?; + let connection = db.get_websocket_connection(connection_id)?; + db.upsert_websocket_connection( + &WebsocketConnection { + state: WebsocketConnectionState::Closing, + ..connection + }, + &UpdateSource::from_window(&window), + )? + }; let mut ws_manager = ws_manager.lock().await; if let Err(e) = ws_manager.close(&connection.id).await { @@ -191,15 +205,21 @@ pub(crate) async fn connect( plugin_manager: State<'_, PluginManager>, ws_manager: State<'_, Mutex>, ) -> Result { - let unrendered_request = get_websocket_request(&app_handle, request_id) + let unrendered_request = app_handle + .queries() + .connect() .await? + .get_websocket_request(request_id)? .ok_or(GenericError("Failed to find GRPC request".to_string()))?; let environment = match environment_id { - Some(id) => Some(get_environment(&app_handle, id).await?), + Some(id) => Some(app_handle.queries().connect().await?.get_environment(id)?), None => None, }; - let base_environment = - get_base_environment(&app_handle, &unrendered_request.workspace_id).await?; + let base_environment = app_handle + .queries() + .connect() + .await? + .get_base_environment(&unrendered_request.workspace_id)?; let request = render_request( &unrendered_request, &base_environment, @@ -242,20 +262,18 @@ pub(crate) async fn connect( // TODO: Handle cookies let _cookie_jar = match cookie_jar_id { - Some(id) => Some(get_cookie_jar(&app_handle, id).await?), + Some(id) => Some(app_handle.queries().connect().await?.get_cookie_jar(id)?), None => None, }; - let connection = upsert_websocket_connection( - &app_handle, + let connection = app_handle.queries().connect().await?.upsert_websocket_connection( &WebsocketConnection { workspace_id: request.workspace_id.clone(), request_id: request_id.to_string(), ..Default::default() }, &UpdateSource::from_window(&window), - ) - .await?; + )?; let (receive_tx, mut receive_rx) = mpsc::channel::(128); let mut ws_manager = ws_manager.lock().await; @@ -278,22 +296,19 @@ pub(crate) async fn connect( { Ok(r) => r, Err(e) => { - return Ok(upsert_websocket_connection( - &app_handle, + return Ok(app_handle.queries().connect().await?.upsert_websocket_connection( &WebsocketConnection { error: Some(format!("{e:?}")), state: WebsocketConnectionState::Closed, ..connection }, &UpdateSource::from_window(&window), - ) - .await?); + )?); } }; - upsert_websocket_event( - &app_handle, - WebsocketEvent { + app_handle.queries().connect().await?.upsert_websocket_event( + &WebsocketEvent { connection_id: connection.id.clone(), request_id: request.id.clone(), workspace_id: connection.workspace_id.clone(), @@ -302,9 +317,7 @@ pub(crate) async fn connect( ..Default::default() }, &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + )?; let response_headers = response .headers() @@ -315,8 +328,7 @@ pub(crate) async fn connect( }) .collect::>(); - let connection = upsert_websocket_connection( - &app_handle, + let connection = app_handle.queries().connect().await?.upsert_websocket_connection( &WebsocketConnection { state: WebsocketConnectionState::Connected, headers: response_headers, @@ -325,8 +337,7 @@ pub(crate) async fn connect( ..connection }, &UpdateSource::from_window(&window), - ) - .await?; + )?; { let connection_id = connection.id.clone(); @@ -340,59 +351,68 @@ pub(crate) async fn connect( has_written_close = true; } - upsert_websocket_event( - &app_handle, - WebsocketEvent { - connection_id: connection_id.clone(), - request_id: request_id.clone(), - workspace_id: workspace_id.clone(), - is_server: true, - message_type: match message { - Message::Text(_) => WebsocketEventType::Text, - Message::Binary(_) => WebsocketEventType::Binary, - Message::Ping(_) => WebsocketEventType::Ping, - Message::Pong(_) => WebsocketEventType::Pong, - Message::Close(_) => WebsocketEventType::Close, - // Raw frame will never happen during a read - Message::Frame(_) => WebsocketEventType::Frame, + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_websocket_event( + &WebsocketEvent { + connection_id: connection_id.clone(), + request_id: request_id.clone(), + workspace_id: workspace_id.clone(), + is_server: true, + message_type: match message { + Message::Text(_) => WebsocketEventType::Text, + Message::Binary(_) => WebsocketEventType::Binary, + Message::Ping(_) => WebsocketEventType::Ping, + Message::Pong(_) => WebsocketEventType::Pong, + Message::Close(_) => WebsocketEventType::Close, + // Raw frame will never happen during a read + Message::Frame(_) => WebsocketEventType::Frame, + }, + message: message.into_data().into(), + ..Default::default() }, - message: message.into_data().into(), - ..Default::default() - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); + &UpdateSource::from_window(&window), + ) + .unwrap(); } info!("Websocket connection closed"); if !has_written_close { - upsert_websocket_event( - &app_handle, - WebsocketEvent { - connection_id: connection_id.clone(), - request_id: request_id.clone(), - workspace_id: workspace_id.clone(), - is_server: true, - message_type: WebsocketEventType::Close, - ..Default::default() + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_websocket_event( + &WebsocketEvent { + connection_id: connection_id.clone(), + request_id: request_id.clone(), + workspace_id: workspace_id.clone(), + is_server: true, + message_type: WebsocketEventType::Close, + ..Default::default() + }, + &UpdateSource::from_window(&window), + ) + .unwrap(); + } + app_handle + .queries() + .connect() + .await + .unwrap() + .upsert_websocket_connection( + &WebsocketConnection { + workspace_id: request.workspace_id.clone(), + request_id: request_id.to_string(), + state: WebsocketConnectionState::Closed, + ..connection }, &UpdateSource::from_window(&window), ) - .await .unwrap(); - } - upsert_websocket_connection( - &app_handle, - &WebsocketConnection { - workspace_id: request.workspace_id.clone(), - request_id: request_id.to_string(), - state: WebsocketConnectionState::Closed, - ..connection - }, - &UpdateSource::from_window(&window), - ) - .await - .unwrap(); }); } diff --git a/src-web/components/HttpResponsePane.tsx b/src-web/components/HttpResponsePane.tsx index b746f6aa..878f717c 100644 --- a/src-web/components/HttpResponsePane.tsx +++ b/src-web/components/HttpResponsePane.tsx @@ -86,6 +86,7 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) { }, [activeRequestId, setActiveTabs], ); + console.log("ACTIVE RESPONSE", activeResponse); return (
('cmd_create_http_request', { + return invokeCmd('cmd_upsert_http_request', { request: { workspaceId, ...patch }, }); }, diff --git a/src-web/hooks/useSyncModelStores.ts b/src-web/hooks/useSyncModelStores.ts index bc9d51f2..a209bd0f 100644 --- a/src-web/hooks/useSyncModelStores.ts +++ b/src-web/hooks/useSyncModelStores.ts @@ -178,7 +178,6 @@ export function removeModelByKv(model: KeyValue) { } function shouldIgnoreModel({ model, updateSource }: ModelPayload) { - console.log('HELLO', updateSource); // Never ignore updates from non-user sources if (updateSource.type !== 'window') { return false; diff --git a/src-web/hooks/useUpdateAnyHttpRequest.ts b/src-web/hooks/useUpdateAnyHttpRequest.ts index f65faa3e..881586b1 100644 --- a/src-web/hooks/useUpdateAnyHttpRequest.ts +++ b/src-web/hooks/useUpdateAnyHttpRequest.ts @@ -21,7 +21,7 @@ export function useUpdateAnyHttpRequest() { const patchedRequest = typeof update === 'function' ? update(request) : { ...request, ...update }; - return invokeCmd('cmd_update_http_request', { request: patchedRequest }); + return invokeCmd('cmd_upsert_http_request', { request: patchedRequest }); }, onSuccess: async (request) => { setHttpRequests(updateModelList(request)); diff --git a/src-web/lib/tauri.ts b/src-web/lib/tauri.ts index a354fb7e..d0236f56 100644 --- a/src-web/lib/tauri.ts +++ b/src-web/lib/tauri.ts @@ -8,7 +8,6 @@ type TauriCmd = | 'cmd_create_cookie_jar' | 'cmd_create_environment' | 'cmd_create_grpc_request' - | 'cmd_create_http_request' | 'cmd_curl_to_request' | 'cmd_delete_all_grpc_connections' | 'cmd_delete_all_http_responses' @@ -75,7 +74,7 @@ type TauriCmd = | 'cmd_update_environment' | 'cmd_update_folder' | 'cmd_update_grpc_request' - | 'cmd_update_http_request' + | 'cmd_upsert_http_request' | 'cmd_update_settings' | 'cmd_update_workspace' | 'cmd_update_workspace_meta';