From 85c14045424b7402f2ebd11cf9fa188a17ac66fe Mon Sep 17 00:00:00 2001 From: maxichrome Date: Sun, 22 May 2022 23:24:43 -0500 Subject: [PATCH] Format existing code with new conventions --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/actions/build-server-image/action.yml | 2 +- .github/actions/install-ffmpeg-macos/index.js | 2 +- .../actions/install-ffmpeg-macos/package.json | 30 +- .github/pull_request_template.md | 3 +- .prettierrc | 3 +- .rustfmt.toml | 1 - .vscode/settings.json | 46 +- CODE_OF_CONDUCT.md | 33 +- CONTRIBUTING.md | 7 +- README.md | 1 - apps/desktop/package.json | 78 +-- apps/desktop/src-tauri/rustfmt.toml | 3 +- apps/desktop/src-tauri/src/build.rs | 10 +- apps/desktop/src-tauri/src/main.rs | 162 ++--- apps/desktop/src-tauri/src/menu.rs | 150 ++-- apps/desktop/src-tauri/src/window.rs | 142 ++-- apps/desktop/src-tauri/tauri.conf.json | 162 ++--- apps/desktop/src-tauri/tauri.linux.conf.json | 144 ++-- apps/desktop/src/index.html | 20 +- apps/desktop/src/index.tsx | 139 ++-- apps/desktop/src/vite-env.d.ts | 2 +- apps/desktop/tsconfig.json | 6 +- apps/desktop/vite.config.ts | 43 +- apps/mobile/package.json | 8 +- apps/server/package.json | 8 +- apps/web/package.json | 58 +- apps/web/public/manifest.json | 46 +- apps/web/src/App.tsx | 129 ++-- apps/web/src/env.d.ts | 4 +- apps/web/src/index.html | 18 +- apps/web/src/index.tsx | 10 +- apps/web/tsconfig.json | 6 +- apps/web/vercel.json | 2 +- apps/web/vite.config.ts | 42 +- core/.rustfmt.toml | 3 +- core/bindings/Client.ts | 11 +- core/bindings/ClientCommand.ts | 16 +- core/bindings/ClientQuery.ts | 12 +- core/bindings/ClientState.ts | 12 +- core/bindings/CoreEvent.ts | 12 +- core/bindings/CoreResource.ts | 14 +- core/bindings/CoreResponse.ts | 24 +- core/bindings/DirectoryWithContents.ts | 7 +- core/bindings/EncryptionAlgorithm.ts | 3 +- core/bindings/File.ts | 27 +- core/bindings/FileKind.ts | 12 +- core/bindings/FilePath.ts | 18 +- core/bindings/JobReport.ts | 13 +- core/bindings/JobStatus.ts | 3 +- core/bindings/LibraryState.ts | 8 +- core/bindings/LocationResource.ts | 12 +- core/bindings/Platform.ts | 3 +- core/bindings/Statistics.ts | 11 +- core/bindings/Volume.ts | 12 +- core/derive/src/lib.rs | 42 +- core/package.json | 32 +- core/prisma/src/main.rs | 2 +- core/scripts/bindingsIndex.ts | 40 +- core/src/client/mod.rs | 124 ++-- core/src/crypto/encryption.rs | 8 +- core/src/db/migrate.rs | 236 +++---- core/src/db/mod.rs | 18 +- core/src/encode/metadata.rs | 194 +++--- core/src/encode/thumb.rs | 193 +++--- core/src/encode/vthumb.rs | 1 + core/src/encode/vthumbstrip.rs | 1 + core/src/file/cas/checksum.rs | 86 +-- core/src/file/cas/identifier.rs | 107 +-- core/src/file/explorer/open.rs | 92 +-- core/src/file/indexer/mod.rs | 38 +- core/src/file/indexer/pathctx.rs | 20 +- core/src/file/indexer/scan.rs | 436 ++++++------ core/src/file/mod.rs | 206 +++--- core/src/file/watcher.rs | 32 +- core/src/job/jobs.rs | 256 ++++--- core/src/job/mod.rs | 8 +- core/src/job/worker.rs | 298 ++++---- core/src/lib.rs | 639 +++++++++--------- core/src/library/loader.rs | 110 +-- core/src/library/mod.rs | 12 +- core/src/library/statistics.rs | 244 +++---- core/src/native/methods.rs | 34 +- core/src/native/swift.rs | 8 +- core/src/p2p/discover.rs | 50 +- core/src/p2p/listener.rs | 68 +- core/src/p2p/mod.rs | 6 +- core/src/p2p/pool.rs | 2 +- core/src/secret/keygen.rs | 1 + core/src/secret/mod.rs | 1 + core/src/state/client.rs | 140 ++-- core/src/sync/crdt/mod.rs | 12 +- core/src/sync/crdt/operation.rs | 38 +- core/src/sync/crdt/replicate.rs | 26 +- core/src/sync/engine.rs | 147 ++-- core/src/sync/examples/file.rs | 32 +- core/src/sync/examples/tag.rs | 28 +- core/src/sync/mod.rs | 26 +- core/src/sys/locations.rs | 304 ++++----- core/src/sys/mod.rs | 16 +- core/src/sys/volumes.rs | 203 +++--- core/src/util/commit.rs | 50 +- docs/architecture/virtual-filesystem.md | 16 +- docs/changelog/index.md | 2 +- docs/product/ideas.md | 10 +- docs/product/roadmap.md | 6 +- package.json | 79 +-- packages/client/package.json | 70 +- packages/client/src/ClientProvider.tsx | 18 +- packages/client/src/bridge.ts | 68 +- packages/client/src/files/query.ts | 17 +- packages/client/src/files/state.ts | 24 +- packages/client/src/window.d.ts | 6 +- packages/client/tsconfig.json | 8 +- packages/config/base.tsconfig.json | 56 +- packages/config/eslint-preset.js | 18 +- packages/config/interface.tsconfig.json | 8 +- packages/config/package.json | 14 +- packages/interface/package.json | 1 - .../src/components/jobs/RunningJobsWidget.tsx | 39 +- packages/interface/src/screens/Settings.tsx | 9 +- packages/interface/src/style.scss | 26 +- packages/ui/package.json | 60 +- packages/ui/src/Button.tsx | 102 +-- packages/ui/src/Dropdown.tsx | 139 ++-- packages/ui/style/postcss.config.js | 8 +- packages/ui/style/style.scss | 73 +- packages/ui/style/tailwind.js | 238 +++---- packages/ui/tsconfig.json | 10 +- pnpm-lock.yaml | Bin 305965 -> 306045 bytes tsconfig.json | 42 +- 132 files changed, 3894 insertions(+), 3701 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1c8c5ffe7..edee57fd3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: 🐞 Bug Report description: Report a bug -labels: +labels: - kind/bug - status/needs-triage @@ -43,7 +43,7 @@ body: id: info attributes: label: Platform and versions - description: "Please include the output of `pnpm --version && cargo --version && rustc --version` along with information about your Operating System such as version and/or specific distribution if revelant." + description: 'Please include the output of `pnpm --version && cargo --version && rustc --version` along with information about your Operating System such as version and/or specific distribution if revelant.' render: shell validations: required: true @@ -53,7 +53,7 @@ body: attributes: label: Stack trace render: shell - + - type: textarea id: context attributes: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 5ec4697aa..b121fc1c4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -11,4 +11,4 @@ contact_links: about: Suggest any ideas you have using our discussion forums. - name: 💬 Discord Chat url: https://discord.gg/gTaF2Z44f5 - about: Ask questions and talk to other Spacedrive users and the maintainers \ No newline at end of file + about: Ask questions and talk to other Spacedrive users and the maintainers diff --git a/.github/actions/build-server-image/action.yml b/.github/actions/build-server-image/action.yml index 6b6f86e38..e4ffc3dce 100644 --- a/.github/actions/build-server-image/action.yml +++ b/.github/actions/build-server-image/action.yml @@ -1,4 +1,4 @@ -name: Build Server Image +name: Build Server Image description: Builds and publishes the docker image for the Spacedrive server inputs: gh_token: diff --git a/.github/actions/install-ffmpeg-macos/index.js b/.github/actions/install-ffmpeg-macos/index.js index 700837043..38812de3c 100644 --- a/.github/actions/install-ffmpeg-macos/index.js +++ b/.github/actions/install-ffmpeg-macos/index.js @@ -3,6 +3,6 @@ const core = require('@actions/core'); const exec = require('@actions/exec'); const github = require('@actions/github'); -// const folders = +// const folders = exec.exec('brew', ['install', 'ffmpeg']); diff --git a/.github/actions/install-ffmpeg-macos/package.json b/.github/actions/install-ffmpeg-macos/package.json index 6d0070abe..a752be00f 100644 --- a/.github/actions/install-ffmpeg-macos/package.json +++ b/.github/actions/install-ffmpeg-macos/package.json @@ -1,17 +1,17 @@ { - "name": "install-ffmpeg-macos", - "version": "0.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "Brendan Allan", - "license": "ISC", - "dependencies": { - "@actions/core": "^1.6.0", - "@actions/exec": "^1.1.1", - "@actions/github": "^5.0.1" - } + "name": "install-ffmpeg-macos", + "version": "0.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "Brendan Allan", + "license": "ISC", + "dependencies": { + "@actions/core": "^1.6.0", + "@actions/exec": "^1.1.1", + "@actions/github": "^5.0.1" + } } diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index eb3361eda..b87b20af0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,10 +1,9 @@ - - + Closes #(issue) diff --git a/.prettierrc b/.prettierrc index 28cff452b..36ee950c2 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,9 +1,10 @@ { + "plugins": ["@trivago/prettier-plugin-sort-imports"], "useTabs": true, "printWidth": 100, "singleQuote": true, "trailingComma": "none", - "jsxBracketSameLine": false, + "bracketSameLine": false, "semi": true, "quoteProps": "consistent", "importOrder": [ diff --git a/.rustfmt.toml b/.rustfmt.toml index 7f591ca55..b6bfaa825 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,5 +1,4 @@ hard_tabs = true -tab_spaces = 4 match_block_trailing_comma = true max_width = 90 newline_style = "Unix" diff --git a/.vscode/settings.json b/.vscode/settings.json index e1511cb49..91bf193cc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,25 +1,25 @@ { - "cSpell.words": [ - "actix", - "bpfrpt", - "creationdate", - "ipfs", - "Keepsafe", - "pathctx", - "prismjs", - "proptype", - "quicktime", - "repr", - "Roadmap", - "svgr", - "tailwindcss", - "trivago", - "tsparticles", - "upsert" - ], - "[rust]": { - "editor.defaultFormatter": "matklad.rust-analyzer" - }, - "rust-analyzer.procMacro.enable": true, - "rust-analyzer.diagnostics.experimental.enable": false + "cSpell.words": [ + "actix", + "bpfrpt", + "creationdate", + "ipfs", + "Keepsafe", + "pathctx", + "prismjs", + "proptype", + "quicktime", + "repr", + "Roadmap", + "svgr", + "tailwindcss", + "trivago", + "tsparticles", + "upsert" + ], + "[rust]": { + "editor.defaultFormatter": "matklad.rust-analyzer" + }, + "rust-analyzer.procMacro.enable": true, + "rust-analyzer.diagnostics.experimental.enable": false } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5df1a2aa4..bb617bbf5 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,3 @@ - # Contributor Covenant Code of Conduct ## Our Pledge @@ -18,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities @@ -107,7 +106,7 @@ Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an +standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within @@ -119,15 +118,15 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla coc]. For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +[https://www.contributor-covenant.org/faq][faq]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq +[mozilla coc]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41df6d1a5..b8ba04560 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,17 +61,18 @@ If you are having issues ensure you are using the following versions of Rust and ### Pull Request When you're finished with the changes, create a pull request, also known as a PR. -- Fill the "Ready for review" template so that we can review your PR. This template helps reviewers understand your changes as well as the purpose of your pull request. + +- Fill the "Ready for review" template so that we can review your PR. This template helps reviewers understand your changes as well as the purpose of your pull request. - Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one. - Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge. -Once you submit your PR, a team member will review your proposal. We may ask questions or request for additional information. + Once you submit your PR, a team member will review your proposal. We may ask questions or request for additional information. - We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch. - As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations). - If you run into any merge issues, checkout this [git tutorial](https://lab.github.com/githubtraining/managing-merge-conflicts) to help you resolve merge conflicts and other issues. ### Your PR is merged! -Congratulations :tada::tada: The Spacedrive team thanks you :sparkles:. +Congratulations :tada::tada: The Spacedrive team thanks you :sparkles:. Once your PR is merged, your contributions will be included in the next release of the application. diff --git a/README.md b/README.md index ebf04950e..1d66cefc2 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,6 @@ Organize files across many devices in one place. From cloud services to offline For independent creatives, hoarders and those that want to own their digital footprint. Spacedrive provides a file management experience like no other, and it's completely free. -

Logo
diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 0aaedc26f..e72ead71a 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,41 +1,41 @@ { - "name": "@sd/desktop", - "version": "1.0.0", - "main": "index.js", - "license": "MIT", - "private": true, - "scripts": { - "vite": "vite", - "dev": "concurrently \"pnpm tauri dev\" \"vite\"", - "tauri": "tauri", - "build": "vite build" - }, - "dependencies": { - "@sd/client": "workspace:*", - "@sd/core": "workspace:*", - "@sd/interface": "workspace:*", - "@sd/ui": "workspace:*", - "@tauri-apps/api": "^1.0.0-rc.3", - "react": "^18.0.0", - "react-dom": "^18.0.0" - }, - "devDependencies": { - "@tauri-apps/cli": "^1.0.0-rc.8", - "@tauri-apps/tauricon": "github:tauri-apps/tauricon", - "@types/babel-core": "^6.25.7", - "@types/byte-size": "^8.1.0", - "@types/react": "^18.0.8", - "@types/react-dom": "^18.0.0", - "@types/react-router-dom": "^5.3.3", - "@types/react-window": "^1.8.5", - "@types/tailwindcss": "^3.0.10", - "@vitejs/plugin-react": "^1.3.1", - "concurrently": "^7.1.0", - "prettier": "^2.6.2", - "sass": "^1.50.0", - "typescript": "^4.6.3", - "vite": "^2.9.5", - "vite-plugin-filter-replace": "^0.1.9", - "vite-plugin-svgr": "^1.1.0" - } + "name": "@sd/desktop", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "private": true, + "scripts": { + "vite": "vite", + "dev": "concurrently \"pnpm tauri dev\" \"vite\"", + "tauri": "tauri", + "build": "vite build" + }, + "dependencies": { + "@sd/client": "workspace:*", + "@sd/core": "workspace:*", + "@sd/interface": "workspace:*", + "@sd/ui": "workspace:*", + "@tauri-apps/api": "^1.0.0-rc.3", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "devDependencies": { + "@tauri-apps/cli": "^1.0.0-rc.8", + "@tauri-apps/tauricon": "github:tauri-apps/tauricon", + "@types/babel-core": "^6.25.7", + "@types/byte-size": "^8.1.0", + "@types/react": "^18.0.8", + "@types/react-dom": "^18.0.0", + "@types/react-router-dom": "^5.3.3", + "@types/react-window": "^1.8.5", + "@types/tailwindcss": "^3.0.10", + "@vitejs/plugin-react": "^1.3.1", + "concurrently": "^7.1.0", + "prettier": "^2.6.2", + "sass": "^1.50.0", + "typescript": "^4.6.3", + "vite": "^2.9.5", + "vite-plugin-filter-replace": "^0.1.9", + "vite-plugin-svgr": "^1.1.0" + } } diff --git a/apps/desktop/src-tauri/rustfmt.toml b/apps/desktop/src-tauri/rustfmt.toml index 136f5f330..a231bfab7 100644 --- a/apps/desktop/src-tauri/rustfmt.toml +++ b/apps/desktop/src-tauri/rustfmt.toml @@ -1,6 +1,5 @@ max_width = 100 -hard_tabs = false -tab_spaces = 2 +hard_tabs = true newline_style = "Auto" use_small_heuristics = "Default" reorder_imports = true diff --git a/apps/desktop/src-tauri/src/build.rs b/apps/desktop/src-tauri/src/build.rs index 5e6666d8b..5aba44c8d 100644 --- a/apps/desktop/src-tauri/src/build.rs +++ b/apps/desktop/src-tauri/src/build.rs @@ -1,11 +1,11 @@ // use swift_rs::build_utils::{link_swift, link_swift_package}; fn main() { - // HOTFIX: compile the swift code for arm64 - // std::env::set_var("CARGO_CFG_TARGET_ARCH", "arm64"); + // HOTFIX: compile the swift code for arm64 + // std::env::set_var("CARGO_CFG_TARGET_ARCH", "arm64"); - // link_swift(); - // link_swift_package("swift-lib", "../../../packages/macos/"); + // link_swift(); + // link_swift_package("swift-lib", "../../../packages/macos/"); - tauri_build::build(); + tauri_build::build(); } diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs index 4eacfa283..93e328aa3 100644 --- a/apps/desktop/src-tauri/src/main.rs +++ b/apps/desktop/src-tauri/src/main.rs @@ -11,106 +11,106 @@ use window::WindowExt; #[tauri::command(async)] async fn client_query_transport( - core: tauri::State<'_, CoreController>, - data: ClientQuery, + core: tauri::State<'_, CoreController>, + data: ClientQuery, ) -> Result { - match core.query(data).await { - Ok(response) => Ok(response), - Err(err) => { - println!("query error: {:?}", err); - Err(err.to_string()) - } - } + match core.query(data).await { + Ok(response) => Ok(response), + Err(err) => { + println!("query error: {:?}", err); + Err(err.to_string()) + } + } } #[tauri::command(async)] async fn client_command_transport( - core: tauri::State<'_, CoreController>, - data: ClientCommand, + core: tauri::State<'_, CoreController>, + data: ClientCommand, ) -> Result { - match core.command(data).await { - Ok(response) => Ok(response), - Err(err) => { - println!("command error: {:?}", err); - Err(err.to_string()) - } - } + match core.command(data).await { + Ok(response) => Ok(response), + Err(err) => { + println!("command error: {:?}", err); + Err(err.to_string()) + } + } } #[tauri::command(async)] async fn app_ready(app_handle: tauri::AppHandle) { - let window = app_handle.get_window("main").unwrap(); + let window = app_handle.get_window("main").unwrap(); - window.show().unwrap(); + window.show().unwrap(); - #[cfg(target_os = "macos")] - { - std::thread::sleep(std::time::Duration::from_millis(1000)); - println!("fixing shadow for, {:?}", window.ns_window().unwrap()); - window.fix_shadow(); - } + #[cfg(target_os = "macos")] + { + std::thread::sleep(std::time::Duration::from_millis(1000)); + println!("fixing shadow for, {:?}", window.ns_window().unwrap()); + window.fix_shadow(); + } } #[tokio::main] async fn main() { - let data_dir = path::data_dir().unwrap_or(std::path::PathBuf::from("./")); - // create an instance of the core - let (mut core, mut event_receiver) = Core::new(data_dir).await; - // run startup tasks - core.initializer().await; - // extract the core controller - let controller = core.get_controller(); - // throw the core into a dedicated thread - tokio::spawn(async move { - core.start().await; - }); - // create tauri app - tauri::Builder::default() - // pass controller to the tauri state manager - .manage(controller) - .setup(|app| { - let app = app.handle(); + let data_dir = path::data_dir().unwrap_or(std::path::PathBuf::from("./")); + // create an instance of the core + let (mut core, mut event_receiver) = Core::new(data_dir).await; + // run startup tasks + core.initializer().await; + // extract the core controller + let controller = core.get_controller(); + // throw the core into a dedicated thread + tokio::spawn(async move { + core.start().await; + }); + // create tauri app + tauri::Builder::default() + // pass controller to the tauri state manager + .manage(controller) + .setup(|app| { + let app = app.handle(); - app.windows().iter().for_each(|(_, window)| { - window.hide().unwrap(); + app.windows().iter().for_each(|(_, window)| { + window.hide().unwrap(); - #[cfg(target_os = "windows")] - window.set_decorations(true).unwrap(); + #[cfg(target_os = "windows")] + window.set_decorations(true).unwrap(); - #[cfg(target_os = "macos")] - window.set_transparent_titlebar(true, true); - }); + #[cfg(target_os = "macos")] + window.set_transparent_titlebar(true, true); + }); - // core event transport - tokio::spawn(async move { - let mut last = Instant::now(); - // handle stream output - while let Some(event) = event_receiver.recv().await { - match event { - CoreEvent::InvalidateQueryDebounced(_) => { - let current = Instant::now(); - if current.duration_since(last) > Duration::from_millis(1000 / 60) { - last = current; - app.emit_all("core_event", &event).unwrap(); - } - } - event => { - app.emit_all("core_event", &event).unwrap(); - } - } - } - }); + // core event transport + tokio::spawn(async move { + let mut last = Instant::now(); + // handle stream output + while let Some(event) = event_receiver.recv().await { + match event { + CoreEvent::InvalidateQueryDebounced(_) => { + let current = Instant::now(); + if current.duration_since(last) > Duration::from_millis(1000 / 60) { + last = current; + app.emit_all("core_event", &event).unwrap(); + } + } + event => { + app.emit_all("core_event", &event).unwrap(); + } + } + } + }); - Ok(()) - }) - .on_menu_event(|event| menu::handle_menu_event(event)) - .on_window_event(|event| window::handle_window_event(event)) - .invoke_handler(tauri::generate_handler![ - client_query_transport, - client_command_transport, - app_ready, - ]) - .menu(menu::get_menu()) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + Ok(()) + }) + .on_menu_event(|event| menu::handle_menu_event(event)) + .on_window_event(|event| window::handle_window_event(event)) + .invoke_handler(tauri::generate_handler![ + client_query_transport, + client_command_transport, + app_ready, + ]) + .menu(menu::get_menu()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); } diff --git a/apps/desktop/src-tauri/src/menu.rs b/apps/desktop/src-tauri/src/menu.rs index 52db3400a..ffd1a4b2e 100644 --- a/apps/desktop/src-tauri/src/menu.rs +++ b/apps/desktop/src-tauri/src/menu.rs @@ -3,88 +3,88 @@ use std::env::consts; use tauri::{AboutMetadata, CustomMenuItem, Menu, MenuItem, Submenu, WindowMenuEvent, Wry}; pub(crate) fn get_menu() -> Menu { - match consts::OS { - "linux" => Menu::new(), - "macos" => custom_menu_bar(), - _ => Menu::new(), - } + match consts::OS { + "linux" => Menu::new(), + "macos" => custom_menu_bar(), + _ => Menu::new(), + } } fn custom_menu_bar() -> Menu { - // let quit = CustomMenuItem::new("quit".to_string(), "Quit"); - // let close = CustomMenuItem::new("close".to_string(), "Close"); - // let jeff = CustomMenuItem::new("jeff".to_string(), "Jeff"); - // let submenu = Submenu::new( - // "File", - // Menu::new().add_item(quit).add_item(close).add_item(jeff), - // ); - let spacedrive = Submenu::new( - "Spacedrive", - Menu::new() - .add_native_item(MenuItem::About( - "Spacedrive".to_string(), - AboutMetadata::new(), - )) // TODO: fill out about metadata - .add_native_item(MenuItem::Separator) - .add_native_item(MenuItem::Services) - .add_native_item(MenuItem::Separator) - .add_native_item(MenuItem::Hide) - .add_native_item(MenuItem::HideOthers) - .add_native_item(MenuItem::ShowAll) - .add_native_item(MenuItem::Separator) - .add_native_item(MenuItem::Quit), - ); + // let quit = CustomMenuItem::new("quit".to_string(), "Quit"); + // let close = CustomMenuItem::new("close".to_string(), "Close"); + // let jeff = CustomMenuItem::new("jeff".to_string(), "Jeff"); + // let submenu = Submenu::new( + // "File", + // Menu::new().add_item(quit).add_item(close).add_item(jeff), + // ); + let spacedrive = Submenu::new( + "Spacedrive", + Menu::new() + .add_native_item(MenuItem::About( + "Spacedrive".to_string(), + AboutMetadata::new(), + )) // TODO: fill out about metadata + .add_native_item(MenuItem::Separator) + .add_native_item(MenuItem::Services) + .add_native_item(MenuItem::Separator) + .add_native_item(MenuItem::Hide) + .add_native_item(MenuItem::HideOthers) + .add_native_item(MenuItem::ShowAll) + .add_native_item(MenuItem::Separator) + .add_native_item(MenuItem::Quit), + ); - let file = Submenu::new( - "File", - Menu::new() - .add_item( - CustomMenuItem::new("new_window".to_string(), "New Window") - .accelerator("CmdOrCtrl+N") - .disabled(), - ) - .add_item( - CustomMenuItem::new("close".to_string(), "Close Window").accelerator("CmdOrCtrl+W"), - ), - ); - let edit = Submenu::new( - "Edit", - Menu::new() - .add_native_item(MenuItem::Copy) - .add_native_item(MenuItem::Paste), - ); - let view = Submenu::new( - "View", - Menu::new() - .add_item( - CustomMenuItem::new("command_pallete".to_string(), "Command Pallete") - .accelerator("CmdOrCtrl+P"), - ) - .add_item(CustomMenuItem::new("layout".to_string(), "Layout").disabled()), - ); - let window = Submenu::new( - "Window", - Menu::new().add_native_item(MenuItem::EnterFullScreen), - ); + let file = Submenu::new( + "File", + Menu::new() + .add_item( + CustomMenuItem::new("new_window".to_string(), "New Window") + .accelerator("CmdOrCtrl+N") + .disabled(), + ) + .add_item( + CustomMenuItem::new("close".to_string(), "Close Window").accelerator("CmdOrCtrl+W"), + ), + ); + let edit = Submenu::new( + "Edit", + Menu::new() + .add_native_item(MenuItem::Copy) + .add_native_item(MenuItem::Paste), + ); + let view = Submenu::new( + "View", + Menu::new() + .add_item( + CustomMenuItem::new("command_pallete".to_string(), "Command Pallete") + .accelerator("CmdOrCtrl+P"), + ) + .add_item(CustomMenuItem::new("layout".to_string(), "Layout").disabled()), + ); + let window = Submenu::new( + "Window", + Menu::new().add_native_item(MenuItem::EnterFullScreen), + ); - let menu = Menu::new() - .add_submenu(spacedrive) - .add_submenu(file) - .add_submenu(edit) - .add_submenu(view) - .add_submenu(window); + let menu = Menu::new() + .add_submenu(spacedrive) + .add_submenu(file) + .add_submenu(edit) + .add_submenu(view) + .add_submenu(window); - menu + menu } pub(crate) fn handle_menu_event(event: WindowMenuEvent) { - match event.menu_item_id() { - "quit" => { - std::process::exit(0); - } - "close" => { - event.window().close().unwrap(); - } - _ => {} - } + match event.menu_item_id() { + "quit" => { + std::process::exit(0); + } + "close" => { + event.window().close().unwrap(); + } + _ => {} + } } diff --git a/apps/desktop/src-tauri/src/window.rs b/apps/desktop/src-tauri/src/window.rs index 77f56c679..676c98292 100644 --- a/apps/desktop/src-tauri/src/window.rs +++ b/apps/desktop/src-tauri/src/window.rs @@ -1,93 +1,93 @@ use tauri::{GlobalWindowEvent, Runtime, Window, Wry}; pub(crate) fn handle_window_event(event: GlobalWindowEvent) { - match event.event() { - _ => {} - } + match event.event() { + _ => {} + } } pub trait WindowExt { - #[cfg(target_os = "macos")] - fn set_toolbar(&self, shown: bool); - #[cfg(target_os = "macos")] - fn set_transparent_titlebar(&self, transparent: bool, large: bool); - #[cfg(target_os = "macos")] - fn fix_shadow(&self); + #[cfg(target_os = "macos")] + fn set_toolbar(&self, shown: bool); + #[cfg(target_os = "macos")] + fn set_transparent_titlebar(&self, transparent: bool, large: bool); + #[cfg(target_os = "macos")] + fn fix_shadow(&self); } impl WindowExt for Window { - #[cfg(target_os = "macos")] - fn set_toolbar(&self, shown: bool) { - use cocoa::{ - appkit::{NSToolbar, NSWindow}, - base::{nil, NO}, - foundation::NSString, - }; + #[cfg(target_os = "macos")] + fn set_toolbar(&self, shown: bool) { + use cocoa::{ + appkit::{NSToolbar, NSWindow}, + base::{nil, NO}, + foundation::NSString, + }; - unsafe { - let id = self.ns_window().unwrap() as cocoa::base::id; + unsafe { + let id = self.ns_window().unwrap() as cocoa::base::id; - if shown { - let toolbar = - NSToolbar::alloc(nil).initWithIdentifier_(NSString::alloc(nil).init_str("wat")); - toolbar.setShowsBaselineSeparator_(NO); - id.setToolbar_(toolbar); - } else { - id.setToolbar_(nil); - } - } - } + if shown { + let toolbar = + NSToolbar::alloc(nil).initWithIdentifier_(NSString::alloc(nil).init_str("wat")); + toolbar.setShowsBaselineSeparator_(NO); + id.setToolbar_(toolbar); + } else { + id.setToolbar_(nil); + } + } + } - #[cfg(target_os = "macos")] - fn set_transparent_titlebar(&self, transparent: bool, large: bool) { - use cocoa::{ - appkit::{NSWindow, NSWindowStyleMask, NSWindowTitleVisibility}, - base::{NO, YES}, - }; + #[cfg(target_os = "macos")] + fn set_transparent_titlebar(&self, transparent: bool, large: bool) { + use cocoa::{ + appkit::{NSWindow, NSWindowStyleMask, NSWindowTitleVisibility}, + base::{NO, YES}, + }; - unsafe { - let id = self.ns_window().unwrap() as cocoa::base::id; + unsafe { + let id = self.ns_window().unwrap() as cocoa::base::id; - let mut style_mask = id.styleMask(); - // println!("existing style mask, {:#?}", style_mask); - style_mask.set( - NSWindowStyleMask::NSFullSizeContentViewWindowMask, - transparent, - ); - style_mask.set( - NSWindowStyleMask::NSTexturedBackgroundWindowMask, - transparent, - ); - style_mask.set( - NSWindowStyleMask::NSUnifiedTitleAndToolbarWindowMask, - transparent && large, - ); - id.setStyleMask_(style_mask); + let mut style_mask = id.styleMask(); + // println!("existing style mask, {:#?}", style_mask); + style_mask.set( + NSWindowStyleMask::NSFullSizeContentViewWindowMask, + transparent, + ); + style_mask.set( + NSWindowStyleMask::NSTexturedBackgroundWindowMask, + transparent, + ); + style_mask.set( + NSWindowStyleMask::NSUnifiedTitleAndToolbarWindowMask, + transparent && large, + ); + id.setStyleMask_(style_mask); - if large { - self.set_toolbar(true); - } + if large { + self.set_toolbar(true); + } - id.setTitleVisibility_(if transparent { - NSWindowTitleVisibility::NSWindowTitleHidden - } else { - NSWindowTitleVisibility::NSWindowTitleVisible - }); + id.setTitleVisibility_(if transparent { + NSWindowTitleVisibility::NSWindowTitleHidden + } else { + NSWindowTitleVisibility::NSWindowTitleVisible + }); - id.setTitlebarAppearsTransparent_(if transparent { YES } else { NO }); - } - } + id.setTitlebarAppearsTransparent_(if transparent { YES } else { NO }); + } + } - #[cfg(target_os = "macos")] - fn fix_shadow(&self) { - use cocoa::appkit::NSWindow; + #[cfg(target_os = "macos")] + fn fix_shadow(&self) { + use cocoa::appkit::NSWindow; - unsafe { - let id = self.ns_window().unwrap() as cocoa::base::id; + unsafe { + let id = self.ns_window().unwrap() as cocoa::base::id; - println!("recomputing shadow for window {:?}", id.title()); + println!("recomputing shadow for window {:?}", id.title()); - id.invalidateShadow(); - } - } + id.invalidateShadow(); + } + } } diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 34a5757fc..efbb3361b 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -1,83 +1,83 @@ { - "package": { - "productName": "Spacedrive", - "version": "0.1.0" - }, - "build": { - "distDir": "../dist", - "devPath": "http://localhost:8001", - "beforeDevCommand": "", - "beforeBuildCommand": "" - }, - "tauri": { - "macOSPrivateApi": true, - "bundle": { - "active": true, - "targets": "all", - "identifier": "app.spacedrive.desktop", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], - "resources": [], - "externalBin": [], - "copyright": "Jamie Pine", - "shortDescription": "The Universal File Explorer", - "longDescription": "A cross-platform file explorer, powered by an open source virtual distributed filesystem.", - "deb": { - "depends": [], - "useBootstrapper": false - }, - "macOS": { - "frameworks": [], - "minimumSystemVersion": "", - "useBootstrapper": false, - "exceptionDomain": "", - "signingIdentity": null, - "entitlements": null - }, - "windows": { - "certificateThumbprint": null, - "digestAlgorithm": "sha256", - "timestampUrl": "" - } - }, - "updater": { - "active": false - }, - "allowlist": { - "all": true, - "protocol": { - "assetScope": ["*"] - }, - "dialog": { - "all": true, - "open": true, - "save": true - } - }, - "windows": [ - { - "title": "Spacedrive", - "width": 1200, - "height": 725, - "minWidth": 700, - "minHeight": 500, - "resizable": true, - "fullscreen": false, - "alwaysOnTop": false, - "focus": false, - "fileDropEnabled": false, - "decorations": true, - "transparent": true, - "center": true - } - ], - "security": { - "csp": "default-src asset: https://asset.localhost blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" - } - } + "package": { + "productName": "Spacedrive", + "version": "0.1.0" + }, + "build": { + "distDir": "../dist", + "devPath": "http://localhost:8001", + "beforeDevCommand": "", + "beforeBuildCommand": "" + }, + "tauri": { + "macOSPrivateApi": true, + "bundle": { + "active": true, + "targets": "all", + "identifier": "app.spacedrive.desktop", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "resources": [], + "externalBin": [], + "copyright": "Jamie Pine", + "shortDescription": "The Universal File Explorer", + "longDescription": "A cross-platform file explorer, powered by an open source virtual distributed filesystem.", + "deb": { + "depends": [], + "useBootstrapper": false + }, + "macOS": { + "frameworks": [], + "minimumSystemVersion": "", + "useBootstrapper": false, + "exceptionDomain": "", + "signingIdentity": null, + "entitlements": null + }, + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "" + } + }, + "updater": { + "active": false + }, + "allowlist": { + "all": true, + "protocol": { + "assetScope": ["*"] + }, + "dialog": { + "all": true, + "open": true, + "save": true + } + }, + "windows": [ + { + "title": "Spacedrive", + "width": 1200, + "height": 725, + "minWidth": 700, + "minHeight": 500, + "resizable": true, + "fullscreen": false, + "alwaysOnTop": false, + "focus": false, + "fileDropEnabled": false, + "decorations": true, + "transparent": true, + "center": true + } + ], + "security": { + "csp": "default-src asset: https://asset.localhost blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" + } + } } diff --git a/apps/desktop/src-tauri/tauri.linux.conf.json b/apps/desktop/src-tauri/tauri.linux.conf.json index 8ab285e3d..16a6e4a2d 100644 --- a/apps/desktop/src-tauri/tauri.linux.conf.json +++ b/apps/desktop/src-tauri/tauri.linux.conf.json @@ -1,74 +1,74 @@ { - "package": { - "productName": "Spacedrive", - "version": "0.1.0" - }, - "build": { - "distDir": "../dist", - "devPath": "http://localhost:8001", - "beforeDevCommand": "", - "beforeBuildCommand": "" - }, - "tauri": { - "bundle": { - "active": true, - "targets": "all", - "identifier": "co.spacedrive.desktop", - "icon": ["icons/icon.icns"], - "resources": [], - "externalBin": [], - "copyright": "Jamie Pine", - "shortDescription": "Your personal virtual cloud.", - "longDescription": "Spacedrive is an open source virtual filesystem, a personal cloud powered by your everyday devices. Feature-rich benefits of the cloud, only its owned and hosted by you with security, privacy and ownership as a foundation. Spacedrive makes it possible to create a limitless directory of your digital life that will stand the test of time.", - "deb": { - "depends": [], - "useBootstrapper": false - }, - "macOS": { - "frameworks": [], - "minimumSystemVersion": "", - "useBootstrapper": false, - "exceptionDomain": "", - "signingIdentity": null, - "entitlements": null - }, - "windows": { - "certificateThumbprint": null, - "digestAlgorithm": "sha256", - "timestampUrl": "" - } - }, - "updater": { - "active": false - }, - "allowlist": { - "all": true, - "os": { - "all": true - }, - "dialog": { - "all": true, - "open": true, - "save": true - } - }, - "windows": [ - { - "title": "Spacedrive", - "width": 1250, - "height": 625, - "resizable": true, - "fullscreen": false, - "alwaysOnTop": false, - "focus": true, - "fileDropEnabled": false, - "decorations": true, - "transparent": false, - "center": true - } - ], - "security": { - "csp": "default-src asset: blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" - } - } + "package": { + "productName": "Spacedrive", + "version": "0.1.0" + }, + "build": { + "distDir": "../dist", + "devPath": "http://localhost:8001", + "beforeDevCommand": "", + "beforeBuildCommand": "" + }, + "tauri": { + "bundle": { + "active": true, + "targets": "all", + "identifier": "co.spacedrive.desktop", + "icon": ["icons/icon.icns"], + "resources": [], + "externalBin": [], + "copyright": "Jamie Pine", + "shortDescription": "Your personal virtual cloud.", + "longDescription": "Spacedrive is an open source virtual filesystem, a personal cloud powered by your everyday devices. Feature-rich benefits of the cloud, only its owned and hosted by you with security, privacy and ownership as a foundation. Spacedrive makes it possible to create a limitless directory of your digital life that will stand the test of time.", + "deb": { + "depends": [], + "useBootstrapper": false + }, + "macOS": { + "frameworks": [], + "minimumSystemVersion": "", + "useBootstrapper": false, + "exceptionDomain": "", + "signingIdentity": null, + "entitlements": null + }, + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "" + } + }, + "updater": { + "active": false + }, + "allowlist": { + "all": true, + "os": { + "all": true + }, + "dialog": { + "all": true, + "open": true, + "save": true + } + }, + "windows": [ + { + "title": "Spacedrive", + "width": 1250, + "height": 625, + "resizable": true, + "fullscreen": false, + "alwaysOnTop": false, + "focus": true, + "fileDropEnabled": false, + "decorations": true, + "transparent": false, + "center": true + } + ], + "security": { + "csp": "default-src asset: blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" + } + } } diff --git a/apps/desktop/src/index.html b/apps/desktop/src/index.html index f493787ea..4d441a0c7 100644 --- a/apps/desktop/src/index.html +++ b/apps/desktop/src/index.html @@ -1,13 +1,13 @@ - - - - - Spacedrive - - -

- - + + + + + Spacedrive + + +
+ + diff --git a/apps/desktop/src/index.tsx b/apps/desktop/src/index.tsx index 34835a780..8c60d8351 100644 --- a/apps/desktop/src/index.tsx +++ b/apps/desktop/src/index.tsx @@ -1,93 +1,94 @@ -import React, { useEffect, useState } from 'react'; -import { createRoot } from 'react-dom/client'; -// import Spacedrive interface -import SpacedriveInterface, { Platform } from '@sd/interface'; -import { listen, Event } from '@tauri-apps/api/event'; -// import types from Spacedrive core (TODO: re-export from client would be cleaner) -import { ClientCommand, ClientQuery, CoreEvent } from '@sd/core'; // import Spacedrive JS client import { BaseTransport } from '@sd/client'; +// import types from Spacedrive core (TODO: re-export from client would be cleaner) +import { ClientCommand, ClientQuery, CoreEvent } from '@sd/core'; +// import Spacedrive interface +import SpacedriveInterface, { Platform } from '@sd/interface'; // import tauri apis import { dialog, invoke, os, shell } from '@tauri-apps/api'; +import { Event, listen } from '@tauri-apps/api/event'; import { convertFileSrc } from '@tauri-apps/api/tauri'; -import '@sd/ui/style'; import { appWindow } from '@tauri-apps/api/window'; +import React, { useEffect, useState } from 'react'; +import { createRoot } from 'react-dom/client'; + +import '@sd/ui/style'; // bind state to core via Tauri class Transport extends BaseTransport { - constructor() { - super(); + constructor() { + super(); - listen('core_event', (e: Event) => { - this.emit('core_event', e.payload); - }); - } - async query(query: ClientQuery) { - return await invoke('client_query_transport', { data: query }); - } - async command(query: ClientCommand) { - return await invoke('client_command_transport', { data: query }); - } + listen('core_event', (e: Event) => { + this.emit('core_event', e.payload); + }); + } + async query(query: ClientQuery) { + return await invoke('client_query_transport', { data: query }); + } + async command(query: ClientCommand) { + return await invoke('client_command_transport', { data: query }); + } } function App() { - function getPlatform(platform: string): Platform { - switch (platform) { - case 'darwin': - return 'macOS'; - case 'win32': - return 'windows'; - case 'linux': - return 'linux'; - default: - return 'browser'; - } - } + function getPlatform(platform: string): Platform { + switch (platform) { + case 'darwin': + return 'macOS'; + case 'win32': + return 'windows'; + case 'linux': + return 'linux'; + default: + return 'browser'; + } + } - const [platform, setPlatform] = useState('macOS'); - const [focused, setFocused] = useState(true); + const [platform, setPlatform] = useState('macOS'); + const [focused, setFocused] = useState(true); - useEffect(() => { - os.platform().then((platform) => setPlatform(getPlatform(platform))); - invoke('app_ready'); - }, []); + useEffect(() => { + os.platform().then((platform) => setPlatform(getPlatform(platform))); + invoke('app_ready'); + }, []); - useEffect(() => { - const unlistenFocus = listen('tauri://focus', () => setFocused(true)); - const unlistenBlur = listen('tauri://blur', () => setFocused(false)); + useEffect(() => { + const unlistenFocus = listen('tauri://focus', () => setFocused(true)); + const unlistenBlur = listen('tauri://blur', () => setFocused(false)); - return () => { - unlistenFocus.then((unlisten) => unlisten()); - unlistenBlur.then((unlisten) => unlisten()); - }; - }, []); + return () => { + unlistenFocus.then((unlisten) => unlisten()); + unlistenBlur.then((unlisten) => unlisten()); + }; + }, []); - return ( - { - return dialog.open(options); - }} - isFocused={focused} - onClose={() => appWindow.close()} - onFullscreen={() => appWindow.setFullscreen(true)} - onMinimize={() => appWindow.minimize()} - onOpen={(path: string) => shell.open(path)} - /> - ); + return ( + { + return dialog.open(options); + }} + isFocused={focused} + onClose={() => appWindow.close()} + onFullscreen={() => appWindow.setFullscreen(true)} + onMinimize={() => appWindow.minimize()} + onOpen={(path: string) => shell.open(path)} + /> + ); } const root = createRoot(document.getElementById('root')!); root.render( - - - + + + ); diff --git a/apps/desktop/src/vite-env.d.ts b/apps/desktop/src/vite-env.d.ts index 04e148c9d..16334d7f0 100644 --- a/apps/desktop/src/vite-env.d.ts +++ b/apps/desktop/src/vite-env.d.ts @@ -1,7 +1,7 @@ /// declare interface ImportMetaEnv { - VITE_OS: string; + VITE_OS: string; } declare module '@babel/core' {} diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index c44e3731e..168f12434 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../packages/config/interface.tsconfig.json", - "compilerOptions": {}, - "include": ["src"] + "extends": "../../packages/config/interface.tsconfig.json", + "compilerOptions": {}, + "include": ["src"] } diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index d669f544a..7c6347331 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -1,27 +1,28 @@ -import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; +import svg from 'vite-plugin-svgr'; + import { name, version } from './package.json'; -import svg from "vite-plugin-svgr" // https://vitejs.dev/config/ export default defineConfig({ - server: { - port: 8001 - }, - plugins: [ - //@ts-ignore - react({ - jsxRuntime: 'classic' - }), - svg({ svgrOptions: { icon: true } }) - ], - root: 'src', - publicDir: '../../packages/interface/src/assets', - define: { - pkgJson: { name, version } - }, - build: { - outDir: '../dist', - assetsDir: '.' - } + server: { + port: 8001 + }, + plugins: [ + //@ts-ignore + react({ + jsxRuntime: 'classic' + }), + svg({ svgrOptions: { icon: true } }) + ], + root: 'src', + publicDir: '../../packages/interface/src/assets', + define: { + pkgJson: { name, version } + }, + build: { + outDir: '../dist', + assetsDir: '.' + } }); diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 84d6df566..82e4e7fec 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -1,6 +1,6 @@ { - "name": "mobile", - "version": "0.0.0", - "main": "index.js", - "license": "MIT" + "name": "mobile", + "version": "0.0.0", + "main": "index.js", + "license": "MIT" } diff --git a/apps/server/package.json b/apps/server/package.json index c9212ac1e..0dc984fc1 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { - "name": "@sd/server", - "version": "0.0.0", - "main": "index.js", - "license": "MIT" + "name": "@sd/server", + "version": "0.0.0", + "main": "index.js", + "license": "MIT" } diff --git a/apps/web/package.json b/apps/web/package.json index aa153cdb1..5bf57d074 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,31 +1,31 @@ { - "name": "@sd/web", - "private": true, - "version": "0.0.0", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "@fontsource/inter": "^4.5.7", - "@sd/client": "*", - "@sd/core": "*", - "@sd/interface": "*", - "@sd/ui": "*", - "react": "^18.0.0", - "react-dom": "^18.0.0" - }, - "devDependencies": { - "@types/react": "^18.0.8", - "@types/react-dom": "^18.0.0", - "@vitejs/plugin-react": "^1.3.1", - "autoprefixer": "^10.4.4", - "postcss": "^8.4.12", - "tailwind": "^4.0.0", - "typescript": "^4.6.3", - "vite": "^2.9.5", - "vite-plugin-svgr": "^1.1.0", - "vite-plugin-tsconfig-paths": "^1.0.5" - } + "name": "@sd/web", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@fontsource/inter": "^4.5.7", + "@sd/client": "*", + "@sd/core": "*", + "@sd/interface": "*", + "@sd/ui": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "devDependencies": { + "@types/react": "^18.0.8", + "@types/react-dom": "^18.0.0", + "@vitejs/plugin-react": "^1.3.1", + "autoprefixer": "^10.4.4", + "postcss": "^8.4.12", + "tailwind": "^4.0.0", + "typescript": "^4.6.3", + "vite": "^2.9.5", + "vite-plugin-svgr": "^1.1.0", + "vite-plugin-tsconfig-paths": "^1.0.5" + } } diff --git a/apps/web/public/manifest.json b/apps/web/public/manifest.json index 6a73e4bdc..a5d9bf516 100644 --- a/apps/web/public/manifest.json +++ b/apps/web/public/manifest.json @@ -1,25 +1,25 @@ { - "short_name": "Spacedrive", - "name": "Spacedrive", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" + "short_name": "Spacedrive", + "name": "Spacedrive", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" } diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index c471715c4..20df5a4b5 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,8 +1,7 @@ -import React, { useEffect } from 'react'; - -import SpacedriveInterface from '@sd/interface'; -import { ClientCommand, ClientQuery, CoreEvent } from '@sd/core'; import { BaseTransport } from '@sd/client'; +import { ClientCommand, ClientQuery, CoreEvent } from '@sd/core'; +import SpacedriveInterface from '@sd/interface'; +import React, { useEffect } from 'react'; const websocket = new WebSocket(import.meta.env.VITE_SDSERVER_BASE_URL || 'ws://localhost:8080/ws'); @@ -10,83 +9,83 @@ const randomId = () => Math.random().toString(36).slice(2); // bind state to core via Tauri class Transport extends BaseTransport { - requestMap = new Map void>(); + requestMap = new Map void>(); - constructor() { - super(); + constructor() { + super(); - websocket.addEventListener('message', (event) => { - if (!event.data) return; + websocket.addEventListener('message', (event) => { + if (!event.data) return; - const { id, payload } = JSON.parse(event.data); + const { id, payload } = JSON.parse(event.data); - const { type, data } = payload; - if (type === 'event') { - this.emit('core_event', data); - } else if (type === 'query' || type === 'command') { - if (this.requestMap.has(id)) { - this.requestMap.get(id)?.(data); - this.requestMap.delete(id); - } - } - }); - } - async query(query: ClientQuery) { - const id = randomId(); - let resolve: (data: any) => void; + const { type, data } = payload; + if (type === 'event') { + this.emit('core_event', data); + } else if (type === 'query' || type === 'command') { + if (this.requestMap.has(id)) { + this.requestMap.get(id)?.(data); + this.requestMap.delete(id); + } + } + }); + } + async query(query: ClientQuery) { + const id = randomId(); + let resolve: (data: any) => void; - const promise = new Promise((res) => { - resolve = res; - }); + const promise = new Promise((res) => { + resolve = res; + }); - // @ts-ignore - this.requestMap.set(id, resolve); + // @ts-ignore + this.requestMap.set(id, resolve); - websocket.send(JSON.stringify({ id, payload: { type: 'query', data: query } })); + websocket.send(JSON.stringify({ id, payload: { type: 'query', data: query } })); - return await promise; - } - async command(command: ClientCommand) { - const id = randomId(); - let resolve: (data: any) => void; + return await promise; + } + async command(command: ClientCommand) { + const id = randomId(); + let resolve: (data: any) => void; - const promise = new Promise((res) => { - resolve = res; - }); + const promise = new Promise((res) => { + resolve = res; + }); - // @ts-ignore - this.requestMap.set(id, resolve); + // @ts-ignore + this.requestMap.set(id, resolve); - websocket.send(JSON.stringify({ id, payload: { type: 'command', data: command } })); + websocket.send(JSON.stringify({ id, payload: { type: 'command', data: command } })); - return await promise; - } + return await promise; + } } function App() { - useEffect(() => { - window.parent.postMessage('spacedrive-hello', '*'); - }, []); + useEffect(() => { + window.parent.postMessage('spacedrive-hello', '*'); + }, []); - return ( -
- {/*
*/} - { - return Promise.resolve([]); - }} - /> -
- ); + return ( +
+ {/*
*/} + { + return Promise.resolve([]); + }} + /> +
+ ); } export default App; diff --git a/apps/web/src/env.d.ts b/apps/web/src/env.d.ts index be63506a6..06e6e15b4 100644 --- a/apps/web/src/env.d.ts +++ b/apps/web/src/env.d.ts @@ -1,9 +1,9 @@ /// interface ImportMetaEnv { - readonly VITE_SDSERVER_BASE_URL: string; + readonly VITE_SDSERVER_BASE_URL: string; } interface ImportMeta { - readonly env: ImportMetaEnv; + readonly env: ImportMetaEnv; } diff --git a/apps/web/src/index.html b/apps/web/src/index.html index 66a5bb247..6237f0bed 100644 --- a/apps/web/src/index.html +++ b/apps/web/src/index.html @@ -1,12 +1,12 @@ - - - Spacedrive - - - -
- - + + + Spacedrive + + + +
+ + diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx index b18b0963a..612be37fb 100644 --- a/apps/web/src/index.tsx +++ b/apps/web/src/index.tsx @@ -1,11 +1,13 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import App from './App'; + import '@sd/ui/style'; +import App from './App'; + const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( - - - + + + ); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index c44e3731e..168f12434 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../packages/config/interface.tsconfig.json", - "compilerOptions": {}, - "include": ["src"] + "extends": "../../packages/config/interface.tsconfig.json", + "compilerOptions": {}, + "include": ["src"] } diff --git a/apps/web/vercel.json b/apps/web/vercel.json index 3a48e56ba..8f728db13 100644 --- a/apps/web/vercel.json +++ b/apps/web/vercel.json @@ -1,3 +1,3 @@ { - "rewrites": [{ "source": "/(.*)", "destination": "/" }] + "rewrites": [{ "source": "/(.*)", "destination": "/" }] } diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 8e0b71c72..3f946dc2d 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; import svg from 'vite-plugin-svgr'; import tsconfigPaths from 'vite-plugin-tsconfig-paths'; @@ -7,24 +7,24 @@ import { name, version } from './package.json'; // https://vitejs.dev/config/ export default defineConfig({ - server: { - port: 8002 - }, - plugins: [ - // @ts-ignore - react({ - jsxRuntime: 'classic' - }), - svg({ svgrOptions: { icon: true } }), - tsconfigPaths() - ], - root: 'src', - publicDir: '../../packages/interface/src/assets', - define: { - pkgJson: { name, version } - }, - build: { - outDir: '../dist', - assetsDir: '.' - } + server: { + port: 8002 + }, + plugins: [ + // @ts-ignore + react({ + jsxRuntime: 'classic' + }), + svg({ svgrOptions: { icon: true } }), + tsconfigPaths() + ], + root: 'src', + publicDir: '../../packages/interface/src/assets', + define: { + pkgJson: { name, version } + }, + build: { + outDir: '../dist', + assetsDir: '.' + } }); diff --git a/core/.rustfmt.toml b/core/.rustfmt.toml index 3901c3cce..411a5f052 100644 --- a/core/.rustfmt.toml +++ b/core/.rustfmt.toml @@ -1,6 +1,5 @@ max_width = 100 -hard_tabs = false -tab_spaces = 2 +hard_tabs = true newline_style = "Unix" use_small_heuristics = "Default" reorder_imports = true diff --git a/core/bindings/Client.ts b/core/bindings/Client.ts index 2c0d9f32f..4660f7e8d 100644 --- a/core/bindings/Client.ts +++ b/core/bindings/Client.ts @@ -1,3 +1,10 @@ -import type { Platform } from "./Platform"; +import type { Platform } from './Platform'; -export interface Client { uuid: string, name: string, platform: Platform, tcp_address: string, last_seen: string, last_synchronized: string, } \ No newline at end of file +export interface Client { + uuid: string; + name: string; + platform: Platform; + tcp_address: string; + last_seen: string; + last_synchronized: string; +} diff --git a/core/bindings/ClientCommand.ts b/core/bindings/ClientCommand.ts index d6b37840c..3ff84de41 100644 --- a/core/bindings/ClientCommand.ts +++ b/core/bindings/ClientCommand.ts @@ -1,2 +1,14 @@ - -export type ClientCommand = { key: "FileRead", params: { id: number, } } | { key: "FileDelete", params: { id: number, } } | { key: "LibDelete", params: { id: number, } } | { key: "TagCreate", params: { name: string, color: string, } } | { key: "TagUpdate", params: { name: string, color: string, } } | { key: "TagAssign", params: { file_id: number, tag_id: number, } } | { key: "TagDelete", params: { id: number, } } | { key: "LocCreate", params: { path: string, } } | { key: "LocUpdate", params: { id: number, name: string | null, } } | { key: "LocDelete", params: { id: number, } } | { key: "SysVolumeUnmount", params: { id: number, } } | { key: "GenerateThumbsForLocation", params: { id: number, path: string, } } | { key: "IdentifyUniqueFiles" }; \ No newline at end of file +export type ClientCommand = + | { key: 'FileRead'; params: { id: number } } + | { key: 'FileDelete'; params: { id: number } } + | { key: 'LibDelete'; params: { id: number } } + | { key: 'TagCreate'; params: { name: string; color: string } } + | { key: 'TagUpdate'; params: { name: string; color: string } } + | { key: 'TagAssign'; params: { file_id: number; tag_id: number } } + | { key: 'TagDelete'; params: { id: number } } + | { key: 'LocCreate'; params: { path: string } } + | { key: 'LocUpdate'; params: { id: number; name: string | null } } + | { key: 'LocDelete'; params: { id: number } } + | { key: 'SysVolumeUnmount'; params: { id: number } } + | { key: 'GenerateThumbsForLocation'; params: { id: number; path: string } } + | { key: 'IdentifyUniqueFiles' }; diff --git a/core/bindings/ClientQuery.ts b/core/bindings/ClientQuery.ts index 21439f03c..67c0f608d 100644 --- a/core/bindings/ClientQuery.ts +++ b/core/bindings/ClientQuery.ts @@ -1,2 +1,10 @@ - -export type ClientQuery = { key: "ClientGetState" } | { key: "SysGetVolumes" } | { key: "LibGetTags" } | { key: "JobGetRunning" } | { key: "JobGetHistory" } | { key: "SysGetLocations" } | { key: "SysGetLocation", params: { id: number, } } | { key: "LibGetExplorerDir", params: { location_id: number, path: string, limit: number, } } | { key: "GetLibraryStatistics" }; \ No newline at end of file +export type ClientQuery = + | { key: 'ClientGetState' } + | { key: 'SysGetVolumes' } + | { key: 'LibGetTags' } + | { key: 'JobGetRunning' } + | { key: 'JobGetHistory' } + | { key: 'SysGetLocations' } + | { key: 'SysGetLocation'; params: { id: number } } + | { key: 'LibGetExplorerDir'; params: { location_id: number; path: string; limit: number } } + | { key: 'GetLibraryStatistics' }; diff --git a/core/bindings/ClientState.ts b/core/bindings/ClientState.ts index 34d84b404..ce5c2d7af 100644 --- a/core/bindings/ClientState.ts +++ b/core/bindings/ClientState.ts @@ -1,3 +1,11 @@ -import type { LibraryState } from "./LibraryState"; +import type { LibraryState } from './LibraryState'; -export interface ClientState { client_uuid: string, client_id: number, client_name: string, data_path: string, tcp_port: number, libraries: Array, current_library_uuid: string, } \ No newline at end of file +export interface ClientState { + client_uuid: string; + client_id: number; + client_name: string; + data_path: string; + tcp_port: number; + libraries: Array; + current_library_uuid: string; +} diff --git a/core/bindings/CoreEvent.ts b/core/bindings/CoreEvent.ts index 043bfa562..ac9b9d2b8 100644 --- a/core/bindings/CoreEvent.ts +++ b/core/bindings/CoreEvent.ts @@ -1,4 +1,10 @@ -import type { ClientQuery } from "./ClientQuery"; -import type { CoreResource } from "./CoreResource"; +import type { ClientQuery } from './ClientQuery'; +import type { CoreResource } from './CoreResource'; -export type CoreEvent = { key: "InvalidateQuery", data: ClientQuery } | { key: "InvalidateQueryDebounced", data: ClientQuery } | { key: "InvalidateResource", data: CoreResource } | { key: "NewThumbnail", data: { cas_id: string, } } | { key: "Log", data: { message: string, } } | { key: "DatabaseDisconnected", data: { reason: string | null, } }; \ No newline at end of file +export type CoreEvent = + | { key: 'InvalidateQuery'; data: ClientQuery } + | { key: 'InvalidateQueryDebounced'; data: ClientQuery } + | { key: 'InvalidateResource'; data: CoreResource } + | { key: 'NewThumbnail'; data: { cas_id: string } } + | { key: 'Log'; data: { message: string } } + | { key: 'DatabaseDisconnected'; data: { reason: string | null } }; diff --git a/core/bindings/CoreResource.ts b/core/bindings/CoreResource.ts index 3c2f70c91..0ae2fc774 100644 --- a/core/bindings/CoreResource.ts +++ b/core/bindings/CoreResource.ts @@ -1,5 +1,11 @@ -import type { File } from "./File"; -import type { JobReport } from "./JobReport"; -import type { LocationResource } from "./LocationResource"; +import type { File } from './File'; +import type { JobReport } from './JobReport'; +import type { LocationResource } from './LocationResource'; -export type CoreResource = "Client" | "Library" | { Location: LocationResource } | { File: File } | { Job: JobReport } | "Tag"; \ No newline at end of file +export type CoreResource = + | 'Client' + | 'Library' + | { Location: LocationResource } + | { File: File } + | { Job: JobReport } + | 'Tag'; diff --git a/core/bindings/CoreResponse.ts b/core/bindings/CoreResponse.ts index 0efa68e79..04c755350 100644 --- a/core/bindings/CoreResponse.ts +++ b/core/bindings/CoreResponse.ts @@ -1,8 +1,18 @@ -import type { ClientState } from "./ClientState"; -import type { DirectoryWithContents } from "./DirectoryWithContents"; -import type { JobReport } from "./JobReport"; -import type { LocationResource } from "./LocationResource"; -import type { Statistics } from "./Statistics"; -import type { Volume } from "./Volume"; +import type { ClientState } from './ClientState'; +import type { DirectoryWithContents } from './DirectoryWithContents'; +import type { JobReport } from './JobReport'; +import type { LocationResource } from './LocationResource'; +import type { Statistics } from './Statistics'; +import type { Volume } from './Volume'; -export type CoreResponse = { key: "Success", data: null } | { key: "SysGetVolumes", data: Array } | { key: "SysGetLocation", data: LocationResource } | { key: "SysGetLocations", data: Array } | { key: "LibGetExplorerDir", data: DirectoryWithContents } | { key: "ClientGetState", data: ClientState } | { key: "LocCreate", data: LocationResource } | { key: "JobGetRunning", data: Array } | { key: "JobGetHistory", data: Array } | { key: "GetLibraryStatistics", data: Statistics }; \ No newline at end of file +export type CoreResponse = + | { key: 'Success'; data: null } + | { key: 'SysGetVolumes'; data: Array } + | { key: 'SysGetLocation'; data: LocationResource } + | { key: 'SysGetLocations'; data: Array } + | { key: 'LibGetExplorerDir'; data: DirectoryWithContents } + | { key: 'ClientGetState'; data: ClientState } + | { key: 'LocCreate'; data: LocationResource } + | { key: 'JobGetRunning'; data: Array } + | { key: 'JobGetHistory'; data: Array } + | { key: 'GetLibraryStatistics'; data: Statistics }; diff --git a/core/bindings/DirectoryWithContents.ts b/core/bindings/DirectoryWithContents.ts index 89c0fb64f..b8af1220c 100644 --- a/core/bindings/DirectoryWithContents.ts +++ b/core/bindings/DirectoryWithContents.ts @@ -1,3 +1,6 @@ -import type { FilePath } from "./FilePath"; +import type { FilePath } from './FilePath'; -export interface DirectoryWithContents { directory: FilePath, contents: Array, } \ No newline at end of file +export interface DirectoryWithContents { + directory: FilePath; + contents: Array; +} diff --git a/core/bindings/EncryptionAlgorithm.ts b/core/bindings/EncryptionAlgorithm.ts index d5b7d9889..d38357bd2 100644 --- a/core/bindings/EncryptionAlgorithm.ts +++ b/core/bindings/EncryptionAlgorithm.ts @@ -1,2 +1 @@ - -export type EncryptionAlgorithm = "None" | "AES128" | "AES192" | "AES256"; \ No newline at end of file +export type EncryptionAlgorithm = 'None' | 'AES128' | 'AES192' | 'AES256'; diff --git a/core/bindings/File.ts b/core/bindings/File.ts index 81017797f..6260db13d 100644 --- a/core/bindings/File.ts +++ b/core/bindings/File.ts @@ -1,5 +1,24 @@ -import type { EncryptionAlgorithm } from "./EncryptionAlgorithm"; -import type { FileKind } from "./FileKind"; -import type { FilePath } from "./FilePath"; +import type { EncryptionAlgorithm } from './EncryptionAlgorithm'; +import type { FileKind } from './FileKind'; +import type { FilePath } from './FilePath'; -export interface File { id: number, cas_id: string, integrity_checksum: string | null, size_in_bytes: string, kind: FileKind, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, encryption: EncryptionAlgorithm, ipfs_id: string | null, comment: string | null, date_created: string, date_modified: string, date_indexed: string, paths: Array, } \ No newline at end of file +export interface File { + id: number; + cas_id: string; + integrity_checksum: string | null; + size_in_bytes: string; + kind: FileKind; + hidden: boolean; + favorite: boolean; + important: boolean; + has_thumbnail: boolean; + has_thumbstrip: boolean; + has_video_preview: boolean; + encryption: EncryptionAlgorithm; + ipfs_id: string | null; + comment: string | null; + date_created: string; + date_modified: string; + date_indexed: string; + paths: Array; +} diff --git a/core/bindings/FileKind.ts b/core/bindings/FileKind.ts index 1e0ec7b6c..90dbc6630 100644 --- a/core/bindings/FileKind.ts +++ b/core/bindings/FileKind.ts @@ -1,2 +1,10 @@ - -export type FileKind = "Unknown" | "Directory" | "Package" | "Archive" | "Image" | "Video" | "Audio" | "Plaintext" | "Alias"; \ No newline at end of file +export type FileKind = + | 'Unknown' + | 'Directory' + | 'Package' + | 'Archive' + | 'Image' + | 'Video' + | 'Audio' + | 'Plaintext' + | 'Alias'; diff --git a/core/bindings/FilePath.ts b/core/bindings/FilePath.ts index 33626e50a..5a6d49ee1 100644 --- a/core/bindings/FilePath.ts +++ b/core/bindings/FilePath.ts @@ -1,2 +1,16 @@ - -export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, temp_cas_id: string | null, has_local_thumbnail: boolean, date_created: string, date_modified: string, date_indexed: string, permissions: string | null, } \ No newline at end of file +export interface FilePath { + id: number; + is_dir: boolean; + location_id: number; + materialized_path: string; + name: string; + extension: string | null; + file_id: number | null; + parent_id: number | null; + temp_cas_id: string | null; + has_local_thumbnail: boolean; + date_created: string; + date_modified: string; + date_indexed: string; + permissions: string | null; +} diff --git a/core/bindings/JobReport.ts b/core/bindings/JobReport.ts index a2adc7462..1b805c687 100644 --- a/core/bindings/JobReport.ts +++ b/core/bindings/JobReport.ts @@ -1,3 +1,12 @@ -import type { JobStatus } from "./JobStatus"; +import type { JobStatus } from './JobStatus'; -export interface JobReport { id: string, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: string, } \ No newline at end of file +export interface JobReport { + id: string; + date_created: string; + date_modified: string; + status: JobStatus; + task_count: number; + completed_task_count: number; + message: string; + seconds_elapsed: string; +} diff --git a/core/bindings/JobStatus.ts b/core/bindings/JobStatus.ts index 8fc11c5dc..8cfc3f318 100644 --- a/core/bindings/JobStatus.ts +++ b/core/bindings/JobStatus.ts @@ -1,2 +1 @@ - -export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed"; \ No newline at end of file +export type JobStatus = 'Queued' | 'Running' | 'Completed' | 'Canceled' | 'Failed'; diff --git a/core/bindings/LibraryState.ts b/core/bindings/LibraryState.ts index 17acd81a2..f58dd547e 100644 --- a/core/bindings/LibraryState.ts +++ b/core/bindings/LibraryState.ts @@ -1,2 +1,6 @@ - -export interface LibraryState { library_uuid: string, library_id: number, library_path: string, offline: boolean, } \ No newline at end of file +export interface LibraryState { + library_uuid: string; + library_id: number; + library_path: string; + offline: boolean; +} diff --git a/core/bindings/LocationResource.ts b/core/bindings/LocationResource.ts index 70bd1e15a..ce74ad548 100644 --- a/core/bindings/LocationResource.ts +++ b/core/bindings/LocationResource.ts @@ -1,2 +1,10 @@ - -export interface LocationResource { id: number, name: string | null, path: string | null, total_capacity: number | null, available_capacity: number | null, is_removable: boolean | null, is_online: boolean, date_created: string, } \ No newline at end of file +export interface LocationResource { + id: number; + name: string | null; + path: string | null; + total_capacity: number | null; + available_capacity: number | null; + is_removable: boolean | null; + is_online: boolean; + date_created: string; +} diff --git a/core/bindings/Platform.ts b/core/bindings/Platform.ts index a25022b64..e460baab5 100644 --- a/core/bindings/Platform.ts +++ b/core/bindings/Platform.ts @@ -1,2 +1 @@ - -export type Platform = "Unknown" | "Windows" | "MacOS" | "Linux" | "IOS" | "Android"; \ No newline at end of file +export type Platform = 'Unknown' | 'Windows' | 'MacOS' | 'Linux' | 'IOS' | 'Android'; diff --git a/core/bindings/Statistics.ts b/core/bindings/Statistics.ts index d3af91139..76147c3c7 100644 --- a/core/bindings/Statistics.ts +++ b/core/bindings/Statistics.ts @@ -1,2 +1,9 @@ - -export interface Statistics { total_file_count: number, total_bytes_used: string, total_bytes_capacity: string, total_bytes_free: string, total_unique_bytes: string, preview_media_bytes: string, library_db_size: string, } \ No newline at end of file +export interface Statistics { + total_file_count: number; + total_bytes_used: string; + total_bytes_capacity: string; + total_bytes_free: string; + total_unique_bytes: string; + preview_media_bytes: string; + library_db_size: string; +} diff --git a/core/bindings/Volume.ts b/core/bindings/Volume.ts index 4b59fefd3..493547009 100644 --- a/core/bindings/Volume.ts +++ b/core/bindings/Volume.ts @@ -1,2 +1,10 @@ - -export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean, } \ No newline at end of file +export interface Volume { + name: string; + mount_point: string; + total_capacity: bigint; + available_capacity: bigint; + is_removable: boolean; + disk_type: string | null; + file_system: string | null; + is_root_filesystem: boolean; +} diff --git a/core/derive/src/lib.rs b/core/derive/src/lib.rs index 6b77d73c6..2d107dab2 100644 --- a/core/derive/src/lib.rs +++ b/core/derive/src/lib.rs @@ -15,28 +15,28 @@ use syn::{parse_macro_input, Data, DeriveInput}; /// ``` #[proc_macro_derive(PropertyOperationApply)] pub fn property_operation_apply(input: TokenStream) -> TokenStream { - let DeriveInput { ident, data, .. } = parse_macro_input!(input); + let DeriveInput { ident, data, .. } = parse_macro_input!(input); - if let Data::Enum(data) = data { - let impls = data.variants.iter().map(|variant| { - let variant_ident = &variant.ident; - quote! { - #ident::#variant_ident(method) => method.apply(ctx), - } - }); + if let Data::Enum(data) = data { + let impls = data.variants.iter().map(|variant| { + let variant_ident = &variant.ident; + quote! { + #ident::#variant_ident(method) => method.apply(ctx), + } + }); - let expanded = quote! { - impl #ident { - fn apply(operation: CrdtCtx, ctx: self::engine::SyncContext) { - match operation.resource { - #(#impls)* - }; - } - } - }; + let expanded = quote! { + impl #ident { + fn apply(operation: CrdtCtx, ctx: self::engine::SyncContext) { + match operation.resource { + #(#impls)* + }; + } + } + }; - TokenStream::from(expanded) - } else { - panic!("The 'PropertyOperationApply' macro can only be used on enums!"); - } + TokenStream::from(expanded) + } else { + panic!("The 'PropertyOperationApply' macro can only be used on enums!"); + } } diff --git a/core/package.json b/core/package.json index b8d39544c..cdddf09f3 100644 --- a/core/package.json +++ b/core/package.json @@ -1,18 +1,18 @@ { - "name": "@sd/core", - "version": "0.0.0", - "main": "index.js", - "license": "MIT", - "scripts": { - "codegen": "cargo test && ts-node ./scripts/bindingsIndex.ts", - "build": "cargo build", - "test": "cargo test", - "test:log": "cargo test -- --nocapture", - "prisma": "cargo prisma" - }, - "devDependencies": { - "@types/node": "^17.0.23", - "ts-node": "^10.7.0", - "typescript": "^4.6.3" - } + "name": "@sd/core", + "version": "0.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "codegen": "cargo test && ts-node ./scripts/bindingsIndex.ts", + "build": "cargo build", + "test": "cargo test", + "test:log": "cargo test -- --nocapture", + "prisma": "cargo prisma" + }, + "devDependencies": { + "@types/node": "^17.0.23", + "ts-node": "^10.7.0", + "typescript": "^4.6.3" + } } diff --git a/core/prisma/src/main.rs b/core/prisma/src/main.rs index b873b532b..96e11d24f 100644 --- a/core/prisma/src/main.rs +++ b/core/prisma/src/main.rs @@ -1,3 +1,3 @@ fn main() { - prisma_client_rust_cli::run(); + prisma_client_rust_cli::run(); } diff --git a/core/scripts/bindingsIndex.ts b/core/scripts/bindingsIndex.ts index d7830e079..addea2bf6 100644 --- a/core/scripts/bindingsIndex.ts +++ b/core/scripts/bindingsIndex.ts @@ -2,29 +2,29 @@ import * as fs from 'fs/promises'; import * as path from 'path'; (async function main() { - async function exists(path: string) { - try { - await fs.access(path); - return true; - } catch { - return false; - } - } + async function exists(path: string) { + try { + await fs.access(path); + return true; + } catch { + return false; + } + } - const files = await fs.readdir(path.join(__dirname, '../bindings')); - const bindings = files.filter((f) => f.endsWith('.ts')); - let str = ''; - // str += `export * from './types';\n`; + const files = await fs.readdir(path.join(__dirname, '../bindings')); + const bindings = files.filter((f) => f.endsWith('.ts')); + let str = ''; + // str += `export * from './types';\n`; - for (let binding of bindings) { - str += `export * from './bindings/${binding.split('.')[0]}';\n`; - } + for (let binding of bindings) { + str += `export * from './bindings/${binding.split('.')[0]}';\n`; + } - let indexExists = await exists(path.join(__dirname, '../index.ts')); + let indexExists = await exists(path.join(__dirname, '../index.ts')); - if (indexExists) { - await fs.rm(path.join(__dirname, '../index.ts')); - } + if (indexExists) { + await fs.rm(path.join(__dirname, '../index.ts')); + } - await fs.writeFile(path.join(__dirname, '../index.ts'), str); + await fs.writeFile(path.join(__dirname, '../index.ts'), str); })(); diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 2729a411a..1cb2e094e 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -1,6 +1,6 @@ use crate::{ - prisma::{self, client}, - state, Core, CoreContext, + prisma::{self, client}, + state, Core, CoreContext, }; use chrono::{DateTime, Utc}; use int_enum::IntEnum; @@ -12,26 +12,26 @@ use ts_rs::TS; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts(export)] pub struct Client { - pub uuid: String, - pub name: String, - pub platform: Platform, - pub tcp_address: String, - #[ts(type = "string")] - pub last_seen: DateTime, - #[ts(type = "string")] - pub last_synchronized: DateTime, + pub uuid: String, + pub name: String, + pub platform: Platform, + pub tcp_address: String, + #[ts(type = "string")] + pub last_seen: DateTime, + #[ts(type = "string")] + pub last_synchronized: DateTime, } #[repr(i32)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, TS, Eq, PartialEq, IntEnum)] #[ts(export)] pub enum Platform { - Unknown = 0, - Windows = 1, - MacOS = 2, - Linux = 3, - IOS = 4, - Android = 5, + Unknown = 0, + Windows = 1, + MacOS = 2, + Linux = 3, + IOS = 4, + Android = 5, } // impl Into for client::Data { @@ -48,66 +48,66 @@ pub enum Platform { // } pub async fn create(core: &Core) -> Result<(), ClientError> { - println!("Creating client..."); - let mut config = state::client::get(); + println!("Creating client..."); + let mut config = state::client::get(); - let db = &core.database; + let db = &core.database; - let hostname = match hostname::get() { - Ok(hostname) => hostname.to_str().unwrap_or_default().to_owned(), - Err(_) => "unknown".to_owned(), - }; + let hostname = match hostname::get() { + Ok(hostname) => hostname.to_str().unwrap_or_default().to_owned(), + Err(_) => "unknown".to_owned(), + }; - let platform = match env::consts::OS { - "windows" => Platform::Windows, - "macos" => Platform::MacOS, - "linux" => Platform::Linux, - _ => Platform::Unknown, - }; + let platform = match env::consts::OS { + "windows" => Platform::Windows, + "macos" => Platform::MacOS, + "linux" => Platform::Linux, + _ => Platform::Unknown, + }; - let client = match db - .client() - .find_unique(client::pub_id::equals(config.client_uuid.clone())) - .exec() - .await? - { - Some(client) => client, - None => { - db.client() - .create( - client::pub_id::set(config.client_uuid.clone()), - client::name::set(hostname.clone()), - vec![ - client::platform::set(platform as i32), - client::online::set(Some(true)), - ], - ) - .exec() - .await? - } - }; + let client = match db + .client() + .find_unique(client::pub_id::equals(config.client_uuid.clone())) + .exec() + .await? + { + Some(client) => client, + None => { + db.client() + .create( + client::pub_id::set(config.client_uuid.clone()), + client::name::set(hostname.clone()), + vec![ + client::platform::set(platform as i32), + client::online::set(Some(true)), + ], + ) + .exec() + .await? + } + }; - config.client_name = hostname; - config.client_id = client.id; - config.save(); + config.client_name = hostname; + config.client_id = client.id; + config.save(); - println!("Client: {:?}", &client); + println!("Client: {:?}", &client); - Ok(()) + Ok(()) } pub async fn get_clients(ctx: &CoreContext) -> Result, ClientError> { - let db = &ctx.database; + let db = &ctx.database; - let client = db.client().find_many(vec![]).exec().await?; + let client = db.client().find_many(vec![]).exec().await?; - Ok(client) + Ok(client) } #[derive(Error, Debug)] pub enum ClientError { - #[error("Database error")] - DatabaseError(#[from] prisma::QueryError), - #[error("Client not found error")] - ClientNotFound, + #[error("Database error")] + DatabaseError(#[from] prisma::QueryError), + #[error("Client not found error")] + ClientNotFound, } diff --git a/core/src/crypto/encryption.rs b/core/src/crypto/encryption.rs index 9771faf01..78b744561 100644 --- a/core/src/crypto/encryption.rs +++ b/core/src/crypto/encryption.rs @@ -6,8 +6,8 @@ use ts_rs::TS; #[derive(Debug, Clone, Copy, Serialize, Deserialize, TS, Eq, PartialEq, IntEnum)] #[ts(export)] pub enum EncryptionAlgorithm { - None = 0, - AES128 = 1, - AES192 = 2, - AES256 = 3, + None = 0, + AES128 = 1, + AES192 = 2, + AES256 = 3, } diff --git a/core/src/db/migrate.rs b/core/src/db/migrate.rs index f89265b24..c7b72f4fe 100644 --- a/core/src/db/migrate.rs +++ b/core/src/db/migrate.rs @@ -11,140 +11,142 @@ const INIT_MIGRATION: &str = include_str!("../../prisma/migrations/migration_tab static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/prisma/migrations"); pub fn sha256_digest(mut reader: R) -> Result { - let mut context = Context::new(&SHA256); - let mut buffer = [0; 1024]; - loop { - let count = reader.read(&mut buffer)?; - if count == 0 { - break; - } - context.update(&buffer[..count]); - } - Ok(context.finish()) + let mut context = Context::new(&SHA256); + let mut buffer = [0; 1024]; + loop { + let count = reader.read(&mut buffer)?; + if count == 0 { + break; + } + context.update(&buffer[..count]); + } + Ok(context.finish()) } pub async fn run_migrations(ctx: &CoreContext) -> Result<()> { - let client = &ctx.database; + let client = &ctx.database; - match client - ._query_raw::( - "SELECT name FROM sqlite_master WHERE type='table' AND name='_migrations'", - ) - .await - { - Ok(data) => { - if data.len() == 0 { - #[cfg(debug_assertions)] - println!("Migration table does not exist"); - // execute migration - match client._execute_raw(INIT_MIGRATION).await { - Ok(_) => {} - Err(e) => { - println!("Failed to create migration table: {}", e); - } - }; + match client + ._query_raw::( + "SELECT name FROM sqlite_master WHERE type='table' AND name='_migrations'", + ) + .await + { + Ok(data) => { + if data.len() == 0 { + #[cfg(debug_assertions)] + println!("Migration table does not exist"); + // execute migration + match client._execute_raw(INIT_MIGRATION).await { + Ok(_) => {} + Err(e) => { + println!("Failed to create migration table: {}", e); + } + }; - let value: Vec = client - ._query_raw("SELECT name FROM sqlite_master WHERE type='table' AND name='_migrations'") - .await - .unwrap(); + let value: Vec = client + ._query_raw( + "SELECT name FROM sqlite_master WHERE type='table' AND name='_migrations'", + ) + .await + .unwrap(); - #[cfg(debug_assertions)] - println!("Migration table created: {:?}", value); - } else { - #[cfg(debug_assertions)] - println!("Migration table exists: {:?}", data); - } + #[cfg(debug_assertions)] + println!("Migration table created: {:?}", value); + } else { + #[cfg(debug_assertions)] + println!("Migration table exists: {:?}", data); + } - let mut migration_subdirs = MIGRATIONS_DIR - .dirs() - .filter(|subdir| { - subdir - .path() - .file_name() - .map(|name| name != OsStr::new("migration_table")) - .unwrap_or(false) - }) - .collect::>(); + let mut migration_subdirs = MIGRATIONS_DIR + .dirs() + .filter(|subdir| { + subdir + .path() + .file_name() + .map(|name| name != OsStr::new("migration_table")) + .unwrap_or(false) + }) + .collect::>(); - migration_subdirs.sort_by(|a, b| { - let a_name = a.path().file_name().unwrap().to_str().unwrap(); - let b_name = b.path().file_name().unwrap().to_str().unwrap(); + migration_subdirs.sort_by(|a, b| { + let a_name = a.path().file_name().unwrap().to_str().unwrap(); + let b_name = b.path().file_name().unwrap().to_str().unwrap(); - let a_time = a_name[..14].parse::().unwrap(); - let b_time = b_name[..14].parse::().unwrap(); + let a_time = a_name[..14].parse::().unwrap(); + let b_time = b_name[..14].parse::().unwrap(); - a_time.cmp(&b_time) - }); + a_time.cmp(&b_time) + }); - for subdir in migration_subdirs { - println!("{:?}", subdir.path()); - let migration_file = subdir - .get_file(subdir.path().join("./migration.sql")) - .unwrap(); - let migration_sql = migration_file.contents_utf8().unwrap(); + for subdir in migration_subdirs { + println!("{:?}", subdir.path()); + let migration_file = subdir + .get_file(subdir.path().join("./migration.sql")) + .unwrap(); + let migration_sql = migration_file.contents_utf8().unwrap(); - let digest = sha256_digest(BufReader::new(migration_file.contents()))?; - // create a lowercase hash from - let checksum = HEXLOWER.encode(digest.as_ref()); - let name = subdir.path().file_name().unwrap().to_str().unwrap(); + let digest = sha256_digest(BufReader::new(migration_file.contents()))?; + // create a lowercase hash from + let checksum = HEXLOWER.encode(digest.as_ref()); + let name = subdir.path().file_name().unwrap().to_str().unwrap(); - // get existing migration by checksum, if it doesn't exist run the migration - let existing_migration = client - .migration() - .find_unique(migration::checksum::equals(checksum.clone())) - .exec() - .await?; + // get existing migration by checksum, if it doesn't exist run the migration + let existing_migration = client + .migration() + .find_unique(migration::checksum::equals(checksum.clone())) + .exec() + .await?; - if existing_migration.is_none() { - #[cfg(debug_assertions)] - println!("Running migration: {}", name); + if existing_migration.is_none() { + #[cfg(debug_assertions)] + println!("Running migration: {}", name); - let steps = migration_sql.split(";").collect::>(); - let steps = &steps[0..steps.len() - 1]; + let steps = migration_sql.split(";").collect::>(); + let steps = &steps[0..steps.len() - 1]; - client - .migration() - .create( - migration::name::set(name.to_string()), - migration::checksum::set(checksum.clone()), - vec![], - ) - .exec() - .await?; + client + .migration() + .create( + migration::name::set(name.to_string()), + migration::checksum::set(checksum.clone()), + vec![], + ) + .exec() + .await?; - for (i, step) in steps.iter().enumerate() { - match client._execute_raw(&format!("{};", step)).await { - Ok(_) => { - #[cfg(debug_assertions)] - println!("Step {} ran successfully", i); - client - .migration() - .find_unique(migration::checksum::equals(checksum.clone())) - .update(vec![migration::steps_applied::set(i as i32 + 1)]) - .exec() - .await?; - } - Err(e) => { - println!("Error running migration: {}", name); - println!("{}", e); - break; - } - } - } + for (i, step) in steps.iter().enumerate() { + match client._execute_raw(&format!("{};", step)).await { + Ok(_) => { + #[cfg(debug_assertions)] + println!("Step {} ran successfully", i); + client + .migration() + .find_unique(migration::checksum::equals(checksum.clone())) + .update(vec![migration::steps_applied::set(i as i32 + 1)]) + .exec() + .await?; + } + Err(e) => { + println!("Error running migration: {}", name); + println!("{}", e); + break; + } + } + } - #[cfg(debug_assertions)] - println!("Migration {} recorded successfully", name); - } else { - #[cfg(debug_assertions)] - println!("Migration {} already exists", name); - } - } - } - Err(err) => { - panic!("Failed to check migration table existence: {:?}", err); - } - } + #[cfg(debug_assertions)] + println!("Migration {} recorded successfully", name); + } else { + #[cfg(debug_assertions)] + println!("Migration {} already exists", name); + } + } + } + Err(err) => { + panic!("Failed to check migration table existence: {:?}", err); + } + } - Ok(()) + Ok(()) } diff --git a/core/src/db/mod.rs b/core/src/db/mod.rs index 205b7b2e2..532d5c6b1 100644 --- a/core/src/db/mod.rs +++ b/core/src/db/mod.rs @@ -5,17 +5,17 @@ pub mod migrate; #[derive(Error, Debug)] pub enum DatabaseError { - #[error("Failed to connect to database")] - MissingConnection, - #[error("Unable find current_library in the client config")] - MalformedConfig, - #[error("Unable to initialize the Prisma client")] - ClientError(#[from] prisma::NewClientError), + #[error("Failed to connect to database")] + MissingConnection, + #[error("Unable find current_library in the client config")] + MalformedConfig, + #[error("Unable to initialize the Prisma client")] + ClientError(#[from] prisma::NewClientError), } pub async fn create_connection(path: &str) -> Result { - println!("Creating database connection: {:?}", path); - let client = prisma::new_client_with_url(&format!("file:{}", &path)).await?; + println!("Creating database connection: {:?}", path); + let client = prisma::new_client_with_url(&format!("file:{}", &path)).await?; - Ok(client) + Ok(client) } diff --git a/core/src/encode/metadata.rs b/core/src/encode/metadata.rs index 93e782cdd..1d1147600 100644 --- a/core/src/encode/metadata.rs +++ b/core/src/encode/metadata.rs @@ -5,132 +5,132 @@ use std::{ffi::OsStr, path::Path}; #[derive(Default, Debug)] pub struct MediaItem { - pub created_at: Option, - pub brand: Option, - pub model: Option, - pub duration_seconds: f64, - pub best_video_stream_index: usize, - pub best_audio_stream_index: usize, - pub best_subtitle_stream_index: usize, - pub steams: Vec, + pub created_at: Option, + pub brand: Option, + pub model: Option, + pub duration_seconds: f64, + pub best_video_stream_index: usize, + pub best_audio_stream_index: usize, + pub best_subtitle_stream_index: usize, + pub steams: Vec, } #[derive(Debug)] pub struct Stream { - pub codec: String, - pub frames: f64, - pub duration_seconds: f64, - pub kind: Option, + pub codec: String, + pub frames: f64, + pub duration_seconds: f64, + pub kind: Option, } #[derive(Debug)] pub enum StreamKind { - Video(VideoStream), - Audio(AudioStream), + Video(VideoStream), + Audio(AudioStream), } #[derive(Debug)] pub struct VideoStream { - pub width: u32, - pub height: u32, - pub aspect_ratio: String, - pub format: format::Pixel, - pub bitrate: usize, + pub width: u32, + pub height: u32, + pub aspect_ratio: String, + pub format: format::Pixel, + pub bitrate: usize, } #[derive(Debug)] pub struct AudioStream { - pub channels: u16, - pub format: format::Sample, - pub bitrate: usize, - pub rate: u32, + pub channels: u16, + pub format: format::Sample, + pub bitrate: usize, + pub rate: u32, } fn extract(iter: &mut Iter, key: &str) -> Option { - iter.find(|k| k.0.contains(key)).map(|k| k.1.to_string()) + iter.find(|k| k.0.contains(key)).map(|k| k.1.to_string()) } pub fn get_video_metadata(path: &str) -> Result<(), ffmpeg::Error> { - ffmpeg::init().unwrap(); + ffmpeg::init().unwrap(); - let mut name = Path::new(path) - .file_name() - .and_then(OsStr::to_str) - .map(ToString::to_string) - .unwrap_or(String::new()); + let mut name = Path::new(path) + .file_name() + .and_then(OsStr::to_str) + .map(ToString::to_string) + .unwrap_or(String::new()); - // strip to exact potential date length and attempt to parse - name = name.chars().take(19).collect(); - // specifically OBS uses this format for time, other checks could be added - let potential_date = NaiveDateTime::parse_from_str(&name, "%Y-%m-%d %H-%M-%S"); + // strip to exact potential date length and attempt to parse + name = name.chars().take(19).collect(); + // specifically OBS uses this format for time, other checks could be added + let potential_date = NaiveDateTime::parse_from_str(&name, "%Y-%m-%d %H-%M-%S"); - match ffmpeg::format::input(&path) { - Ok(context) => { - let mut media_item = MediaItem::default(); - let metadata = context.metadata(); - let mut iter = metadata.iter(); + match ffmpeg::format::input(&path) { + Ok(context) => { + let mut media_item = MediaItem::default(); + let metadata = context.metadata(); + let mut iter = metadata.iter(); - // creation_time is usually the creation date of the file - media_item.created_at = extract(&mut iter, "creation_time"); - // apple photos use "com.apple.quicktime.creationdate", which we care more about than the creation_time - media_item.created_at = extract(&mut iter, "creationdate"); - // fallback to potential time if exists - if media_item.created_at.is_none() { - media_item.created_at = potential_date.map(|d| d.to_string()).ok(); - } - // origin metadata - media_item.brand = extract(&mut iter, "major_brand"); - media_item.brand = extract(&mut iter, "make"); - media_item.model = extract(&mut iter, "model"); + // creation_time is usually the creation date of the file + media_item.created_at = extract(&mut iter, "creation_time"); + // apple photos use "com.apple.quicktime.creationdate", which we care more about than the creation_time + media_item.created_at = extract(&mut iter, "creationdate"); + // fallback to potential time if exists + if media_item.created_at.is_none() { + media_item.created_at = potential_date.map(|d| d.to_string()).ok(); + } + // origin metadata + media_item.brand = extract(&mut iter, "major_brand"); + media_item.brand = extract(&mut iter, "make"); + media_item.model = extract(&mut iter, "model"); - if let Some(stream) = context.streams().best(ffmpeg::media::Type::Video) { - media_item.best_video_stream_index = stream.index(); - } - if let Some(stream) = context.streams().best(ffmpeg::media::Type::Audio) { - media_item.best_audio_stream_index = stream.index(); - } - if let Some(stream) = context.streams().best(ffmpeg::media::Type::Subtitle) { - media_item.best_subtitle_stream_index = stream.index(); - } - media_item.duration_seconds = - context.duration() as f64 / f64::from(ffmpeg::ffi::AV_TIME_BASE); + if let Some(stream) = context.streams().best(ffmpeg::media::Type::Video) { + media_item.best_video_stream_index = stream.index(); + } + if let Some(stream) = context.streams().best(ffmpeg::media::Type::Audio) { + media_item.best_audio_stream_index = stream.index(); + } + if let Some(stream) = context.streams().best(ffmpeg::media::Type::Subtitle) { + media_item.best_subtitle_stream_index = stream.index(); + } + media_item.duration_seconds = + context.duration() as f64 / f64::from(ffmpeg::ffi::AV_TIME_BASE); - for stream in context.streams() { - let codec = ffmpeg::codec::context::Context::from_parameters(stream.parameters())?; + for stream in context.streams() { + let codec = ffmpeg::codec::context::Context::from_parameters(stream.parameters())?; - let mut stream_item = Stream { - codec: codec.id().name().to_string(), - frames: stream.frames() as f64, - duration_seconds: stream.duration() as f64 * f64::from(stream.time_base()), - kind: None, - }; + let mut stream_item = Stream { + codec: codec.id().name().to_string(), + frames: stream.frames() as f64, + duration_seconds: stream.duration() as f64 * f64::from(stream.time_base()), + kind: None, + }; - if codec.medium() == ffmpeg::media::Type::Video { - if let Ok(video) = codec.decoder().video() { - stream_item.kind = Some(StreamKind::Video(VideoStream { - bitrate: video.bit_rate(), - format: video.format(), - width: video.width(), - height: video.height(), - aspect_ratio: video.aspect_ratio().to_string(), - })); - } - } else if codec.medium() == ffmpeg::media::Type::Audio { - if let Ok(audio) = codec.decoder().audio() { - stream_item.kind = Some(StreamKind::Audio(AudioStream { - channels: audio.channels(), - bitrate: audio.bit_rate(), - rate: audio.rate(), - format: audio.format(), - })); - } - } - media_item.steams.push(stream_item); - } - println!("{:#?}", media_item); - } + if codec.medium() == ffmpeg::media::Type::Video { + if let Ok(video) = codec.decoder().video() { + stream_item.kind = Some(StreamKind::Video(VideoStream { + bitrate: video.bit_rate(), + format: video.format(), + width: video.width(), + height: video.height(), + aspect_ratio: video.aspect_ratio().to_string(), + })); + } + } else if codec.medium() == ffmpeg::media::Type::Audio { + if let Ok(audio) = codec.decoder().audio() { + stream_item.kind = Some(StreamKind::Audio(AudioStream { + channels: audio.channels(), + bitrate: audio.bit_rate(), + rate: audio.rate(), + format: audio.format(), + })); + } + } + media_item.steams.push(stream_item); + } + println!("{:#?}", media_item); + } - Err(error) => println!("error: {}", error), - } - Ok(()) + Err(error) => println!("error: {}", error), + } + Ok(()) } diff --git a/core/src/encode/thumb.rs b/core/src/encode/thumb.rs index 25940ddd1..1d877c1b3 100644 --- a/core/src/encode/thumb.rs +++ b/core/src/encode/thumb.rs @@ -1,9 +1,9 @@ use crate::job::jobs::JobReportUpdate; use crate::state::client; use crate::{ - job::{jobs::Job, worker::WorkerContext}, - prisma::file_path, - CoreContext, + job::{jobs::Job, worker::WorkerContext}, + prisma::file_path, + CoreContext, }; use crate::{sys, CoreEvent}; use anyhow::Result; @@ -15,9 +15,9 @@ use webp::*; #[derive(Debug, Clone)] pub struct ThumbnailJob { - pub location_id: i32, - pub path: String, - pub background: bool, + pub location_id: i32, + pub path: String, + pub background: bool, } static THUMBNAIL_SIZE_FACTOR: f32 = 0.2; @@ -26,121 +26,124 @@ pub static THUMBNAIL_CACHE_DIR_NAME: &str = "thumbnails"; #[async_trait::async_trait] impl Job for ThumbnailJob { - async fn run(&self, ctx: WorkerContext) -> Result<()> { - let config = client::get(); - let core_ctx = ctx.core_ctx.clone(); + async fn run(&self, ctx: WorkerContext) -> Result<()> { + let config = client::get(); + let core_ctx = ctx.core_ctx.clone(); - let location = sys::locations::get_location(&core_ctx, self.location_id).await?; + let location = sys::locations::get_location(&core_ctx, self.location_id).await?; - fs::create_dir_all( - Path::new(&config.data_path) - .join(THUMBNAIL_CACHE_DIR_NAME) - .join(format!("{}", self.location_id)), - )?; + fs::create_dir_all( + Path::new(&config.data_path) + .join(THUMBNAIL_CACHE_DIR_NAME) + .join(format!("{}", self.location_id)), + )?; - let root_path = location.path.unwrap(); + let root_path = location.path.unwrap(); - let image_files = get_images(&core_ctx, self.location_id, &self.path).await?; + let image_files = get_images(&core_ctx, self.location_id, &self.path).await?; - let location_id = location.id.clone(); + let location_id = location.id.clone(); - println!("Found {:?} files", image_files.len()); + println!("Found {:?} files", image_files.len()); - let is_background = self.background.clone(); + let is_background = self.background.clone(); - tokio::task::spawn_blocking(move || { - ctx.progress(vec![ - JobReportUpdate::TaskCount(image_files.len()), - JobReportUpdate::Message(format!("Preparing to process {} files", image_files.len())), - ]); + tokio::task::spawn_blocking(move || { + ctx.progress(vec![ + JobReportUpdate::TaskCount(image_files.len()), + JobReportUpdate::Message(format!( + "Preparing to process {} files", + image_files.len() + )), + ]); - for (i, image_file) in image_files.iter().enumerate() { - ctx.progress(vec![JobReportUpdate::Message(format!( - "Processing {}", - image_file.materialized_path.clone() - ))]); - let path = format!("{}{}", root_path, image_file.materialized_path); - let checksum = image_file.temp_cas_id.as_ref().unwrap(); + for (i, image_file) in image_files.iter().enumerate() { + ctx.progress(vec![JobReportUpdate::Message(format!( + "Processing {}", + image_file.materialized_path.clone() + ))]); + let path = format!("{}{}", root_path, image_file.materialized_path); + let checksum = image_file.temp_cas_id.as_ref().unwrap(); - // Define and write the WebP-encoded file to a given path - let output_path = Path::new(&config.data_path) - .join(THUMBNAIL_CACHE_DIR_NAME) - .join(format!("{}", location_id)) - .join(checksum) - .with_extension("webp"); + // Define and write the WebP-encoded file to a given path + let output_path = Path::new(&config.data_path) + .join(THUMBNAIL_CACHE_DIR_NAME) + .join(format!("{}", location_id)) + .join(checksum) + .with_extension("webp"); - // check if file exists at output path - if !output_path.exists() { - println!("writing {:?} to {}", output_path, path); - generate_thumbnail(&path, &output_path) - .map_err(|e| { - println!("error generating thumb {:?}", e); - }) - .unwrap_or(()); + // check if file exists at output path + if !output_path.exists() { + println!("writing {:?} to {}", output_path, path); + generate_thumbnail(&path, &output_path) + .map_err(|e| { + println!("error generating thumb {:?}", e); + }) + .unwrap_or(()); - ctx.progress(vec![JobReportUpdate::CompletedTaskCount(i + 1)]); + ctx.progress(vec![JobReportUpdate::CompletedTaskCount(i + 1)]); - if !is_background { - block_on(ctx.core_ctx.emit(CoreEvent::NewThumbnail { - cas_id: checksum.to_string(), - })); - }; - } else { - println!("Thumb exists, skipping... {}", output_path.display()); - } - } - }) - .await?; + if !is_background { + block_on(ctx.core_ctx.emit(CoreEvent::NewThumbnail { + cas_id: checksum.to_string(), + })); + }; + } else { + println!("Thumb exists, skipping... {}", output_path.display()); + } + } + }) + .await?; - Ok(()) - } + Ok(()) + } } pub fn generate_thumbnail(file_path: &str, output_path: &PathBuf) -> Result<()> { - // Using `image` crate, open the included .jpg file - let img = image::open(file_path)?; - let (w, h) = img.dimensions(); - // Optionally, resize the existing photo and convert back into DynamicImage - let img: DynamicImage = image::DynamicImage::ImageRgba8(imageops::resize( - &img, - (w as f32 * THUMBNAIL_SIZE_FACTOR) as u32, - (h as f32 * THUMBNAIL_SIZE_FACTOR) as u32, - imageops::FilterType::Triangle, - )); - // Create the WebP encoder for the above image - let encoder: Encoder = Encoder::from_image(&img).map_err(|_| anyhow::anyhow!("jeff"))?; + // Using `image` crate, open the included .jpg file + let img = image::open(file_path)?; + let (w, h) = img.dimensions(); + // Optionally, resize the existing photo and convert back into DynamicImage + let img: DynamicImage = image::DynamicImage::ImageRgba8(imageops::resize( + &img, + (w as f32 * THUMBNAIL_SIZE_FACTOR) as u32, + (h as f32 * THUMBNAIL_SIZE_FACTOR) as u32, + imageops::FilterType::Triangle, + )); + // Create the WebP encoder for the above image + let encoder: Encoder = Encoder::from_image(&img).map_err(|_| anyhow::anyhow!("jeff"))?; - // Encode the image at a specified quality 0-100 - let webp: WebPMemory = encoder.encode(THUMBNAIL_QUALITY); + // Encode the image at a specified quality 0-100 + let webp: WebPMemory = encoder.encode(THUMBNAIL_QUALITY); - println!("Writing to {}", output_path.display()); + println!("Writing to {}", output_path.display()); - std::fs::write(&output_path, &*webp)?; + std::fs::write(&output_path, &*webp)?; - Ok(()) + Ok(()) } pub async fn get_images( - ctx: &CoreContext, - location_id: i32, - path: &str, + ctx: &CoreContext, + location_id: i32, + path: &str, ) -> Result> { - let mut params = vec![ - file_path::location_id::equals(location_id), - file_path::extension::in_vec(vec![ - "png".to_string(), - "jpeg".to_string(), - "jpg".to_string(), - "gif".to_string(), - "webp".to_string(), - ]), - ]; + let mut params = vec![ + file_path::location_id::equals(location_id), + file_path::extension::in_vec(vec![ + "png".to_string(), + "jpeg".to_string(), + "jpg".to_string(), + "gif".to_string(), + "webp".to_string(), + ]), + ]; - if !path.is_empty() { - params.push(file_path::materialized_path::starts_with(path.to_string())) - } + if !path.is_empty() { + params.push(file_path::materialized_path::starts_with(path.to_string())) + } - let image_files = ctx.database.file_path().find_many(params).exec().await?; + let image_files = ctx.database.file_path().find_many(params).exec().await?; - Ok(image_files) + Ok(image_files) } diff --git a/core/src/encode/vthumb.rs b/core/src/encode/vthumb.rs index e69de29bb..8b1378917 100644 --- a/core/src/encode/vthumb.rs +++ b/core/src/encode/vthumb.rs @@ -0,0 +1 @@ + diff --git a/core/src/encode/vthumbstrip.rs b/core/src/encode/vthumbstrip.rs index e69de29bb..8b1378917 100644 --- a/core/src/encode/vthumbstrip.rs +++ b/core/src/encode/vthumbstrip.rs @@ -0,0 +1 @@ + diff --git a/core/src/file/cas/checksum.rs b/core/src/file/cas/checksum.rs index 7b7eedaa3..ea016fd62 100644 --- a/core/src/file/cas/checksum.rs +++ b/core/src/file/cas/checksum.rs @@ -16,62 +16,62 @@ static SAMPLE_COUNT: u64 = 4; static SAMPLE_SIZE: u64 = 10000; fn read_at(file: &File, offset: u64, size: u64) -> Result> { - let mut buf = vec![0u8; size as usize]; + let mut buf = vec![0u8; size as usize]; - #[cfg(target_family = "unix")] - file.read_exact_at(&mut buf, offset)?; + #[cfg(target_family = "unix")] + file.read_exact_at(&mut buf, offset)?; - #[cfg(target_family = "windows")] - file.seek_read(&mut buf, offset)?; + #[cfg(target_family = "windows")] + file.seek_read(&mut buf, offset)?; - Ok(buf) + Ok(buf) } pub fn generate_cas_id(path: &str, size: u64) -> Result { - // open file reference - let file = File::open(path)?; + // open file reference + let file = File::open(path)?; - let mut context = Context::new(&SHA256); + let mut context = Context::new(&SHA256); - // include the file size in the checksum - context.update(&size.to_le_bytes()); + // include the file size in the checksum + context.update(&size.to_le_bytes()); - // if size is small enough, just read the whole thing - if SAMPLE_COUNT * SAMPLE_SIZE > size { - let buf = read_at(&file, 0, size.try_into()?)?; - context.update(&buf); - } else { - // loop over samples - for i in 0..SAMPLE_COUNT { - let buf = read_at(&file, (size / SAMPLE_COUNT) * i, SAMPLE_SIZE.try_into()?)?; - context.update(&buf); - } - // sample end of file - let buf = read_at(&file, size - SAMPLE_SIZE, SAMPLE_SIZE.try_into()?)?; - context.update(&buf); - } + // if size is small enough, just read the whole thing + if SAMPLE_COUNT * SAMPLE_SIZE > size { + let buf = read_at(&file, 0, size.try_into()?)?; + context.update(&buf); + } else { + // loop over samples + for i in 0..SAMPLE_COUNT { + let buf = read_at(&file, (size / SAMPLE_COUNT) * i, SAMPLE_SIZE.try_into()?)?; + context.update(&buf); + } + // sample end of file + let buf = read_at(&file, size - SAMPLE_SIZE, SAMPLE_SIZE.try_into()?)?; + context.update(&buf); + } - let digest = context.finish(); - let hex = HEXLOWER.encode(digest.as_ref()); + let digest = context.finish(); + let hex = HEXLOWER.encode(digest.as_ref()); - Ok(hex) + Ok(hex) } pub fn full_checksum(path: &str) -> Result { - // read file as buffer and convert to digest - let mut reader = BufReader::new(File::open(path).unwrap()); - let mut context = Context::new(&SHA256); - let mut buffer = [0; 1024]; - loop { - let count = reader.read(&mut buffer)?; - if count == 0 { - break; - } - context.update(&buffer[..count]); - } - let digest = context.finish(); - // create a lowercase hash from - let hex = HEXLOWER.encode(digest.as_ref()); + // read file as buffer and convert to digest + let mut reader = BufReader::new(File::open(path).unwrap()); + let mut context = Context::new(&SHA256); + let mut buffer = [0; 1024]; + loop { + let count = reader.read(&mut buffer)?; + if count == 0 { + break; + } + context.update(&buffer[..count]); + } + let digest = context.finish(); + // create a lowercase hash from + let hex = HEXLOWER.encode(digest.as_ref()); - Ok(hex) + Ok(hex) } diff --git a/core/src/file/cas/identifier.rs b/core/src/file/cas/identifier.rs index b71a9eeb9..6c1267397 100644 --- a/core/src/file/cas/identifier.rs +++ b/core/src/file/cas/identifier.rs @@ -1,19 +1,19 @@ use crate::job::jobs::JobReportUpdate; use crate::{ - file::FileError, - job::{jobs::Job, worker::WorkerContext}, - prisma::{file_path}, - CoreContext, + file::FileError, + job::{jobs::Job, worker::WorkerContext}, + prisma::file_path, + CoreContext, }; use anyhow::Result; use futures::executor::block_on; -use serde::{Deserialize, Serialize}; use prisma_client_rust::Direction; +use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Debug)] pub struct FileCreated { - pub id: i32, - pub cas_id: String, + pub id: i32, + pub cas_id: String, } #[derive(Debug)] @@ -21,20 +21,20 @@ pub struct FileIdentifierJob; #[async_trait::async_trait] impl Job for FileIdentifierJob { - async fn run(&self, ctx: WorkerContext) -> Result<()> { - let total_count = count_orphan_file_paths(&ctx.core_ctx).await?; - println!("Found {} orphan file paths", total_count); + async fn run(&self, ctx: WorkerContext) -> Result<()> { + let total_count = count_orphan_file_paths(&ctx.core_ctx).await?; + println!("Found {} orphan file paths", total_count); - let task_count = (total_count as f64 / 100f64).ceil() as usize; + let task_count = (total_count as f64 / 100f64).ceil() as usize; - println!("Will process {} tasks", task_count); + println!("Will process {} tasks", task_count); - // update job with total task count based on orphan file_paths count - ctx.progress(vec![JobReportUpdate::TaskCount(task_count)]); + // update job with total task count based on orphan file_paths count + ctx.progress(vec![JobReportUpdate::TaskCount(task_count)]); - let db = ctx.core_ctx.database.clone(); + let db = ctx.core_ctx.database.clone(); - let ctx = tokio::task::spawn_blocking(move || { + let ctx = tokio::task::spawn_blocking(move || { let mut completed: usize = 0; let mut cursor: i32 = 1; @@ -86,57 +86,60 @@ impl Job for FileIdentifierJob { ctx }).await?; - let remaining = count_orphan_file_paths(&ctx.core_ctx).await?; + let remaining = count_orphan_file_paths(&ctx.core_ctx).await?; - println!("Finished with {} files remaining because your code is bad.", remaining); + println!( + "Finished with {} files remaining because your code is bad.", + remaining + ); - // if remaining > 0 { - // ctx.core_ctx.spawn_job(Box::new(FileIdentifierJob)); - // } + // if remaining > 0 { + // ctx.core_ctx.spawn_job(Box::new(FileIdentifierJob)); + // } - Ok(()) - } + Ok(()) + } } #[derive(Deserialize, Serialize, Debug)] struct CountRes { - count: Option, + count: Option, } pub async fn count_orphan_file_paths(ctx: &CoreContext) -> Result { - let db = &ctx.database; - let files_count = db - ._query_raw::( - r#"SELECT COUNT(*) AS count FROM file_paths WHERE file_id IS NULL AND is_dir IS FALSE"#, - ) - .await?; - Ok(files_count[0].count.unwrap_or(0)) + let db = &ctx.database; + let files_count = db + ._query_raw::( + r#"SELECT COUNT(*) AS count FROM file_paths WHERE file_id IS NULL AND is_dir IS FALSE"#, + ) + .await?; + Ok(files_count[0].count.unwrap_or(0)) } pub async fn get_orphan_file_paths( - ctx: &CoreContext, - cursor: i32, + ctx: &CoreContext, + cursor: i32, ) -> Result, FileError> { - let db = &ctx.database; - println!("cursor: {:?}", cursor); - let files = db - .file_path() - .find_many(vec![ - file_path::file_id::equals(None), - file_path::is_dir::equals(false), - ]) - .order_by(file_path::id::order(Direction::Asc)) - .cursor(file_path::id::cursor(cursor)) - .take(100) - .exec() - .await?; - Ok(files) + let db = &ctx.database; + println!("cursor: {:?}", cursor); + let files = db + .file_path() + .find_many(vec![ + file_path::file_id::equals(None), + file_path::is_dir::equals(false), + ]) + .order_by(file_path::id::order(Direction::Asc)) + .cursor(file_path::id::cursor(cursor)) + .take(100) + .exec() + .await?; + Ok(files) } pub fn prepare_file_values(file_path: &file_path::Data) -> String { - format!( - "(\"{}\",\"{}\")", - file_path.temp_cas_id.as_ref().unwrap(), - "0" - ) + format!( + "(\"{}\",\"{}\")", + file_path.temp_cas_id.as_ref().unwrap(), + "0" + ) } diff --git a/core/src/file/explorer/open.rs b/core/src/file/explorer/open.rs index 8b2ba0dac..5665e1a67 100644 --- a/core/src/file/explorer/open.rs +++ b/core/src/file/explorer/open.rs @@ -1,62 +1,62 @@ use std::path::Path; use crate::{ - encode::thumb::THUMBNAIL_CACHE_DIR_NAME, - file::{DirectoryWithContents, FileError, FilePath}, - prisma::file_path, - state::client, - sys::locations::get_location, - CoreContext, + encode::thumb::THUMBNAIL_CACHE_DIR_NAME, + file::{DirectoryWithContents, FileError, FilePath}, + prisma::file_path, + state::client, + sys::locations::get_location, + CoreContext, }; pub async fn open_dir( - ctx: &CoreContext, - location_id: &i32, - path: &str, + ctx: &CoreContext, + location_id: &i32, + path: &str, ) -> Result { - let db = &ctx.database; - let config = client::get(); + let db = &ctx.database; + let config = client::get(); - // get location - let location = get_location(ctx, location_id.clone()).await?; + // get location + let location = get_location(ctx, location_id.clone()).await?; - let directory = db - .file_path() - .find_first(vec![ - file_path::location_id::equals(location.id), - file_path::materialized_path::equals(path.into()), - file_path::is_dir::equals(true), - ]) - .exec() - .await? - .ok_or(FileError::DirectoryNotFound(path.to_string()))?; + let directory = db + .file_path() + .find_first(vec![ + file_path::location_id::equals(location.id), + file_path::materialized_path::equals(path.into()), + file_path::is_dir::equals(true), + ]) + .exec() + .await? + .ok_or(FileError::DirectoryNotFound(path.to_string()))?; - let files = db - .file_path() - .find_many(vec![file_path::parent_id::equals(Some(directory.id))]) - .exec() - .await?; + let files = db + .file_path() + .find_many(vec![file_path::parent_id::equals(Some(directory.id))]) + .exec() + .await?; - let files: Vec = files.into_iter().map(|l| l.into()).collect(); + let files: Vec = files.into_iter().map(|l| l.into()).collect(); - let mut contents: Vec = vec![]; + let mut contents: Vec = vec![]; - for mut file in files { - if file.temp_cas_id.is_some() { - let path = Path::new(&config.data_path) - .join(THUMBNAIL_CACHE_DIR_NAME) - .join(format!("{}", location.id)) - .join(file.temp_cas_id.as_ref().unwrap()) - .with_extension("webp"); + for mut file in files { + if file.temp_cas_id.is_some() { + let path = Path::new(&config.data_path) + .join(THUMBNAIL_CACHE_DIR_NAME) + .join(format!("{}", location.id)) + .join(file.temp_cas_id.as_ref().unwrap()) + .with_extension("webp"); - let exists = path.exists(); - file.has_local_thumbnail = exists; - } - contents.push(file); - } + let exists = path.exists(); + file.has_local_thumbnail = exists; + } + contents.push(file); + } - Ok(DirectoryWithContents { - directory: directory.into(), - contents, - }) + Ok(DirectoryWithContents { + directory: directory.into(), + contents, + }) } diff --git a/core/src/file/indexer/mod.rs b/core/src/file/indexer/mod.rs index 01e13a1a3..05afbb828 100644 --- a/core/src/file/indexer/mod.rs +++ b/core/src/file/indexer/mod.rs @@ -1,6 +1,6 @@ use crate::job::{ - jobs::{Job, JobReportUpdate}, - worker::WorkerContext, + jobs::{Job, JobReportUpdate}, + worker::WorkerContext, }; use anyhow::Result; @@ -12,25 +12,25 @@ pub use {pathctx::PathContext, scan::scan_path}; #[derive(Debug)] pub struct IndexerJob { - pub path: String, + pub path: String, } #[async_trait::async_trait] impl Job for IndexerJob { - async fn run(&self, ctx: WorkerContext) -> Result<()> { - let core_ctx = ctx.core_ctx.clone(); - scan_path(&core_ctx, self.path.as_str(), move |p| { - ctx.progress( - p.iter() - .map(|p| match p.clone() { - ScanProgress::ChunkCount(c) => JobReportUpdate::TaskCount(c), - ScanProgress::SavedChunks(p) => JobReportUpdate::CompletedTaskCount(p), - ScanProgress::Message(m) => JobReportUpdate::Message(m), - }) - .collect(), - ) - }) - .await?; - Ok(()) - } + async fn run(&self, ctx: WorkerContext) -> Result<()> { + let core_ctx = ctx.core_ctx.clone(); + scan_path(&core_ctx, self.path.as_str(), move |p| { + ctx.progress( + p.iter() + .map(|p| match p.clone() { + ScanProgress::ChunkCount(c) => JobReportUpdate::TaskCount(c), + ScanProgress::SavedChunks(p) => JobReportUpdate::CompletedTaskCount(p), + ScanProgress::Message(m) => JobReportUpdate::Message(m), + }) + .collect(), + ) + }) + .await?; + Ok(()) + } } diff --git a/core/src/file/indexer/pathctx.rs b/core/src/file/indexer/pathctx.rs index d329ed24c..aa6a28444 100644 --- a/core/src/file/indexer/pathctx.rs +++ b/core/src/file/indexer/pathctx.rs @@ -1,13 +1,13 @@ // PathContext provides the indexer with instruction to handle particular directory structures and identify rich context. pub struct PathContext { - // an app specific key "com.github.repo" - pub key: String, - pub name: String, - pub is_dir: bool, - // possible file extensions for this path - pub extensions: Vec, - // sub-paths that must be found - pub must_contain_sub_paths: Vec, - // sub-paths that are ignored - pub always_ignored_sub_paths: Option, + // an app specific key "com.github.repo" + pub key: String, + pub name: String, + pub is_dir: bool, + // possible file extensions for this path + pub extensions: Vec, + // sub-paths that must be found + pub must_contain_sub_paths: Vec, + // sub-paths that are ignored + pub always_ignored_sub_paths: Option, } diff --git a/core/src/file/indexer/scan.rs b/core/src/file/indexer/scan.rs index 31efbb6bf..b2e22cb74 100644 --- a/core/src/file/indexer/scan.rs +++ b/core/src/file/indexer/scan.rs @@ -10,276 +10,276 @@ use walkdir::{DirEntry, WalkDir}; #[derive(Clone)] pub enum ScanProgress { - ChunkCount(usize), - SavedChunks(usize), - Message(String), + ChunkCount(usize), + SavedChunks(usize), + Message(String), } // creates a vector of valid path buffers from a directory pub async fn scan_path( - ctx: &CoreContext, - path: &str, - on_progress: impl Fn(Vec) + Send + Sync + 'static, + ctx: &CoreContext, + path: &str, + on_progress: impl Fn(Vec) + Send + Sync + 'static, ) -> Result<()> { - let db = &ctx.database; - let path = path.to_string(); + let db = &ctx.database; + let path = path.to_string(); - let location = create_location(&ctx, &path).await?; + let location = create_location(&ctx, &path).await?; - // query db to highers id, so we can increment it for the new files indexed - #[derive(Deserialize, Serialize, Debug)] - struct QueryRes { - id: Option, - } - // grab the next id so we can increment in memory for batch inserting - let first_file_id = match db - ._query_raw::(r#"SELECT MAX(id) id FROM file_paths"#) - .await - { - Ok(rows) => rows[0].id.unwrap_or(0), - Err(e) => Err(anyhow!("Error querying for next file id: {}", e))?, - }; + // query db to highers id, so we can increment it for the new files indexed + #[derive(Deserialize, Serialize, Debug)] + struct QueryRes { + id: Option, + } + // grab the next id so we can increment in memory for batch inserting + let first_file_id = match db + ._query_raw::(r#"SELECT MAX(id) id FROM file_paths"#) + .await + { + Ok(rows) => rows[0].id.unwrap_or(0), + Err(e) => Err(anyhow!("Error querying for next file id: {}", e))?, + }; - //check is path is a directory - if !PathBuf::from(&path).is_dir() { - return Err(anyhow::anyhow!("{} is not a directory", &path)); - } - let dir_path = path.clone(); + //check is path is a directory + if !PathBuf::from(&path).is_dir() { + return Err(anyhow::anyhow!("{} is not a directory", &path)); + } + let dir_path = path.clone(); - // spawn a dedicated thread to scan the directory for performance - let (paths, scan_start, on_progress) = tokio::task::spawn_blocking(move || { - // store every valid path discovered - let mut paths: Vec<(PathBuf, i32, Option)> = Vec::new(); - // store a hashmap of directories to their file ids for fast lookup - let mut dirs: HashMap = HashMap::new(); - // begin timer for logging purposes - let scan_start = Instant::now(); + // spawn a dedicated thread to scan the directory for performance + let (paths, scan_start, on_progress) = tokio::task::spawn_blocking(move || { + // store every valid path discovered + let mut paths: Vec<(PathBuf, i32, Option)> = Vec::new(); + // store a hashmap of directories to their file ids for fast lookup + let mut dirs: HashMap = HashMap::new(); + // begin timer for logging purposes + let scan_start = Instant::now(); - let mut next_file_id = first_file_id; - let mut get_id = || { - next_file_id += 1; - next_file_id - }; - // walk through directory recursively - for entry in WalkDir::new(&dir_path).into_iter().filter_entry(|dir| { - let approved = - !is_hidden(dir) && !is_app_bundle(dir) && !is_node_modules(dir) && !is_library(dir); - approved - }) { - // extract directory entry or log and continue if failed - let entry = match entry { - Ok(entry) => entry, - Err(e) => { - println!("Error reading file {}", e); - continue; - } - }; - let path = entry.path(); + let mut next_file_id = first_file_id; + let mut get_id = || { + next_file_id += 1; + next_file_id + }; + // walk through directory recursively + for entry in WalkDir::new(&dir_path).into_iter().filter_entry(|dir| { + let approved = + !is_hidden(dir) && !is_app_bundle(dir) && !is_node_modules(dir) && !is_library(dir); + approved + }) { + // extract directory entry or log and continue if failed + let entry = match entry { + Ok(entry) => entry, + Err(e) => { + println!("Error reading file {}", e); + continue; + } + }; + let path = entry.path(); - let parent_path = path - .parent() - .unwrap_or(Path::new("")) - .to_str() - .unwrap_or(""); - let parent_dir_id = dirs.get(&*parent_path); + let parent_path = path + .parent() + .unwrap_or(Path::new("")) + .to_str() + .unwrap_or(""); + let parent_dir_id = dirs.get(&*parent_path); - let str = match path.as_os_str().to_str() { - Some(str) => str, - None => { - println!("Error reading file {}", &path.display()); - continue; - } - }; + let str = match path.as_os_str().to_str() { + Some(str) => str, + None => { + println!("Error reading file {}", &path.display()); + continue; + } + }; - on_progress(vec![ - ScanProgress::Message(format!("{}", str)), - ScanProgress::ChunkCount(paths.len() / 100), - ]); + on_progress(vec![ + ScanProgress::Message(format!("{}", str)), + ScanProgress::ChunkCount(paths.len() / 100), + ]); - let file_id = get_id(); + let file_id = get_id(); - if entry.file_type().is_dir() || entry.file_type().is_file() { - paths.push((path.to_owned(), file_id, parent_dir_id.cloned())); - } + if entry.file_type().is_dir() || entry.file_type().is_file() { + paths.push((path.to_owned(), file_id, parent_dir_id.cloned())); + } - if entry.file_type().is_dir() { - let _path = match path.to_str() { - Some(path) => path.to_owned(), - None => continue, - }; - dirs.insert(_path, file_id); - } - } - (paths, scan_start, on_progress) - }) - .await - .unwrap(); + if entry.file_type().is_dir() { + let _path = match path.to_str() { + Some(path) => path.to_owned(), + None => continue, + }; + dirs.insert(_path, file_id); + } + } + (paths, scan_start, on_progress) + }) + .await + .unwrap(); - let db_write_start = Instant::now(); - let scan_read_time = scan_start.elapsed(); + let db_write_start = Instant::now(); + let scan_read_time = scan_start.elapsed(); - for (i, chunk) in paths.chunks(100).enumerate() { - on_progress(vec![ - ScanProgress::SavedChunks(i as usize), - ScanProgress::Message(format!( - "Writing {} of {} to db", - i * chunk.len(), - paths.len(), - )), - ]); + for (i, chunk) in paths.chunks(100).enumerate() { + on_progress(vec![ + ScanProgress::SavedChunks(i as usize), + ScanProgress::Message(format!( + "Writing {} of {} to db", + i * chunk.len(), + paths.len(), + )), + ]); - // vector to store active models - let mut files: Vec = Vec::new(); - for (file_path, file_id, parent_dir_id) in chunk { - files.push( - match prepare_values(&file_path, *file_id, &location, parent_dir_id) { - Ok(file) => file, - Err(e) => { - println!("Error creating file model from path {:?}: {}", file_path, e); - continue; - } - }, - ); - } - let raw_sql = format!( - r#" + // vector to store active models + let mut files: Vec = Vec::new(); + for (file_path, file_id, parent_dir_id) in chunk { + files.push( + match prepare_values(&file_path, *file_id, &location, parent_dir_id) { + Ok(file) => file, + Err(e) => { + println!("Error creating file model from path {:?}: {}", file_path, e); + continue; + } + }, + ); + } + let raw_sql = format!( + r#" INSERT INTO file_paths (id, is_dir, location_id, materialized_path, name, extension, parent_id, date_created, temp_cas_id) VALUES {} "#, - files.join(", ") - ); - // println!("{}", raw_sql); - let count = db._execute_raw(&raw_sql).await; - println!("Inserted {:?} records", count); - } - println!( - "scan of {:?} completed in {:?}. {:?} files found. db write completed in {:?}", - &path, - scan_read_time, - paths.len(), - db_write_start.elapsed() - ); - Ok(()) + files.join(", ") + ); + // println!("{}", raw_sql); + let count = db._execute_raw(&raw_sql).await; + println!("Inserted {:?} records", count); + } + println!( + "scan of {:?} completed in {:?}. {:?} files found. db write completed in {:?}", + &path, + scan_read_time, + paths.len(), + db_write_start.elapsed() + ); + Ok(()) } // reads a file at a path and creates an ActiveModel with metadata fn prepare_values( - file_path: &PathBuf, - id: i32, - location: &LocationResource, - parent_id: &Option, + file_path: &PathBuf, + id: i32, + location: &LocationResource, + parent_id: &Option, ) -> Result { - let metadata = fs::metadata(&file_path)?; - let location_path = location.path.as_ref().unwrap().as_str(); - // let size = metadata.len(); - let name; - let extension; + let metadata = fs::metadata(&file_path)?; + let location_path = location.path.as_ref().unwrap().as_str(); + // let size = metadata.len(); + let name; + let extension; - // if the 'file_path' is not a directory, then get the extension and name. + // if the 'file_path' is not a directory, then get the extension and name. - // if 'file_path' is a directory, set extension to an empty string to avoid periods in folder names - // - being interpreted as file extensions - if file_path.is_dir() { - extension = "".to_string(); - name = extract_name(file_path.file_name()); - } else { - extension = extract_name(file_path.extension()); - name = extract_name(file_path.file_stem()); - } + // if 'file_path' is a directory, set extension to an empty string to avoid periods in folder names + // - being interpreted as file extensions + if file_path.is_dir() { + extension = "".to_string(); + name = extract_name(file_path.file_name()); + } else { + extension = extract_name(file_path.extension()); + name = extract_name(file_path.file_stem()); + } - let materialized_path = match file_path.to_str() { - Some(p) => p - .clone() - .strip_prefix(&location_path) - // .and_then(|p| p.strip_suffix(format!("{}{}", name, extension).as_str())) - .unwrap_or_default(), - None => return Err(anyhow!("{}", file_path.to_str().unwrap_or_default())), - }; + let materialized_path = match file_path.to_str() { + Some(p) => p + .clone() + .strip_prefix(&location_path) + // .and_then(|p| p.strip_suffix(format!("{}{}", name, extension).as_str())) + .unwrap_or_default(), + None => return Err(anyhow!("{}", file_path.to_str().unwrap_or_default())), + }; - let cas_id = { - if !metadata.is_dir() { - // TODO: remove unwrap, skip and make sure to continue loop - let mut x = generate_cas_id(&file_path.to_str().unwrap(), metadata.len()).unwrap(); - x.truncate(16); - x - } else { - "".to_string() - } - }; + let cas_id = { + if !metadata.is_dir() { + // TODO: remove unwrap, skip and make sure to continue loop + let mut x = generate_cas_id(&file_path.to_str().unwrap(), metadata.len()).unwrap(); + x.truncate(16); + x + } else { + "".to_string() + } + }; - let date_created: DateTime = metadata.created().unwrap().into(); - let parsed_date_created = date_created.to_rfc3339_opts(SecondsFormat::Millis, true); + let date_created: DateTime = metadata.created().unwrap().into(); + let parsed_date_created = date_created.to_rfc3339_opts(SecondsFormat::Millis, true); - let values = format!( - "({}, {}, {}, \"{}\", \"{}\", \"{}\", {},\"{}\", \"{}\")", - id, - metadata.is_dir(), - location.id, - materialized_path, - name, - extension.to_lowercase(), - parent_id - .clone() - .map(|id| format!("\"{}\"", &id)) - .unwrap_or("NULL".to_string()), - parsed_date_created, - cas_id - ); + let values = format!( + "({}, {}, {}, \"{}\", \"{}\", \"{}\", {},\"{}\", \"{}\")", + id, + metadata.is_dir(), + location.id, + materialized_path, + name, + extension.to_lowercase(), + parent_id + .clone() + .map(|id| format!("\"{}\"", &id)) + .unwrap_or("NULL".to_string()), + parsed_date_created, + cas_id + ); - println!("{}", values); + println!("{}", values); - Ok(values) + Ok(values) } // extract name from OsStr returned by PathBuff fn extract_name(os_string: Option<&OsStr>) -> String { - os_string - .unwrap_or_default() - .to_str() - .unwrap_or_default() - .to_owned() + os_string + .unwrap_or_default() + .to_str() + .unwrap_or_default() + .to_owned() } fn is_hidden(entry: &DirEntry) -> bool { - entry - .file_name() - .to_str() - .map(|s| s.starts_with(".")) - .unwrap_or(false) + entry + .file_name() + .to_str() + .map(|s| s.starts_with(".")) + .unwrap_or(false) } fn is_library(entry: &DirEntry) -> bool { - entry - .path() - .to_str() - // make better this is shit - .map(|s| s.contains("/Library/")) - .unwrap_or(false) + entry + .path() + .to_str() + // make better this is shit + .map(|s| s.contains("/Library/")) + .unwrap_or(false) } fn is_node_modules(entry: &DirEntry) -> bool { - entry - .file_name() - .to_str() - .map(|s| s.contains("node_modules")) - .unwrap_or(false) + entry + .file_name() + .to_str() + .map(|s| s.contains("node_modules")) + .unwrap_or(false) } fn is_app_bundle(entry: &DirEntry) -> bool { - let is_dir = entry.metadata().unwrap().is_dir(); - let contains_dot = entry - .file_name() - .to_str() - .map(|s| s.contains(".app") | s.contains(".bundle")) - .unwrap_or(false); + let is_dir = entry.metadata().unwrap().is_dir(); + let contains_dot = entry + .file_name() + .to_str() + .map(|s| s.contains(".app") | s.contains(".bundle")) + .unwrap_or(false); - let is_app_bundle = is_dir && contains_dot; - // if is_app_bundle { - // let path_buff = entry.path(); - // let path = path_buff.to_str().unwrap(); + let is_app_bundle = is_dir && contains_dot; + // if is_app_bundle { + // let path_buff = entry.path(); + // let path = path_buff.to_str().unwrap(); - // self::path(&path, ); - // } + // self::path(&path, ); + // } - is_app_bundle + is_app_bundle } diff --git a/core/src/file/mod.rs b/core/src/file/mod.rs index c7821b44f..8c913edbf 100644 --- a/core/src/file/mod.rs +++ b/core/src/file/mod.rs @@ -4,9 +4,9 @@ use thiserror::Error; use ts_rs::TS; use crate::{ - crypto::encryption::EncryptionAlgorithm, - prisma::{self, file, file_path}, - sys::SysError, + crypto::encryption::EncryptionAlgorithm, + prisma::{self, file, file_path}, + sys::SysError, }; pub mod cas; pub mod explorer; @@ -17,134 +17,134 @@ pub mod watcher; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts(export)] pub struct File { - pub id: i32, - pub cas_id: String, - pub integrity_checksum: Option, - pub size_in_bytes: String, - pub kind: FileKind, + pub id: i32, + pub cas_id: String, + pub integrity_checksum: Option, + pub size_in_bytes: String, + pub kind: FileKind, - pub hidden: bool, - pub favorite: bool, - pub important: bool, - pub has_thumbnail: bool, - pub has_thumbstrip: bool, - pub has_video_preview: bool, - pub encryption: EncryptionAlgorithm, - pub ipfs_id: Option, - pub comment: Option, + pub hidden: bool, + pub favorite: bool, + pub important: bool, + pub has_thumbnail: bool, + pub has_thumbstrip: bool, + pub has_video_preview: bool, + pub encryption: EncryptionAlgorithm, + pub ipfs_id: Option, + pub comment: Option, - #[ts(type = "string")] - pub date_created: chrono::DateTime, - #[ts(type = "string")] - pub date_modified: chrono::DateTime, - #[ts(type = "string")] - pub date_indexed: chrono::DateTime, + #[ts(type = "string")] + pub date_created: chrono::DateTime, + #[ts(type = "string")] + pub date_modified: chrono::DateTime, + #[ts(type = "string")] + pub date_indexed: chrono::DateTime, - pub paths: Vec, - // pub media_data: Option, - // pub tags: Vec, - // pub label: Vec