diff --git a/.dockerignore b/.dockerignore index 615017f5..4dbc33aa 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,43 +1,6 @@ -Dockerfile -.github -.vscode -*.md +/sites +/cli +/desktop +/backend # go backend -#### gitignore below -# Nuxt dev/build outputs -.output -.data -.nuxt -.nitro -.cache -dist - -# Node dependencies -node_modules -.yarn - -# Logs -logs -*.log - -# Misc -.DS_Store -.fleet -.idea - -# Local env files -.env -.env.* -!.env.example - -.data - - -# deploy template -deploy-template/* - -!deploy-template/compose.yml - -# generated prisma client -/prisma/client -/prisma/validate +node_modules \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index f04182eb..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: CI - -on: - push: - branches: - - develop - pull_request: - branches: - - develop - -permissions: - contents: read - -jobs: - typecheck: - name: Typecheck - runs-on: ubuntu-latest - steps: - - name: Check out the repo - uses: actions/checkout@v4 - with: - submodules: true - - - name: Setup Node.js environment - uses: actions/setup-node@v4 - with: - node-version: lts/* - cache: "yarn" - - - name: Install dependencies - run: yarn install --immutable --network-timeout 1000000 - - - name: Typecheck - run: yarn typecheck - - lint: - name: Lint - runs-on: ubuntu-latest - steps: - - name: Check out the repo - uses: actions/checkout@v4 - with: - submodules: true - - - name: Setup Node.js environment - uses: actions/setup-node@v4 - with: - node-version: lts/* - cache: "yarn" - - - name: Install dependencies - run: yarn install --immutable --network-timeout 1000000 - - - name: Lint - run: yarn lint diff --git a/.github/workflows/client-release.yml b/.github/workflows/client-release.yml new file mode 100644 index 00000000..ee511a24 --- /dev/null +++ b/.github/workflows/client-release.yml @@ -0,0 +1,140 @@ +name: "Build and release desktop" + +on: + workflow_dispatch: + inputs: + tagName: + required: false + type: string + description: "tagName to be associated with this release." + release: + types: [published] + # This can be used to automatically publish nightlies at UTC nighttime +# schedule: +# - cron: "0 2 * * *" # run at 2 AM UTC + +# This workflow will trigger on each push to the `release` branch to create or update a GitHub release, build your app, and upload the artifacts to the release. + +jobs: + publish-tauri: + permissions: + contents: write + strategy: + fail-fast: false + matrix: + include: + - platform: "macos-14" # for Arm based macs (M1 and above). + args: "--target aarch64-apple-darwin" + - platform: "macos-14" # for Intel based macs. + args: "--target x86_64-apple-darwin" + - platform: "ubuntu-22.04" # for Tauri v1 you could replace this with ubuntu-20.04. + args: "" + - platform: "ubuntu-22.04-arm" + args: "--target aarch64-unknown-linux-gnu" + - platform: "windows-latest" + args: "" + + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: setup pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: setup node + uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: pnpm + + + - name: install Rust nightly + uses: dtolnay/rust-toolchain@nightly + with: + # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds. + targets: ${{ matrix.platform == 'macos-14' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: './desktop/src-tauri -> target' + + - name: install dependencies (ubuntu only) + if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-22.04-arm' # This must match the platform value defined above. + run: | + sudo apt-get update + sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils + # webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2. + + - name: Import Apple Developer Certificate + if: matrix.platform == 'macos-14' + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security set-keychain-settings -t 3600 -u build.keychain + + # Add build.keychain to the user keychain search list so that codesign + # (invoked later by tauri-action WITHOUT an explicit --keychain) can + # resolve the signing identity from it. + security list-keychains -d user -s build.keychain $(security list-keychains -d user | tr -d '"') + + echo "Created keychain" + + curl https://droposs.org/drop.der --output drop.der + + # swiftc libs/appletrust/add-certificate.swift + # ./add-certificate drop.der + # rm add-certificate + + # echo "Added certificate to keychain using swift util" + + ## Script is equivalent to: + sudo security authorizationdb write com.apple.trust-settings.admin allow + sudo security add-trusted-cert -d -r trustRoot -k build.keychain -p codeSign -u -1 drop.der + sudo security authorizationdb remove com.apple.trust-settings.admin + + security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign + echo "Imported certificate" + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain + security find-identity -v -p codesigning build.keychain + + - name: Verify Certificate + if: matrix.platform == 'macos-14' + run: | + CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Drop OSS") + CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') + echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV + echo "Certificate imported. Using identity: $CERT_ID" + + - name: install frontend dependencies + run: pnpm install # change this to npm, pnpm or bun depending on which one you use. + + - uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Do NOT set APPLE_CERTIFICATE / APPLE_CERTIFICATE_PASSWORD here. Doing so + # makes tauri-action import the cert into its own throwaway keychain and + # look up the identity by Apple-only name prefixes (e.g. + # "Developer ID Application:"), which never matches our "Drop OSS" cert + # and fails with "failed to resolve signing identity". Instead we rely on + # the build.keychain prepared above and only pass the resolved identity. + APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }} + NO_STRIP: true + with: + tagName: ${{ inputs.print_tags || 'v__VERSION__' }} # the action automatically replaces \_\_VERSION\_\_ with the app version. + releaseName: "Auto-release v__VERSION__" + releaseBody: "See the assets to download this version and install. This release was created automatically." + releaseDraft: false + prerelease: true + args: ${{ matrix.args }} + projectPath: './desktop' \ No newline at end of file diff --git a/.github/workflows/droplet-ci.yml b/.github/workflows/droplet-ci.yml new file mode 100644 index 00000000..c125d2e7 --- /dev/null +++ b/.github/workflows/droplet-ci.yml @@ -0,0 +1,56 @@ +name: Droplet CI + +on: + push: + branches: [develop] + paths: + - "libraries/droplet/**" + - "libraries/droplet_types/**" + - "libraries/libarchive/**" + - ".github/workflows/droplet-ci.yml" + pull_request: + branches: [develop] + paths: + - "libraries/droplet/**" + - "libraries/droplet_types/**" + - "libraries/libarchive/**" + - ".github/workflows/droplet-ci.yml" + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +jobs: + ci: + name: Build, Test, Lint + runs-on: ubuntu-latest + defaults: + run: + working-directory: libraries/droplet + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt, clippy + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: "./libraries/droplet -> target" + + - name: Install libarchive + run: | + sudo apt-get update + sudo apt-get install -y libarchive-dev + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Run Clippy (lint) + run: cargo clippy --all-targets --all-features -- -D warnings + + - name: Run tests + run: cargo test --all-features --all --verbose diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 00000000..4b78b0c8 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,100 @@ +name: Deploy website to GitHub Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: [develop] + paths: + - "sites/promo/**" + - "sites/docs/**" + - "package.json" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - ".github/workflows/pages.yml" + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment per the "pages" group, skipping runs queued +# between the in-progress run and the latest queued one. cancel-in-progress defaults +# to false, so in-flight production deployments are allowed to complete. +concurrency: "pages" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "pnpm" + + # Only install the promo site (radiant) and docs site (docs-next) and their + # dependencies so the public website deploy stays decoupled from the + # server/desktop build pipelines. + - name: Install dependencies + run: pnpm install --filter radiant... --filter docs-next... + + - name: Setup Pages + id: setup_pages + uses: actions/configure-pages@v5 + + - name: Restore cache + uses: actions/cache@v4 + with: + path: | + sites/promo/.next/cache + # Generate a new cache whenever packages or source files change. + key: ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('sites/promo/**.[jt]s', 'sites/promo/**.[jt]sx') }} + # If source files changed but packages didn't, rebuild from a prior cache. + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml') }}- + + - name: Build promo site with Next.js + working-directory: sites/promo + run: pnpm run build + env: + PAGES_BASE_PATH: ${{ steps.setup_pages.outputs.base_path }} + + - name: Build docs site with Astro + working-directory: sites/docs + run: pnpm run build + + # Nest the Starlight docs (built with base: "/docs") inside the promo export + # so both ship from a single GitHub Pages deployment at /docs. + - name: Assemble docs into /docs + run: | + rm -rf sites/promo/out/docs + mkdir -p sites/promo/out/docs + cp -r sites/docs/dist/. sites/promo/out/docs/ + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: sites/promo/out + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml new file mode 100644 index 00000000..99511123 --- /dev/null +++ b/.github/workflows/server-ci.yml @@ -0,0 +1,71 @@ +name: Server CI + +on: + push: + branches: [develop] + paths: + - "server/**" + - "libraries/base/**" + - "package.json" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - ".github/workflows/server-ci.yml" + pull_request: + branches: [develop] + paths: + - "server/**" + - "libraries/base/**" + - "package.json" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - ".github/workflows/server-ci.yml" + +permissions: + contents: read + +jobs: + typecheck: + name: Typecheck + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Typecheck + working-directory: server + run: pnpm run typecheck + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Lint + working-directory: server + run: pnpm run lint diff --git a/.github/workflows/release.yml b/.github/workflows/server-release.yml similarity index 51% rename from .github/workflows/release.yml rename to .github/workflows/server-release.yml index 8045e05d..208f402f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/server-release.yml @@ -1,4 +1,4 @@ -name: Release Workflow +name: Build and release server on: workflow_dispatch: {} @@ -8,10 +8,20 @@ on: schedule: - cron: "0 2 * * *" # run at 2 AM UTC +env: + REGISTRY_IMAGE: ghcr.io/drop-oss/drop + jobs: - web: - name: Push website Docker image to registry - runs-on: ubuntu-latest + build: + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} permissions: packages: write contents: read @@ -19,12 +29,35 @@ jobs: - name: Check out the repo uses: actions/checkout@v4 with: - submodules: true fetch-depth: 3 # fix for when this gets triggered by tag fetch-tags: true ref: ${{ github.ref }} token: ${{ secrets.GITHUB_TOKEN }} + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY_IMAGE }} + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Determine final version id: get_final_ver run: | @@ -43,22 +76,58 @@ jobs: echo "Drop's release tag will be: $FINAL_VER" echo "final_ver=$FINAL_VER" >> $GITHUB_OUTPUT - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v3 + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 with: - buildkitd-flags: --debug + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + tags: ${{ env.REGISTRY_IMAGE }} + outputs: type=image,push-by-digest=true,name-canonical=true,push=true + provenance: mode=max + sbom: true + build-args: | + BUILD_DROP_VERSION=${{ steps.get_final_ver.outputs.final_ver }} + BUILD_GIT_REF=${{ github.sha }} - - name: Log in to the Container registry + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + needs: + - build + permissions: + packages: write + contents: read + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - name: Login to Docker Hub uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 @@ -77,33 +146,12 @@ jobs: # set latest tag for stable releases type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }} - - name: Cache - uses: actions/cache@v4 - id: cache - with: - path: cache-mount - key: cache-mount-${{ hashFiles('Dockerfile') }} + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) - - name: Restore Docker cache mounts - uses: reproducible-containers/buildkit-cache-dance@v3 - with: - builder: ${{ steps.setup-buildx.outputs.name }} - cache-dir: cache-mount - dockerfile: Dockerfile - skip-extraction: ${{ steps.cache.outputs.cache-hit }} - - - name: Build and push image - id: build-and-push - uses: docker/build-push-action@v6 - with: - context: . - push: true - provenance: mode=max - sbom: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - platforms: linux/amd64,linux/arm64 - cache-from: type=gha - cache-to: type=gha,mode=max - build-args: | - BUILD_DROP_VERSION=${{ steps.get_final_ver.outputs.final_ver }} + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} diff --git a/.gitignore b/.gitignore index 7188be16..763301fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,37 +1,2 @@ -# Nuxt dev/build outputs -.output -.data -.nuxt -.nitro -.cache -dist - -# Node dependencies -node_modules -.yarn - -# Logs -logs -*.log - -# Misc -.DS_Store -.fleet -.idea - -# Local env files -.env -.env.* -!.env.example - -.data - - -# deploy template -deploy-template/* - -!deploy-template/compose.yml - -# generated prisma client -/prisma/client -/prisma/validate \ No newline at end of file +dist/ +node_modules/ \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index de224957..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,54 +0,0 @@ -variables: - GIT_SUBMODULE_STRATEGY: recursive - -stages: - - build - -services: - - docker:24.0.5-dind - -before_script: - - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY" - -build: - stage: build - image: docker:latest - variables: - IMAGE_NAME: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA - LATEST_IMAGE_NAME: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_NAME:latest - PUBLISH_IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG - PUBLISH_LATEST_IMAGE_NAME: $CI_REGISTRY_IMAGE:latest - script: - - docker build -t $IMAGE_NAME . - - docker image tag $IMAGE_NAME $LATEST_IMAGE_NAME - - docker push $IMAGE_NAME - - docker push $LATEST_IMAGE_NAME - - | - if [ $CI_COMMIT_TAG ]; then - docker image tag $IMAGE_NAME $PUBLISH_IMAGE_NAME - docker image tag $IMAGE_NAME $PUBLISH_LATEST_IMAGE_NAME - docker push $PUBLISH_IMAGE_NAME $PUBLISH_LATEST_IMAGE_NAME - fi - -build-arm64: - stage: build - image: arm64v8/docker:latest - tags: - - aarch64 - variables: - IMAGE_NAME: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA-arm64 - LATEST_IMAGE_NAME: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_NAME:latest-arm64 - PUBLISH_IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG-arm64 - PUBLISH_LATEST_IMAGE_NAME: $CI_REGISTRY_IMAGE:latest-arm64 - script: - - docker build -t $IMAGE_NAME . --platform=linux/arm64 - - docker image tag $IMAGE_NAME $LATEST_IMAGE_NAME - - docker push $IMAGE_NAME - - docker push $LATEST_IMAGE_NAME - - | - if [ $CI_COMMIT_TAG ]; then - docker image tag $IMAGE_NAME $PUBLISH_IMAGE_NAME - docker image tag $IMAGE_NAME $PUBLISH_LATEST_IMAGE_NAME - docker push $PUBLISH_IMAGE_NAME - docker push $PUBLISH_LATEST_IMAGE_NAME - fi diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e24bb0cb..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "drop-base"] - path = drop-base - url = https://github.com/Drop-OSS/drop-base.git diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 3c9727cb..00000000 --- a/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -drop-base/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 9055675c..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,271 +0,0 @@ -# CONTRIBUTING GUIDELINES - -Drop is a community-driven project. Contribution is welcome, encouraged, and appreciated. -It is also essential for the development of the project. - -First, please take a moment to review our [code of conduct](CODE_OF_CONDUCT.md). - -These guidelines are an attempt at better addressing pending -issues and pull requests. Please read them closely. - -Foremost, be so kind as to [search](#use-the-search-luke). This ensures any contribution -you would make is not already covered. - - - -- [Reporting Issues](#reporting-issues) - - [You have a problem](#you-have-a-problem) - - [You have a suggestion](#you-have-a-suggestion) -- [Development](#development) - - [Note: `--optional` flag is **REQUIRED**](#note-optional-flag-is-required) - - [Tech Stack](#tech-stack) -- [Submitting Pull Requests](#submitting-pull-requests) - - [Getting started](#getting-started) - - [You have a solution](#you-have-a-solution) - - [You have an addition](#you-have-an-addition) -- [Use the Search, Luke](#use-the-search-luke) -- [Translation](#translation) -- [Commit Guidelines](#commit-guidelines) - - [Format](#format) - - [Style](#style) - - - -## Reporting Issues - -### You have a problem - -Please be so kind as to [search](#use-the-search-luke) for any open issue already covering -your problem. - -If you find one, comment on it, so we know more people are experiencing it. - - - -If you cannot find an existing issue, you can go ahead and create an issue with as much -detail as you can provide. -It should include the data gathered as indicated above, along with the following: - -1. How to reproduce the problem -2. What the correct behavior should be -3. What the actual behavior is - -Please copy to anyone relevant (e.g. plugin maintainers) by mentioning their GitHub handle -(starting with `@`) in your message. - -We will do our very best to help you. - -### You have a suggestion - -Please be so kind as to [search](#use-the-search-luke) for any open issue already covering -your suggestion. - -If you find one, comment on it, so we know more people are supporting it. - -If not, you can go ahead and create an issue. Please copy to anyone relevant (e.g. plugin -maintainers) by mentioning their GitHub handle (starting with `@`) in your message. - -## Development - -To get started with development, you need `yarn` and `docker compose` installed (or know how to set up a PostgreSQL database). - -Steps: - -1. Run `git submodule update --init --recursive` to setup submodules -1. Copy the `.env.example` to `.env` and add any api keys you need to use (e.g. for the Giant Bomb API) - - You can find other configuration options in the [documentation](https://docs.droposs.org/) -1. Create the `.data` directory with `mkdir .data` -1. Ensure that your user owns the `.data` directory with `sudo chown -R $(id -u $(whoami))` -1. Open up a terminal and navigate to `dev-tools`, and run `docker compose up` -1. Open up another terminal in the root directory of the project and run `yarn` and then `yarn prisma migrate dev` to setup the database -1. Run `yarn dev` to start the development server - -As part of the first-time bootstrap, Drop creates an invitation with the fixed id of 'admin'. So, to create an admin account, go to: - -http://localhost:3000/auth/register?id=admin - -### Tech Stack - -This repo uses the Nuxt 3 + TailwindCSS stack, with the `yarn` package manager. - -For the database, Drop uses Prisma connected to PostgreSQL. - -## Submitting Pull Requests - -### Getting started - -You should be familiar with the basics of -[contributing on GitHub](https://help.github.com/articles/using-pull-requests) - - - -You MUST always create PRs with _a dedicated branch_ based on the latest upstream tree. - -If you create your own PR, please make sure you do it right. Also be so kind as to reference -any issue that would be solved in the PR description body, -[for instance](https://help.github.com/articles/closing-issues-via-commit-messages/) -_"Fixes #XXXX"_ for issue number XXXX. - -### You have a solution - -Please be so kind as to [search](#use-the-search-luke) for any open issue already covering -your [problem](#you-have-a-problem), and any pending/merged/rejected PR covering your solution. - -If the solution is already reported, try it out and +1 the pull request if the -solution works ok. On the other hand, if you think your solution is better, post -it with reference to the other one so we can have both solutions to compare. - -If not, then go ahead and submit a PR. Please copy to anyone relevant (e.g. plugin -maintainers) by mentioning their GitHub handle (starting with `@`) in your message. - -### You have an addition - -We are absolutely accepting more contributions or features to drop, but please, make sure -that it is reasonable. Contributions that only cover a very small niche are likely to not -be added. - -Please be so kind as to [search](#use-the-search-luke) for any pending, merged or rejected Pull Requests -covering or related to what you want to add. - -If you find one, try it out and work with the author on a common solution. - -If not, then go ahead and submit a PR. Please copy to anyone relevant (e.g. plugin -maintainers) by mentioning their GitHub handle (starting with `@`) in your message. - -For any extensive change, such as API changes, you will have to find testers to +1 your PR. - ---- - -## Use the Search, Luke - -_May the Force (of past experiences) be with you_ - -GitHub offers [many search features](https://help.github.com/articles/searching-github/) -to help you check whether a similar contribution to yours already exists. Please search -before making any contribution, it avoids duplicates and eases maintenance. Trust me, -that works 90% of the time. - -You can also take a look at the [FAQ](https://github.com/Drop-OSS/docs/wiki/FAQ) -to be sure your contribution has not already come up. - -If all fails, your thing has probably not been reported yet, so you can go ahead -and [create an issue](#reporting-issues) or [submit a PR](#submitting-pull-requests). - ---- - -## Translation - -If you want to help translate Drop, we would love to have your help! You can do so on our [weblate instance](https://translate.droposs.org/engage/drop/). Please make sure to **read** the [message format syntax](https://vue-i18n.intlify.dev/guide/essentials/syntax.html) page before starting. We use this special syntax to enable high quality translations, and failure to do so may result in your translations **causing errors** in Drop. - -## Commit Guidelines - -Drop uses the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) -specification. The automatic changelog tool uses these to automatically generate -a changelog based on the commit messages. Here's a guide to writing a commit message -to allow this: - -### Format - -``` -type(scope)!: subject -``` - -- `type`: the type of the commit is one of the following: - - `feat`: new features. - - `fix`: bug fixes. - - `docs`: documentation changes. - - `refactor`: refactor of a particular code section without introducing - new features or bug fixes. - - `style`: code style improvements. - - `perf`: performance improvements. - - `test`: changes to the test suite. - - `ci`: changes to the CI system. - - `build`: changes to the build system. - - `chore`: for other changes that don't match previous types. This doesn't appear - in the changelog. - -- `scope`: section of the codebase that the commit makes changes to. If it makes changes to - many sections, or if no section in particular is modified, leave blank without the parentheses. - Examples: - - Commit that changes the `git` plugin: - - ``` - feat(git): add alias for `git commit` - ``` - - - Commit that changes many plugins: - - ``` - style: fix inline declaration of arrays - ``` - - For changes to plugins or themes, the scope should be the plugin or theme name: - - ✅ `fix(agnoster): commit subject` - - ❌ `fix(theme/agnoster): commit subject` - -- `!`: this goes after the `scope` (or the `type` if scope is empty), to indicate that the commit - introduces breaking changes. - - Optionally, you can specify a message that the changelog tool will display to the user to indicate - what's changed and what they can do to deal with it. You can use multiple lines to type this message; - the changelog parser will keep reading until the end of the commit message or until it finds an empty - line. - - Example (made up): - - ``` - style(agnoster)!: change dirty git repo glyph - - BREAKING CHANGE: the glyph to indicate when a git repository is dirty has - changed from a Powerline character to a standard UTF-8 emoji. You can - change it back by setting `ZSH_THEME_DIRTY_GLYPH`. - - Fixes #420 - - Co-authored-by: Username - ``` - -- `subject`: a brief description of the changes. This will be displayed in the changelog. If you need - to specify other details, you can use the commit body, but it won't be visible. - - Formatting tricks: the commit subject may contain: - - Links to related issues or PRs by writing `#issue`. This will be highlighted by the changelog tool: - - ``` - feat(archlinux): add support for aura AUR helper (#9467) - ``` - - - Formatted inline code by using backticks: the text between backticks will also be highlighted by - the changelog tool: - ``` - feat(shell-proxy): enable unexported `DEFAULT_PROXY` setting (#9774) - ``` - -### Style - -Try to keep the first commit line short. It's harder to do using this commit style but try to be -concise, and if you need more space, you can use the commit body. Try to make sure that the commit -subject is clear and precise enough that users will know what changed by just looking at the changelog. - ---- - - - -## Reference - -This contributing guide is adapted from the -[oh-my-zsh contribution guide](https://github.com/ohmyzsh/ohmyzsh/blob/master/CONTRIBUTING.md). -If there are any issues with this, please email admin@deepcore.dev. diff --git a/Dockerfile b/Dockerfile index d5629265..71eba487 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,56 +1,160 @@ # syntax=docker/dockerfile:1 -### Unified deps builder -# FROM node:lts-alpine AS deps -# WORKDIR /app -# COPY package.json yarn.lock ./ -# RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --network-timeout 1000000 --ignore-scripts - -### Build for app -FROM node:lts-alpine AS build-system -# setup workdir - has to be the same filepath as app because fuckin' Prisma +# Pinned to bookworm so the glibc here matches the torrential build stage +# and the libarchive runtime package is named `libarchive13` (trixie renames it to libarchive13t64). +FROM node:lts-bookworm-slim AS base +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable WORKDIR /app +## so corepack knows pnpm's version +COPY . . +## prevent prompt to download +ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 +## setup for offline +RUN corepack pack +## don't call out to network anymore +ENV COREPACK_ENABLE_NETWORK=0 + +### INSTALL DEPS ONCE +FROM base AS deps +RUN pnpm install --frozen-lockfile --ignore-scripts + +### BUILD TORRENTIAL +# Bookworm-pinned to match the runtime image's glibc (a trixie build would not run on bookworm). +FROM rustlang/rust:nightly-bookworm-slim AS torrential-build +## libarchive-dev + pkg-config let libarchive3-sys link libarchive dynamically (glibc). +## protobuf-compiler is kept for parity (torrential's build.rs uses a vendored protoc). +RUN apt-get update && apt-get install -y --no-install-recommends \ + pkg-config \ + libarchive-dev \ + protobuf-compiler \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /build +COPY . . +## Network resilience on hosts with poor/bursty throughput to crates.io: +## observed on .88 (~11KB/s, transfers occasionally stalling near-zero for +## tens of seconds) -- cargo's libcurl transport aborts a download once it +## sees less than CARGO_HTTP_LOW_SPEED_LIMIT bytes/sec for +## CARGO_HTTP_LOW_SPEED_TIMEOUT seconds (curl default: 10 bytes/s over 30s), +## which a merely-slow-but-alive connection can trip. A from-scratch build +## on such a host burned ~50 minutes downloading crates before one transfer +## exhausted CARGO_NET_RETRY's default 3 attempts and hard-failed the whole +## build (`failed to download from .../libc/0.2.178/download`, exit 101). +## Lower the low-speed floor so a trickling-but-live transfer isn't treated +## as dead, raise the retry budget, and give sparse-index/registry +## operations a longer ceiling before giving up outright. +ENV CARGO_HTTP_LOW_SPEED_LIMIT=1 +ENV CARGO_HTTP_LOW_SPEED_TIMEOUT=120 +ENV CARGO_NET_RETRY=10 +ENV CARGO_HTTP_TIMEOUT=600 +ENV CARGO_HTTP_MULTIPLEXING=false +## Resource-pressure cap: bound cargo's parallel job count on a +## memory-contended host. Empty (default) leaves CARGO_BUILD_JOBS unset, so +## cargo falls back to its own CPU-count autodetect -- pass +## --build-arg CARGO_JOBS=2 to constrain it. Rust codegen units are +## memory-hungry per job running in parallel; this is the Rust-side twin of +## SKIP_TYPECHECK below. +ARG CARGO_JOBS="" +## Persistent cache mounts for the registry index/downloads and the +## target/ build dir: any change ANYWHERE in the repo invalidates the +## `COPY . .` above (and therefore this RUN layer) on every build, so +## without these every single build re-downloads and recompiles every +## crate from zero -- the ~50-minute cost that motivated the network +## hardening above, paid again on every future commit. Cache mounts +## live in the buildx cache store, not the image layer, so they survive +## across builds regardless of layer invalidation. sharing=locked +## because this builder can run more than one image build at a time. +## Cache mounts are NOT part of the final layer filesystem, so the +## compiled binary is copied out to a stable (non-cached) path before +## the mount unwinds -- see the run-system stage's COPY --from below, +## which reads /build/torrential-bin, not target/release/torrential +## directly. +RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ + --mount=type=cache,target=/build/torrential/target,sharing=locked \ + if [ -n "$CARGO_JOBS" ]; then export CARGO_BUILD_JOBS="$CARGO_JOBS"; fi; \ + cargo build --release --manifest-path ./torrential/Cargo.toml && \ + cp ./torrential/target/release/torrential /build/torrential-bin + +### BUILD APP +FROM base AS build-system + ENV NODE_ENV=production ENV NUXT_TELEMETRY_DISABLED=1 -# ENV YARN_CACHE_FOLDER=/root/.yarn -# add git so drop can determine its git ref at build -# pnpm for build -RUN apk add --no-cache git pnpm +## add git so drop can determine its git ref at build +RUN apt-get update && apt-get install -y --no-install-recommends git \ + && rm -rf /var/lib/apt/lists/* -# copy deps and rest of project files -# COPY --from=deps /app/node_modules ./node_modules +## copy deps and rest of project files COPY . . +COPY --from=deps /app/node_modules ./node_modules + ARG BUILD_DROP_VERSION ARG BUILD_GIT_REF -# build -RUN pnpm import -RUN pnpm install --shamefully-hoist -RUN pnpm run build -# RUN --mount=type=cache,target=/root/.yarn yarn postinstall && yarn build +## Resource-pressure escape hatch (see nuxt.config.ts's typescript.typeCheck +## comment): --build-arg SKIP_TYPECHECK=true skips vue-tsc's full-project +## typecheck, the single most memory-hungry step of `nuxt build`, for +## building on a host that's already near its memory limit. Off by default. +ARG SKIP_TYPECHECK=false +ENV NUXT_BUILD_SKIP_TYPECHECK=$SKIP_TYPECHECK -### create run environment for Drop -FROM node:lts-alpine AS run-system -WORKDIR /app +## Resource-pressure cap: bound Node's old-space heap for the +## postinstall/build step (Vite/Rolldown/vue-tsc are all Node processes). +## Empty (default) leaves NODE_OPTIONS unset, i.e. Node's own default heap +## sizing -- pass --build-arg NODE_MAX_OLD_SPACE=4096 to constrain it. +ARG NODE_MAX_OLD_SPACE="" +ENV NODE_OPTIONS="" +RUN if [ -n "$NODE_MAX_OLD_SPACE" ]; then export NODE_OPTIONS="--max-old-space-size=$NODE_MAX_OLD_SPACE"; fi; \ + pnpm run --filter=drop postinstall && pnpm run --filter=drop build + + +# create run environment for Drop +FROM base AS run-system ENV NODE_ENV=production ENV NUXT_TELEMETRY_DISABLED=1 +# The base stage's `COPY . .` puts the whole repo into the runtime WORKDIR (/app), +# but at runtime only the artifacts copied explicitly below are needed. Drop the +# inherited `torrential` source dir: the service resolves the binary by scanning +# the cwd for `torrential`, and a directory there is spawned as ./torrential and +# fails with EACCES. With it gone, resolution falls through to the `torrential` +# binary installed on PATH (/usr/bin/torrential) below. +RUN rm -rf /app/torrential + # RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn add --network-timeout 1000000 --no-lockfile --ignore-scripts prisma@6.11.1 -RUN apk add --no-cache pnpm -RUN pnpm install prisma@6.11.1 +## runtime deps: +## - libarchive13: torrential now links libarchive dynamically (glibc build) +## - p7zip-full: provides the 7z CLI +## - nginx: front-end proxy +## - openssl + ca-certificates: required by Prisma's query engine on Debian +## pnpm itself is provided by corepack (enabled in the base stage) +RUN apt-get update && apt-get install -y --no-install-recommends \ + libarchive13 \ + p7zip-full \ + nginx \ + openssl \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* +RUN pnpm install prisma@7.7.0 --global # init prisma to download all required files RUN pnpm prisma init -COPY --from=build-system /app/package.json ./ -COPY --from=build-system /app/.output ./app -COPY --from=build-system /app/prisma ./prisma -COPY --from=build-system /app/build ./startup +COPY --from=build-system /app/server/prisma.config.ts ./ +COPY --from=build-system /app/server/.output ./app +COPY --from=build-system /app/server/prisma ./prisma +COPY --from=build-system /app/server/build ./startup +COPY --from=build-system /app/server/build/nginx.conf /nginx.conf +COPY --from=torrential-build /build/torrential-bin /usr/bin/torrential ENV LIBRARY="/library" ENV DATA="/data" +ENV NGINX_CONFIG="/nginx.conf" +# Nuxt's port +ENV PORT=4000 CMD ["sh", "/app/startup/launch.sh"] diff --git a/README.md b/README.md index 33cc5d0e..13d96fa6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # Drop [![Website](https://img.shields.io/badge/website-000000?style=for-the-badge&logo=About.me&logoColor=white)](https://droposs.org) -[![Docs](https://img.shields.io/badge/DOCS-black?style=for-the-badge&logo=docusaurus)](https://docs.droposs.org/) +[![Docs](https://img.shields.io/badge/DOCS-black?style=for-the-badge&logo=docusaurus)](https://droposs.org/docs) [![Static Badge](https://img.shields.io/badge/FORUM-blue?style=for-the-badge)](https://forum.droposs.org) [![GitHub License](https://img.shields.io/badge/AGPL--3.0-red?style=for-the-badge)](LICENSE) [![Discord](https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/ACq4qZp4a9) @@ -28,7 +28,7 @@ Drop is an open-source game distribution platform, similar to GameVault or Steam ## Deployment -See our documentation on how to [deploy Drop](https://docs.droposs.org/docs/guides/quickstart) for more information. +See our documentation on how to [deploy Drop](https://droposs.org/docs/admin/quickstart) for more information. ## Contributing diff --git a/SECURITY.md b/SECURITY.md index 8fc42ef6..2561dac8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,4 +2,4 @@ To report a vulnerability, please DO NOT create an issue for it as this may lead to the vulnerability being exploited before it -can be fixed. Instead, please email [security@deepcore.dev](mailto:security@deepcore.dev) +can be fixed. Instead, please email [security@droposs.org](mailto:security@droposs.org) diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 00000000..7447f89a --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1 @@ +/bin \ No newline at end of file diff --git a/backend/core/database.go b/backend/core/database.go new file mode 100644 index 00000000..a50decab --- /dev/null +++ b/backend/core/database.go @@ -0,0 +1,19 @@ +package core + +import ( + "context" + "fmt" + "os" + + "github.com/jackc/pgx/v5" +) + +func connect() { + conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL")) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err) + os.Exit(1) + } + defer conn.Close(context.Background()) + +} diff --git a/backend/core/go.mod b/backend/core/go.mod new file mode 100644 index 00000000..ae1311a4 --- /dev/null +++ b/backend/core/go.mod @@ -0,0 +1,11 @@ +module drop/core + +go 1.26.1 + +require github.com/jackc/pgx/v5 v5.9.2 + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + golang.org/x/text v0.29.0 // indirect +) diff --git a/backend/core/go.sum b/backend/core/go.sum new file mode 100644 index 00000000..f5b2410e --- /dev/null +++ b/backend/core/go.sum @@ -0,0 +1,26 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 00000000..00366967 --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,5 @@ +module drop + +go 1.26.1 + +require github.com/gorilla/mux v1.8.1 diff --git a/backend/go.sum b/backend/go.sum new file mode 100644 index 00000000..5d28444c --- /dev/null +++ b/backend/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= \ No newline at end of file diff --git a/backend/go.work b/backend/go.work new file mode 100644 index 00000000..f1da3508 --- /dev/null +++ b/backend/go.work @@ -0,0 +1,3 @@ +go 1.26.1 + +use ./core diff --git a/backend/go.work.sum b/backend/go.work.sum new file mode 100644 index 00000000..c1225400 --- /dev/null +++ b/backend/go.work.sum @@ -0,0 +1,9 @@ +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/backend/main.go b/backend/main.go new file mode 100644 index 00000000..d5d0c856 --- /dev/null +++ b/backend/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "strings" + + "github.com/gorilla/mux" +) + +func handler(res http.ResponseWriter, req *http.Request) { + fmt.Fprintf(res, "G'day there mate") +} +func routingMiddleware(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + url := *r.URL + url.Path = strings.TrimSuffix(r.URL.Path, "/") + r.URL = &url + + h.ServeHTTP(w, r) + }) +} + +func main() { + r := mux.NewRouter().StrictSlash(true) + r.Use(routingMiddleware) + + r.HandleFunc("/api/v1", handler) + + srv := &http.Server{ + Addr: ":3433", + Handler: r, + } + log.Printf("starting drop server on :3433") + srv.ListenAndServe() +} diff --git a/changelog.md b/changelog.md deleted file mode 100644 index ac147b22..00000000 --- a/changelog.md +++ /dev/null @@ -1,544 +0,0 @@ -## Release 0.2.0-beta - -### Fixes - -- fix recursive dirs util #02d6346 -- Fix username length requirement #0a5a649 -- remove dynamic imports #0f10626 -- fix for missing developers or publishers #25fc957 -- split prisma schemas #2859005 -- results are returned alphabetically #33d3770 -- update prisma schemas #36776cc -- removed global flag #43e32b4 -- properly disconnect websockets from task handler #5358f1f -- follow best practices #54c5d55 -- future lenience #5c78b20 -- fix width of token breaking things #61d88c3 -- fixed websocket authentication #62ea9a1 -- fix delta manifest generation #6df560c -- admin invitation w/ system user #8463e35 -- properly import icons #8945196 -- prisma create footprint #952ece8 -- game panel now always shows 3 lines exactly #9c2249e -- remove unnecessary import #a361c38 -- fix disconnect code #a8f2106 -- fix types #b511b40 -- add drop-base as git submodule #b75ebd1 -- Update README.md with discord link #c6bb21d -- fix expires requirement in the admin endpoint #c7b675f -- fix always being created as admin #c7eb11a -- moved icons and created PlatformClient so we can use the enum on the frontend #cada630 -- recurse submodules #db103de -- fix FATAL: "root"... message #dbb315a -- only show versions that are directories #ef8f3ae - -### Features - -- update prisma & delete games #089c3e0 -- manual handshake #12e3125 -- fetch game endpoint #1f4d075 -- under the hood organisation and consolidation #26a31f6 -- 'no images' slide on image carousel #28baabc -- improve feedback when metadata fails #2c19e13 -- introduction of 'system user' #2c21a23 -- change name, description and icon #2cfe75a -- 'manual' metadata provider #2f52a16 -- add disabled state #38fc6b8 -- overhauled version importing #39d7ce7 -- automatically create library folder if it doesn't exist #39fe9d5 -- smoother bar in admin task ui #4488ae2 -- add noWrapper option #4f9b949 -- add version metadata route #5393db3 -- completed admin UI, with minor changes to backend #599da0e -- adjust gradient #5a1f841 -- keep track of last connected #69e4c25 -- added notification system w/ interwoven refactoring #6e6f09d -- content length header for chunk downloads #76bceb1 -- add title to tab #7b0756c -- add button to open in admin panel #7b3b919 -- client capability framework + peer API configuration #7d72a86 -- customisable image carousel and new layout #937954f -- support more types #9b12d45 -- generate a server certificate for mtls APIs #9c4b6f3 -- new endpoints, ui and beginnings of main store page #9cbdcbc -- backend #a309651 -- more subtle design improvements #a815542 -- add aden's carousel pagination design #a86045c -- add header #a8a152e -- client side search #b50e27f -- new ws handler #bc0c47c -- user widget now redirects to actual page #bfafe02 -- require lowercase usernames #d7160ab -- more ui improvements #e408ac5 -- add modifying game descriptions #e505e58 -- mobile nav #e5cf13f -- slightly improved game page #e796b46 -- game carousel #ecc819e -- add enum dictionary type #f2e0182 -- improved ux #f3ed0f6 -- cleanup and raw accessors #f7d767d -- add support for overriding UMU id #fd4a7d1 -- add .sh for linux #fe9373a - -### Other Changes - -- quexeky -- fixed manifest generation #03a37f7 -- manual ci/cd #03b0b0c -- ability to fetch client certs for p2p #0a715fe -- disable tls in build #0f80fcd -- Updated README.md #17971e0 -- Merge pull request #18 from Drop-OSS/develop -- initial work on metadata system #196f87c -- more ui #1bd19ad -- remove log statements #1d5e1bd -- small fixes & SSR disabled #1f575b2 -- update information and setup guide #2236622 -- metadata engine #22ac7f6 -- Update CONTRIBUTING.md #2309407 -- slight bug fixes and clean up #24a0d11 -- almst complete admin ui and initial store designs #27070b6 -- handshakes #2b4382d -- user mobile header #2e44ef3 -- more consistent naming for globals #305de9f -- replaced markdown-it with micromark #31e8359 -- fixes to store page for mobile clients #328b9ba -- game version re-ordering #329c74d -- verbose yarn install #36568c3 -- patch for no version check in manifest generation #395219d -- migrate bcrypt to bcryptjs #3a51c9c -- added download chunk endpoint #3dd6062 -- Update README.md #425934d -- build only ci #4273a20 -- object storage + full permission system + testing #435551c -- rename admin socket session map #44c6028 -- bump droplet and add vue carousel #46551f9 -- version importing #46c8f0c -- back to yarn, with nuxt telemetry force disabled #46d35ad -- finished object endpoints #486bce8 -- update dependencies and add note about optional dependencies #4fa771a -- use configuration from docs for ci/cd #52315d0 -- slight fixes to register logic #583301f -- removed yarn.lock #584bcf1 -- Version bump #5f29c28 -- immutable application settings framework #5fe2036 -- fixed docker daemon location #62a111b -- copy autodevops configuration #6328c24 -- Delete .gitlab-ci.yml #69f341b -- admin ui shell #6b5e48d -- bump @drop/droplet version for windows developers #6ba5cdd -- Add LICENSE #6e2dc89 -- custom dind #716eac7 -- task API #718f5ba -- use gitlab ci variable declaration #7194d35 -- move icons into dedicated folder #74fa671 -- another stage of client authentication #7523e53 -- refactoring #7869043 -- moved windows logo into logos dir #789d3ba -- updated text colours across app #7a88f4c -- starting docs infra #7d2a1c6 -- more cleaning #7e17626 -- slight patch to rename query to be more consistent #7f4db0c -- move to raw docker #803752e -- server side and user client side completed for registration #848a611 -- beginnings of download implementation #8674ac7 -- more consistent naming for object handler #87230fb -- use autodevops build stage #886beb6 -- Updated tailwind config #88c95d6 -- change name of store file #8999303 -- split prisma schemas #9011cf5 -- client initiate #909432a -- more client routes to support Drop app update #91b7e10 -- additional polish and QoL features #93bc143 -- upload images to games #9b7ee4e -- migrate to pnpm due to ci/cd issues with yarn #9cb2d6d -- run yarn install in CI/CD non interactively #a208fbe -- completed game importing; partial work on version importing #a7c33e7 -- remove canvas from dependencies #a8f58eb -- fix registry authentication #ad25d3e -- consolidate type utils #adb4b73 -- Updated README.md #b0ef675 -- add proper carousel to store page #b2ab827 -- move to yarn v2 #b744671 -- remove client API deadweight #b9ae26c -- add expires field #be6c30d -- ca groundwork #bfafd2a -- cleanup & polish #c355f6f -- remove bcrypt (debug) #c3914cc -- non rounded bottom #c4391d3 -- failed gracefully on invalid chunk index #c4a3e4e -- update deploy template #c4a419f -- migrate to new droplet ca system #c4d8113 -- docker based deployment #c5d00b4 -- updated CONTRIBUTING.md #cd0d2bf -- update prisma version #ce0a9ab -- README update #ceacd84 -- patch metadata handler #cf578bd -- Added SECURITY.md #d3d93b0 -- finalised client APIs and authentication method #d4e2dc8 -- Update README.md #db916bf -- object storage interface + utility functions #de388a9 -- initial commit #e1a789f -- fixed task system #e1c1d7e -- Update file chunk.get.ts #e4339c3 -- ui groundwork #e52f072 -- Update changelog #eadcaa1 -- check for no version in manifest generation #eb3f9f9 -- break into single column store on lg devices #ecb381e -- better server side signin redirects #ef13b68 -- patch signin #f3672f8 - -_changelog generated by_ [go-conventional-commits](https://github.com/joselitofilho/go-conventional-commits) - -## Release 0.2.0-beta - -### Fixes - -- fix recursive dirs util #02d6346 -- Fix username length requirement #0a5a649 -- remove dynamic imports #0f10626 -- fix for missing developers or publishers #25fc957 -- split prisma schemas #2859005 -- results are returned alphabetically #33d3770 -- update prisma schemas #36776cc -- removed global flag #43e32b4 -- properly disconnect websockets from task handler #5358f1f -- follow best practices #54c5d55 -- future lenience #5c78b20 -- fix width of token breaking things #61d88c3 -- fixed websocket authentication #62ea9a1 -- fix delta manifest generation #6df560c -- admin invitation w/ system user #8463e35 -- properly import icons #8945196 -- prisma create footprint #952ece8 -- game panel now always shows 3 lines exactly #9c2249e -- remove unnecessary import #a361c38 -- fix disconnect code #a8f2106 -- fix types #b511b40 -- add drop-base as git submodule #b75ebd1 -- Update README.md with discord link #c6bb21d -- fix expires requirement in the admin endpoint #c7b675f -- fix always being created as admin #c7eb11a -- moved icons and created PlatformClient so we can use the enum on the frontend #cada630 -- recurse submodules #db103de -- fix FATAL: "root"... message #dbb315a -- only show versions that are directories #ef8f3ae - -### Features - -- update prisma & delete games #089c3e0 -- manual handshake #12e3125 -- fetch game endpoint #1f4d075 -- under the hood organisation and consolidation #26a31f6 -- 'no images' slide on image carousel #28baabc -- improve feedback when metadata fails #2c19e13 -- introduction of 'system user' #2c21a23 -- change name, description and icon #2cfe75a -- 'manual' metadata provider #2f52a16 -- add disabled state #38fc6b8 -- overhauled version importing #39d7ce7 -- automatically create library folder if it doesn't exist #39fe9d5 -- smoother bar in admin task ui #4488ae2 -- add noWrapper option #4f9b949 -- add version metadata route #5393db3 -- completed admin UI, with minor changes to backend #599da0e -- adjust gradient #5a1f841 -- keep track of last connected #69e4c25 -- added notification system w/ interwoven refactoring #6e6f09d -- content length header for chunk downloads #76bceb1 -- add title to tab #7b0756c -- add button to open in admin panel #7b3b919 -- client capability framework + peer API configuration #7d72a86 -- customisable image carousel and new layout #937954f -- support more types #9b12d45 -- generate a server certificate for mtls APIs #9c4b6f3 -- new endpoints, ui and beginnings of main store page #9cbdcbc -- backend #a309651 -- more subtle design improvements #a815542 -- add aden's carousel pagination design #a86045c -- add header #a8a152e -- client side search #b50e27f -- new ws handler #bc0c47c -- user widget now redirects to actual page #bfafe02 -- require lowercase usernames #d7160ab -- more ui improvements #e408ac5 -- add modifying game descriptions #e505e58 -- mobile nav #e5cf13f -- slightly improved game page #e796b46 -- game carousel #ecc819e -- add enum dictionary type #f2e0182 -- improved ux #f3ed0f6 -- cleanup and raw accessors #f7d767d -- add support for overriding UMU id #fd4a7d1 -- add .sh for linux #fe9373a - -### Other Changes - -- quexeky -- fixed manifest generation #03a37f7 -- manual ci/cd #03b0b0c -- ability to fetch client certs for p2p #0a715fe -- disable tls in build #0f80fcd -- Updated README.md #17971e0 -- Merge pull request #18 from Drop-OSS/develop -- initial work on metadata system #196f87c -- more ui #1bd19ad -- remove log statements #1d5e1bd -- small fixes & SSR disabled #1f575b2 -- update information and setup guide #2236622 -- metadata engine #22ac7f6 -- Update CONTRIBUTING.md #2309407 -- slight bug fixes and clean up #24a0d11 -- almst complete admin ui and initial store designs #27070b6 -- handshakes #2b4382d -- user mobile header #2e44ef3 -- more consistent naming for globals #305de9f -- replaced markdown-it with micromark #31e8359 -- fixes to store page for mobile clients #328b9ba -- game version re-ordering #329c74d -- verbose yarn install #36568c3 -- patch for no version check in manifest generation #395219d -- migrate bcrypt to bcryptjs #3a51c9c -- added download chunk endpoint #3dd6062 -- Update README.md #425934d -- build only ci #4273a20 -- object storage + full permission system + testing #435551c -- rename admin socket session map #44c6028 -- bump droplet and add vue carousel #46551f9 -- version importing #46c8f0c -- back to yarn, with nuxt telemetry force disabled #46d35ad -- finished object endpoints #486bce8 -- update dependencies and add note about optional dependencies #4fa771a -- use configuration from docs for ci/cd #52315d0 -- slight fixes to register logic #583301f -- removed yarn.lock #584bcf1 -- Version bump #5f29c28 -- immutable application settings framework #5fe2036 -- fixed docker daemon location #62a111b -- copy autodevops configuration #6328c24 -- Delete .gitlab-ci.yml #69f341b -- admin ui shell #6b5e48d -- bump @drop/droplet version for windows developers #6ba5cdd -- Add LICENSE #6e2dc89 -- custom dind #716eac7 -- task API #718f5ba -- use gitlab ci variable declaration #7194d35 -- move icons into dedicated folder #74fa671 -- another stage of client authentication #7523e53 -- refactoring #7869043 -- moved windows logo into logos dir #789d3ba -- updated text colours across app #7a88f4c -- starting docs infra #7d2a1c6 -- more cleaning #7e17626 -- slight patch to rename query to be more consistent #7f4db0c -- move to raw docker #803752e -- server side and user client side completed for registration #848a611 -- beginnings of download implementation #8674ac7 -- more consistent naming for object handler #87230fb -- use autodevops build stage #886beb6 -- Updated tailwind config #88c95d6 -- change name of store file #8999303 -- split prisma schemas #9011cf5 -- client initiate #909432a -- more client routes to support Drop app update #91b7e10 -- additional polish and QoL features #93bc143 -- upload images to games #9b7ee4e -- migrate to pnpm due to ci/cd issues with yarn #9cb2d6d -- run yarn install in CI/CD non interactively #a208fbe -- completed game importing; partial work on version importing #a7c33e7 -- remove canvas from dependencies #a8f58eb -- fix registry authentication #ad25d3e -- consolidate type utils #adb4b73 -- Updated README.md #b0ef675 -- add proper carousel to store page #b2ab827 -- move to yarn v2 #b744671 -- remove client API deadweight #b9ae26c -- add expires field #be6c30d -- ca groundwork #bfafd2a -- cleanup & polish #c355f6f -- remove bcrypt (debug) #c3914cc -- non rounded bottom #c4391d3 -- failed gracefully on invalid chunk index #c4a3e4e -- update deploy template #c4a419f -- migrate to new droplet ca system #c4d8113 -- docker based deployment #c5d00b4 -- updated CONTRIBUTING.md #cd0d2bf -- update prisma version #ce0a9ab -- README update #ceacd84 -- patch metadata handler #cf578bd -- Added SECURITY.md #d3d93b0 -- finalised client APIs and authentication method #d4e2dc8 -- Update README.md #db916bf -- object storage interface + utility functions #de388a9 -- initial commit #e1a789f -- fixed task system #e1c1d7e -- Update file chunk.get.ts #e4339c3 -- ui groundwork #e52f072 -- Update changelog #eadcaa1 -- check for no version in manifest generation #eb3f9f9 -- break into single column store on lg devices #ecb381e -- better server side signin redirects #ef13b68 -- patch signin #f3672f8 - -_changelog generated by_ [go-conventional-commits](https://github.com/joselitofilho/go-conventional-commits) - -## Release 0.1.0-beta - -### Fixes - -- remove dynamic imports #0f10626 -- fix for missing developers or publishers #25fc957 -- split prisma schemas #2859005 -- results are returned alphabetically #33d3770 -- properly disconnect websockets from task handler #5358f1f -- follow best practices #54c5d55 -- future lenience #5c78b20 -- fixed websocket authentication #62ea9a1 -- fix delta manifest generation #6df560c -- admin invitation w/ system user #8463e35 -- properly import icons #8945196 -- prisma create footprint #952ece8 -- game panel now always shows 3 lines exactly #9c2249e -- remove unnecessary import #a361c38 -- fix types #b511b40 -- fix expires requirement in the admin endpoint #c7b675f -- moved icons and created PlatformClient so we can use the enum on the frontend #cada630 -- only show versions that are directories #ef8f3ae - -### Features - -- update prisma & delete games #089c3e0 -- fetch game endpoint #1f4d075 -- under the hood organisation and consolidation #26a31f6 -- introduction of 'system user' #2c21a23 -- automatically create library folder if it doesn't exist #39fe9d5 -- smoother bar in admin task ui #4488ae2 -- add version metadata route #5393db3 -- completed admin UI, with minor changes to backend #599da0e -- keep track of last connected #69e4c25 -- added notification system w/ interwoven refactoring #6e6f09d -- content length header for chunk downloads #76bceb1 -- add title to tab #7b0756c -- add button to open in admin panel #7b3b919 -- client capability framework + peer API configuration #7d72a86 -- generate a server certificate for mtls APIs #9c4b6f3 -- new endpoints, ui and beginnings of main store page #9cbdcbc -- more subtle design improvements #a815542 -- add header #a8a152e -- client side search #b50e27f -- new ws handler #bc0c47c -- user widget now redirects to actual page #bfafe02 -- require lowercase usernames #d7160ab -- more ui improvements #e408ac5 -- slightly improved game page #e796b46 -- game carousel #ecc819e -- add enum dictionary type #f2e0182 -- cleanup and raw accessors #f7d767d -- add support for overriding UMU id #fd4a7d1 - -### Other Changes - -- quexeky -- fixed manifest generation #03a37f7 -- manual ci/cd #03b0b0c -- ability to fetch client certs for p2p #0a715fe -- disable tls in build #0f80fcd -- Updated README.md #17971e0 -- initial work on metadata system #196f87c -- more ui #1bd19ad -- remove log statements #1d5e1bd -- small fixes & SSR disabled #1f575b2 -- update information and setup guide #2236622 -- metadata engine #22ac7f6 -- Update CONTRIBUTING.md #2309407 -- slight bug fixes and clean up #24a0d11 -- almst complete admin ui and initial store designs #27070b6 -- handshakes #2b4382d -- user mobile header #2e44ef3 -- more consistent naming for globals #305de9f -- replaced markdown-it with micromark #31e8359 -- fixes to store page for mobile clients #328b9ba -- game version re-ordering #329c74d -- verbose yarn install #36568c3 -- patch for no version check in manifest generation #395219d -- migrate bcrypt to bcryptjs #3a51c9c -- added download chunk endpoint #3dd6062 -- Update README.md #425934d -- build only ci #4273a20 -- object storage + full permission system + testing #435551c -- rename admin socket session map #44c6028 -- bump droplet and add vue carousel #46551f9 -- version importing #46c8f0c -- back to yarn, with nuxt telemetry force disabled #46d35ad -- finished object endpoints #486bce8 -- update dependencies and add note about optional dependencies #4fa771a -- use configuration from docs for ci/cd #52315d0 -- slight fixes to register logic #583301f -- removed yarn.lock #584bcf1 -- Version bump #5f29c28 -- immutable application settings framework #5fe2036 -- fixed docker daemon location #62a111b -- copy autodevops configuration #6328c24 -- Delete .gitlab-ci.yml #69f341b -- admin ui shell #6b5e48d -- bump @drop/droplet version for windows developers #6ba5cdd -- Add LICENSE #6e2dc89 -- task API #718f5ba -- use gitlab ci variable declaration #7194d35 -- move icons into dedicated folder #74fa671 -- another stage of client authentication #7523e53 -- refactoring #7869043 -- moved windows logo into logos dir #789d3ba -- updated text colours across app #7a88f4c -- starting docs infra #7d2a1c6 -- more cleaning #7e17626 -- slight patch to rename query to be more consistent #7f4db0c -- move to raw docker #803752e -- server side and user client side completed for registration #848a611 -- beginnings of download implementation #8674ac7 -- more consistent naming for object handler #87230fb -- use autodevops build stage #886beb6 -- Updated tailwind config #88c95d6 -- change name of store file #8999303 -- split prisma schemas #9011cf5 -- client initiate #909432a -- more client routes to support Drop app update #91b7e10 -- additional polish and QoL features #93bc143 -- upload images to games #9b7ee4e -- migrate to pnpm due to ci/cd issues with yarn #9cb2d6d -- run yarn install in CI/CD non interactively #a208fbe -- completed game importing; partial work on version importing #a7c33e7 -- remove canvas from dependencies #a8f58eb -- fix registry authentication #ad25d3e -- consolidate type utils #adb4b73 -- Updated README.md #b0ef675 -- add proper carousel to store page #b2ab827 -- move to yarn v2 #b744671 -- remove client API deadweight #b9ae26c -- add expires field #be6c30d -- ca groundwork #bfafd2a -- cleanup & polish #c355f6f -- remove bcrypt (debug) #c3914cc -- non rounded bottom #c4391d3 -- failed gracefully on invalid chunk index #c4a3e4e -- update deploy template #c4a419f -- migrate to new droplet ca system #c4d8113 -- docker based deployment #c5d00b4 -- updated CONTRIBUTING.md #cd0d2bf -- update prisma version #ce0a9ab -- README update #ceacd84 -- patch metadata handler #cf578bd -- Added SECURITY.md #d3d93b0 -- finalised client APIs and authentication method #d4e2dc8 -- Update README.md #db916bf -- object storage interface + utility functions #de388a9 -- initial commit #e1a789f -- fixed task system #e1c1d7e -- Update file chunk.get.ts #e4339c3 -- ui groundwork #e52f072 -- check for no version in manifest generation #eb3f9f9 -- break into single column store on lg devices #ecb381e -- better server side signin redirects #ef13b68 -- patch signin #f3672f8 - -_changelog generated by_ [go-conventional-commits](https://github.com/joselitofilho/go-conventional-commits) diff --git a/cli/.envrc b/cli/.envrc new file mode 100644 index 00000000..3550a30f --- /dev/null +++ b/cli/.envrc @@ -0,0 +1 @@ +use flake diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 00000000..83807067 --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1,4 @@ +/target +logs/ +.vscode +.direnv \ No newline at end of file diff --git a/cli/Cargo.lock b/cli/Cargo.lock new file mode 100644 index 00000000..093b9014 --- /dev/null +++ b/cli/Cargo.lock @@ -0,0 +1,3396 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive 0.5.1", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive 0.6.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.17", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", + "synstructure 0.13.2", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", + "synstructure 0.13.2", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "gloo-timers", + "tokio", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.52.0", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "console" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.61.2", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs 0.6.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs 0.7.1", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "dialoguer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" +dependencies = [ + "console", + "shell-words", + "tempfile", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "downpour" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "clap", + "console", + "dialoguer", + "dirs", + "droplet-rs", + "fern", + "futures", + "indicatif", + "log", + "opendal", + "rand 0.9.3", + "reqwest 0.13.1", + "serde", + "serde_json", + "tokio", + "tokio-util", + "url", + "webbrowser", +] + +[[package]] +name = "droplet-rs" +version = "0.16.3" +dependencies = [ + "anyhow", + "async-trait", + "droplet_types", + "dyn-clone", + "futures", + "getrandom 0.3.4", + "hex", + "humansize", + "libarchive-drop", + "rcgen", + "ring", + "serde", + "serde_json", + "sha2", + "speedometer", + "test-generator", + "time", + "tokio", + "uuid", + "x509-parser 0.17.0", +] + +[[package]] +name = "droplet_types" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 1.0.109", + "synstructure 0.12.6", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fern" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" +dependencies = [ + "colored", + "log", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "indicatif" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" +dependencies = [ + "console", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.61.2", +] + +[[package]] +name = "jiff-static" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libarchive-drop" +version = "0.1.1" +dependencies = [ + "libarchive3-sys", + "libc", +] + +[[package]] +name = "libarchive3-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd3beae8f59a4c7a806523269b5392037577c150446e88d684dfa6de6031ca7" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs 0.6.2", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs 0.7.1", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opendal" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d075ab8a203a6ab4bc1bce0a4b9fe486a72bf8b939037f4b78d95386384bc80a" +dependencies = [ + "anyhow", + "backon", + "base64", + "bytes", + "crc32c", + "futures", + "getrandom 0.2.16", + "http", + "http-body", + "jiff", + "log", + "md-5", + "percent-encoding", + "quick-xml 0.38.4", + "reqsign", + "reqwest 0.12.28", + "serde", + "serde_json", + "tokio", + "url", + "uuid", +] + +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid 0.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.3", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2 1.0.103", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser 0.16.0", + "yasna", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.17", +] + +[[package]] +name = "reqsign" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43451dbf3590a7590684c25fb8d12ecdcc90ed3ac123433e500447c7d77ed701" +dependencies = [ + "anyhow", + "async-trait", + "base64", + "chrono", + "form_urlencoded", + "getrandom 0.2.16", + "hex", + "hmac", + "home", + "http", + "log", + "percent-encoding", + "quick-xml 0.37.5", + "rand 0.8.5", + "reqwest 0.12.28", + "rust-ini", + "serde", + "serde_json", + "sha1", + "sha2", + "tokio", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "reqwest" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "serde_json" +version = "1.0.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "speedometer" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2789736092fa21b44baf8590acb4b360cb91f0f597bd6c1f1741ca9644c95c1e" +dependencies = [ + "failure", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid 0.1.0", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 1.0.109", + "unicode-xid 0.2.6", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "test-generator" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b23be2add79223226e1cb6446cb3e37506a5927089870687a0f1149bb7a073a" +dependencies = [ + "glob", + "proc-macro2 0.4.30", + "quote 0.6.13", + "syn 0.15.44", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "time" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote 1.0.43", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs 0.6.2", + "data-encoding", + "der-parser 9.0.0", + "lazy_static", + "nom", + "oid-registry 0.7.1", + "ring", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs 0.7.1", + "data-encoding", + "der-parser 10.0.0", + "lazy_static", + "nom", + "oid-registry 0.8.1", + "ring", + "rusticata-macros", + "thiserror 2.0.17", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", + "synstructure 0.13.2", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", + "synstructure 0.13.2", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.43", + "syn 2.0.114", +] + +[[package]] +name = "zmij" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 00000000..f269f5a8 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "downpour" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.100" +async-trait = "0.1.89" +chrono = "0.4.43" +clap = { version = "4.5.54", features = ["derive"] } +console = "0.16.2" +dialoguer = "0.12.0" +dirs = "6.0.0" +droplet-rs = { path = "../libraries/droplet" } +fern = { version = "0.7.1", features = ["colored"] } +futures = "0.3.31" +indicatif = "0.18.3" +log = "0.4.29" +opendal = { version = "0.55.0", features = ["services-s3"] } +rand = "0.9.3" +reqwest = { version = "0.13.1", features = ["json"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.148" +tokio = { version = "1.48.0", features = ["fs", "macros"] } +tokio-util = { version = "0.7.18", features = ["compat"] } +url = "2.5.8" +webbrowser = "1.0.6" diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 00000000..77643fd9 --- /dev/null +++ b/cli/README.md @@ -0,0 +1,3 @@ +# CLI (`downpour`) + +The cli way to access Drop. Used for admin tasks that require local access, like uploading game content. \ No newline at end of file diff --git a/cli/flake.lock b/cli/flake.lock new file mode 100644 index 00000000..94100467 --- /dev/null +++ b/cli/flake.lock @@ -0,0 +1,96 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1768564909, + "narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1744536153, + "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1768704795, + "narHash": "sha256-Y33TAp2BHEcuspYvcmBXXD0qdvjftv73PwyKTDOjoSY=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "4b7472a78857ac789fb26616040f55cfcbd36c6e", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/cli/flake.nix b/cli/flake.nix new file mode 100644 index 00000000..7c806fd3 --- /dev/null +++ b/cli/flake.nix @@ -0,0 +1,52 @@ +{ + description = "Drop-OSS app development environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay.url = "github:oxalica/rust-overlay"; + }; + + outputs = + { + self, + nixpkgs, + flake-utils, + rust-overlay, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { + inherit system overlays; + }; + libraries = with pkgs; [ + glib + glibc + openssl + ]; + in + { + devShells.default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + pkg-config + git + rust-bin.nightly.latest.default + rust-analyzer + cargo-expand + ]; + + + buildInputs = libraries; + + shellHook = '' + export LD_LIBRARY_PATH="${ + pkgs.lib.makeLibraryPath libraries + }:$LD_LIBRARY_PATH" + echo "Downpour development environment loaded" + ''; + }; + } + ); +} diff --git a/cli/rust-toolchain.toml b/cli/rust-toolchain.toml new file mode 100644 index 00000000..271800cb --- /dev/null +++ b/cli/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/cli/spec.md b/cli/spec.md new file mode 100644 index 00000000..e5401750 --- /dev/null +++ b/cli/spec.md @@ -0,0 +1,10 @@ +# Downpour CLI spec +`downpour [command] --opts` +## Commands: +- new - creates/initalizes a depot at the endpoint. Creates manifest.json and speedtest +- connect [name] - connects to an s3 endpoint and saves the endpoint to some sort of credentials file. Name is either as provided or the hostname of the endpoint +- upload - uploads game as described before. Should fail if depot isn't initialized with new from above +- copy - copies between two depots +- mark [exists/absent] - modifies depot's manifest.json to show content exists or is absent without copying (for third party copies) +- rename - renames an endpoint [NEEDS API ROUTES - can't do yet] +- delete - delete an endpoint [NEEDS API ROUTES - can't do yet] diff --git a/cli/src/cli.rs b/cli/src/cli.rs new file mode 100644 index 00000000..318fc457 --- /dev/null +++ b/cli/src/cli.rs @@ -0,0 +1,69 @@ +use clap::{Args, Parser, Subcommand, ValueEnum}; + +use crate::{commands::connect::config_option::ConfigOptionCli, interactive_variable}; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +pub struct Cli { + #[command(subcommand)] + pub command: Commands, + + /// Specify data file path + #[arg(short, long)] + pub data: Option, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Configures downpour endpoints + Connect { + #[arg(short, long)] + name: Option, + #[command(subcommand)] + option: ConfigOptionCli, + }, + /// Uploads new game version to depot + Upload { + #[clap(flatten)] + info: UploadInfoCli, + #[arg(short, long)] + /// Alias of a given connection + name: Option, + }, +} + +#[derive(Args)] +pub struct UploadInfo { + pub path: String, + pub game_id: String, + pub version_id: String, +} +#[derive(Args)] +pub struct UploadInfoCli { + /// Relative path to new version files + #[arg(short, long, default_value_t = String::from("."))] + pub path: String, + /// ID of game to attach to + #[arg(short, long)] + pub game_id: Option, + /// Version ID to attach to + #[arg(short, long)] + pub version_id: Option, +} +impl UploadInfoCli { + pub fn interactive_configure(self) -> UploadInfo { + let path = self.path; + interactive_variable!(self, game_id, "Game ID"); + interactive_variable!(self, version_id, "Version ID"); + UploadInfo { + path, + game_id, + version_id, + } + } +} + +#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)] +pub enum UploadStyle { + S3, +} diff --git a/cli/src/commands/connect/config.rs b/cli/src/commands/connect/config.rs new file mode 100644 index 00000000..549109d9 --- /dev/null +++ b/cli/src/commands/connect/config.rs @@ -0,0 +1,152 @@ +use crate::{ + commands::connect::{ + config_option::{ConfigOption, ConfigOptionCli}, + configurable::Configure, + speedtest::{SPEEDTEST_PATH, Speedtest}, + }, + manifest::DepotManifest, +}; +use dialoguer::{Confirm, theme::ColorfulTheme}; +use futures::AsyncWriteExt; +use indicatif::{ProgressBar, ProgressStyle}; +use log::{debug, info}; +use opendal::Operator; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, fs, ops::Not}; +use tokio_util::compat::FuturesAsyncWriteCompatExt; + +const CONFIG_DIR: &str = "downpour/config.json"; + +#[derive(Serialize, Deserialize)] +pub struct Config { + configurations: HashMap, + active: Option, +} +impl Config { + pub fn new() -> Self { + Self { + configurations: HashMap::new(), + active: None, + } + } + pub fn exists(&self, name: &String) -> bool { + self.configurations.contains_key(name) + } + pub fn save(&self) -> anyhow::Result<()> { + let json = serde_json::to_string(self)?; + let save_path = dirs::config_dir() + .expect("Apparently your home directory doesn't exist") // Should probably formalise that error + .join(CONFIG_DIR); + fs::create_dir_all(save_path.parent().unwrap())?; + fs::write(save_path, json)?; + Ok(()) + } + pub fn read() -> Self { + let save_path = dirs::config_dir() + .expect("Apparently your home directory doesn't exist") // Should probably formalise that error + .join(CONFIG_DIR); + if fs::exists(&save_path) + .unwrap_or_else(|_| panic!("Could not read save path {:#?}", &save_path)) + { + serde_json::from_str(&fs::read_to_string(save_path).unwrap()).unwrap() + } else { + Config::new() + } + } + pub fn add_item(&mut self, name: String, object: ConfigOption) { + if matches!(object, ConfigOption::S3(..)) { + self.active = Some(name.clone()) + } + self.configurations.insert(name, object); + self.save().expect("Failed to save config"); + } + + pub fn get_active(&self) -> Option<&ConfigOption> { + if let Some(active) = &self.active { + self.configurations.get(active) + } else { + None + } + } + pub fn get>(&self, name: T) -> Option<&ConfigOption> { + self.configurations.get(name.as_ref()) + } +} + +pub async fn manage_configuration( + config: &mut Config, + name: Option, + option: ConfigOptionCli, +) -> anyhow::Result<()> { + let mut name = name; + if let Some(name) = &name + && config.exists(name) + { + let confirm = Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(format!( + "An entry already exists with the name \"{}\". Would you like to overwrite it?", + name + )) + .interact()?; + if !confirm { + return Err(anyhow::anyhow!("User cancelled action")); + } + } + let config_option = match option { + ConfigOptionCli::S3(s3_config_cli) => s3_config_cli.clone().configure(&mut name).await?, + }; + let name = name.expect("Default name was not provided by ConfigOption. This is a bug"); + config.add_item(name, config_option.clone()); + let operator = config_option.build()?; + + generate_manifest(&operator).await?; + info!("Finished uploading manifest"); + generate_speedtest(&operator).await?; + info!("Finished uploading speedtest"); + Ok(()) +} + +async fn generate_speedtest(operator: &Operator) -> anyhow::Result<()> { + // Workaround to operator.exists("...") also logging a 404 warning + let lister = operator.list_with(SPEEDTEST_PATH).limit(1).await?; + if lister.is_empty().not() { + info!("Speedtest already exists on Depot. Skipping speedtest upload..."); + return Ok(()); + } + let mut writer = operator + .writer(SPEEDTEST_PATH) + .await? + .into_futures_async_write() + .compat_write(); + + let progress_bar = ProgressBar::new(10_000).with_style( + ProgressStyle::default_bar() + .template("[{elapsed_precise}] [ETA {eta}] {bar} {percent_precise}%") + .unwrap(), + ); + + let mut reader = Speedtest::new(|progress| { + let progress_int = (progress * 100f32).round() as u64; + progress_bar.set_position(progress_int); + }); + let written = tokio::io::copy(&mut reader, &mut writer).await?; + progress_bar.finish(); + debug!("Wrote {} bytes to {:?}", written, operator.info()); + writer.into_inner().close().await?; + debug!("Closed writer"); + Ok(()) +} + +async fn generate_manifest(operator: &Operator) -> anyhow::Result<()> { + let lister = operator.list_with("manifest.json").limit(1).await?; + if lister.is_empty().not() { + info!("Manifest already exists on Depot. Skipping manifest upload..."); + return Ok(()); + } + let data = DepotManifest::new(); + operator + .write("manifest.json", serde_json::to_string(&data)?) + .await?; + + Ok(()) +} diff --git a/cli/src/commands/connect/config_option.rs b/cli/src/commands/connect/config_option.rs new file mode 100644 index 00000000..d0257bbd --- /dev/null +++ b/cli/src/commands/connect/config_option.rs @@ -0,0 +1,27 @@ +use clap::Subcommand; +use opendal::{Operator, layers::LoggingLayer}; +use serde::{Deserialize, Serialize}; + +use crate::{ + commands::connect::s3::{S3Config, S3ConfigCli}, + operator_builder::OperatorBuilder, +}; + +#[derive(Subcommand, Clone)] +pub enum ConfigOptionCli { + // Connect to any S3-compatible endpoint + S3(S3ConfigCli), +} +#[derive(Serialize, Deserialize, Clone)] +pub enum ConfigOption { + S3(S3Config), +} + +impl ConfigOption { + pub fn build(&self) -> anyhow::Result { + Ok(match self { + ConfigOption::S3(s3_config) => s3_config.build()?, + } + .layer(LoggingLayer::default())) + } +} diff --git a/cli/src/commands/connect/configurable.rs b/cli/src/commands/connect/configurable.rs new file mode 100644 index 00000000..d653be30 --- /dev/null +++ b/cli/src/commands/connect/configurable.rs @@ -0,0 +1,5 @@ +use crate::commands::connect::config_option::ConfigOption; + +pub trait Configure { + async fn configure(self, name: &mut Option) -> anyhow::Result; +} diff --git a/cli/src/commands/connect/interactive.rs b/cli/src/commands/connect/interactive.rs new file mode 100644 index 00000000..8e823198 --- /dev/null +++ b/cli/src/commands/connect/interactive.rs @@ -0,0 +1,47 @@ +use std::str::FromStr; + +use dialoguer::{Input, theme::ColorfulTheme}; + +#[macro_export] +macro_rules! interactive_variable { + ($value:ident, $var:ident, $prompt:expr) => { + let $var = if let Some($var) = $value.$var { + $var + } else { + $crate::commands::connect::interactive::query_variable($prompt).unwrap() + }; + }; +} +#[macro_export] +macro_rules! interactive_optional_variable { + ($value:ident, $var:ident, $prompt:expr) => { + let $var = if let Some($var) = $value.$var { + Some($var) + } else { + $crate::commands::connect::interactive::query_optional_variable($prompt).unwrap() + }; + }; +} +pub fn query_variable(prompt: impl ToString) -> dialoguer::Result +where + ::Err: ToString, +{ + Input::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt.to_string()) + .interact_text() +} +pub fn query_optional_variable( + prompt: impl ToString, +) -> dialoguer::Result> +where + ::Err: ToString, +{ + let input: T = Input::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt.to_string()) + .allow_empty(true) + .interact_text()?; + if input.to_string().is_empty() { + return Ok(None); + } + Ok(Some(input)) +} diff --git a/cli/src/commands/connect/mod.rs b/cli/src/commands/connect/mod.rs new file mode 100644 index 00000000..37f13750 --- /dev/null +++ b/cli/src/commands/connect/mod.rs @@ -0,0 +1,7 @@ +pub mod config; +pub mod configurable; +pub mod s3; +#[macro_use] +pub mod interactive; +pub mod config_option; +pub mod speedtest; diff --git a/cli/src/commands/connect/s3.rs b/cli/src/commands/connect/s3.rs new file mode 100644 index 00000000..5b30d101 --- /dev/null +++ b/cli/src/commands/connect/s3.rs @@ -0,0 +1,67 @@ +use clap::Args; +use opendal::Operator; +use serde::{Deserialize, Serialize}; + +use crate::{ + commands::connect::{config_option::ConfigOption, configurable::Configure}, + interactive_variable, + operator_builder::OperatorBuilder, +}; + +#[derive(Args, Clone)] +pub struct S3ConfigCli { + key_id: Option, + secret_key: Option, + endpoint: Option, + region: Option, + bucket_name: Option, + root: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct S3Config { + key_id: String, + secret_key: String, + endpoint: String, + region: String, + bucket_name: String, + root: Option, +} + +impl Configure for S3ConfigCli { + async fn configure(self, name: &mut Option) -> anyhow::Result { + interactive_variable!(self, key_id, "S3 Key ID"); + interactive_variable!(self, secret_key, "S3 Secret Key"); + interactive_variable!(self, region, "S3 Region"); + interactive_variable!(self, bucket_name, "S3 Bucket Name"); + interactive_variable!(self, endpoint, "S3 Endpoint"); + if let None = name { + *name = Some(endpoint.clone()); + } + Ok(ConfigOption::S3(S3Config { + secret_key, + key_id, + region, + bucket_name, + endpoint, + root: self.root, + })) + } +} + +impl OperatorBuilder for S3Config { + fn build(&self) -> anyhow::Result { + let builder = opendal::services::S3::default() + .access_key_id(&self.key_id) + .secret_access_key(&self.secret_key) + .region(&self.region) + .endpoint(&self.endpoint) + .root(self.root.as_deref().unwrap_or("/")) + .bucket(&self.bucket_name) + .disable_config_load(); + + let op: Operator = Operator::new(builder)?.finish(); + + Ok(op) + } +} diff --git a/cli/src/commands/connect/speedtest.rs b/cli/src/commands/connect/speedtest.rs new file mode 100644 index 00000000..a039469f --- /dev/null +++ b/cli/src/commands/connect/speedtest.rs @@ -0,0 +1,41 @@ +use rand::{RngCore, SeedableRng, rng, rngs::StdRng}; +use tokio::io::AsyncRead; + +#[derive(Clone, Debug)] +pub struct Speedtest { + core: rand::rngs::StdRng, + to_write: usize, + callback: Box, +} +pub const SPEEDTEST_BYTES: usize = 64 * 1024 * 1024; +pub const SPEEDTEST_PATH: &str = "speedtest"; + +impl AsyncRead for Speedtest { + fn poll_read( + self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + let mut s = self; + let to_write = buf.remaining().min(s.to_write); + + let filled = { + let fill_slice = buf.initialize_unfilled_to(to_write); + s.core.fill_bytes(fill_slice); + fill_slice.len() + }; + s.to_write = s.to_write.saturating_sub(filled); + (s.callback)((1f32 - (s.to_write as f32 / SPEEDTEST_BYTES as f32)) * 100f32); + buf.advance(filled); + std::task::Poll::Ready(Ok(())) + } +} +impl Speedtest { + pub fn new(callback: F) -> Self { + Self { + core: StdRng::from_rng(&mut rng()), + to_write: SPEEDTEST_BYTES, + callback: Box::new(callback), + } + } +} diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs new file mode 100644 index 00000000..0a9a925a --- /dev/null +++ b/cli/src/commands/mod.rs @@ -0,0 +1,2 @@ +pub mod connect; +pub mod upload; diff --git a/cli/src/commands/upload/interface.rs b/cli/src/commands/upload/interface.rs new file mode 100644 index 00000000..dab98556 --- /dev/null +++ b/cli/src/commands/upload/interface.rs @@ -0,0 +1,79 @@ +use std::path::Path; + +use crate::{ + cli::UploadInfo, + commands::connect::{config::Config, config_option::ConfigOption}, + manifest::{ClosureFactory, CompressionOption, DepotManifest, generate_v2_manifest}, + operator_builder::OperatorBuilder, +}; +use futures::AsyncWriteExt; +use log::info; +use opendal::{FuturesAsyncWriter, Operator}; +use tokio_util::compat::{Compat, FuturesAsyncWriteCompatExt}; + +pub async fn upload( + upload_info: &UploadInfo, + config: Config, + name: &Option, +) -> anyhow::Result<()> { + let game_id = upload_info.game_id.clone(); + let path = upload_info.path.clone(); + let version_id = upload_info.version_id.clone(); + + let operator = get_operator(config, name)?; + + let mut existing_depot_manifest = get_depot_manifest(&operator).await?; + + info!("Uploading chunks"); + + let v2_manifest = generate_v2_manifest( + Path::new(&path), + ClosureFactory::new( + async move |id: String| { + info!("Uploading chunk id {id}"); + let writer = operator + .writer(&format!("{game_id}/{version_id}/{id}")) + .await + .unwrap() + .into_futures_async_write() + .compat_write(); + writer + }, + |writer: Compat| async { + writer.into_inner().close().await.unwrap(); + }, + ), + ) + .await?; + + info!("Finished uploading chunks"); + + existing_depot_manifest.append( + upload_info.game_id.to_string(), + upload_info.version_id.to_string(), + CompressionOption::None, + ); + Ok(()) +} + +async fn get_depot_manifest(operator: &Operator) -> Result { + let existing_depot_manifest = operator.read("manifest.json").await?.to_bytes(); + let existing_depot_manifest: DepotManifest = + serde_json::from_slice(existing_depot_manifest.as_ref())?; + Ok(existing_depot_manifest) +} + +fn get_operator(config: Config, name: &Option) -> anyhow::Result { + let operator = match if let Some(name) = name { + config + .get(name) + .ok_or(anyhow::anyhow!("Name does not exist"))? + } else { + config.get_active().ok_or(anyhow::anyhow!( + "No active connection set. Please specify with --name" + ))? + } { + ConfigOption::S3(s3_config) => s3_config.build()?, + }; + Ok(operator) +} diff --git a/cli/src/commands/upload/mod.rs b/cli/src/commands/upload/mod.rs new file mode 100644 index 00000000..8d3d626b --- /dev/null +++ b/cli/src/commands/upload/mod.rs @@ -0,0 +1 @@ +pub mod interface; diff --git a/cli/src/logging.rs b/cli/src/logging.rs new file mode 100644 index 00000000..6a2abef1 --- /dev/null +++ b/cli/src/logging.rs @@ -0,0 +1,53 @@ +use fern::colors::{Color, ColoredLevelConfig}; +use log::LevelFilter; +use std::env; +use std::fs; +use std::io; + +pub fn configure_logging() -> anyhow::Result<()> { + let log_level = env::var("RUST_LOG") + .unwrap_or_else(|_| "info".to_string()) + .parse::()?; + + let log_dir = env::var("LOG_FILE_DIR").unwrap_or_else(|_| "logs".to_string()); + + fs::create_dir_all(&log_dir)?; + + let colors = ColoredLevelConfig::new() + .error(Color::Red) + .warn(Color::Yellow) + .info(Color::Blue) + .debug(Color::Green) + .trace(Color::Magenta); + + fern::Dispatch::new() + .chain( + fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "[{}] {}: {}", + chrono::Local::now().format("%H:%M:%S%.3f"), + colors.color(record.level()), + message + )) + }) + .chain(io::stdout()), + ) + .chain( + fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "[{}] {} {} - {}", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"), + record.level(), + record.target(), + message + )) + }) + .chain(fern::log_file(format!("{}/app.log", log_dir))?), + ) + .level(log_level) + .apply()?; + + Ok(()) +} diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 00000000..91cc05e7 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,34 @@ +#![feature(async_fn_traits)] + +use crate::commands::connect::config::manage_configuration; +use crate::{ + cli::{Cli, Commands}, + commands::connect::config::Config, + commands::upload, +}; +use clap::Parser; +mod cli; +mod commands; +mod logging; +mod manifest; +mod operator_builder; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + crate::logging::configure_logging()?; + + let cli = Cli::parse(); + + let mut config = Config::read(); + match cli.command { + Commands::Connect { name, option } => { + manage_configuration(&mut config, name, option).await? + } + Commands::Upload { info, name } => { + let info = info.interactive_configure(); + upload::interface::upload(&info, config, &name).await?; + } + }; + + Ok(()) +} diff --git a/cli/src/manifest.rs b/cli/src/manifest.rs new file mode 100644 index 00000000..7ad8d42d --- /dev/null +++ b/cli/src/manifest.rs @@ -0,0 +1,114 @@ +use std::{collections::HashMap, path::Path}; + +use async_trait::async_trait; +use droplet_rs::manifest::{Manifest, ManifestWriterFactory, generate_manifest_rusty}; +use indicatif::{ProgressBar, ProgressStyle}; +use log::info; +use serde::{Deserialize, Serialize}; +use tokio::io::AsyncWrite; + +#[derive(Serialize, Deserialize)] +pub struct DepotManifest { + content: HashMap, +} +#[derive(Serialize, Deserialize)] +struct DepotManifestGameData { + version_id: String, + compression: CompressionOption, +} +#[derive(Serialize, Deserialize)] +pub enum CompressionOption { + None, + Gzip, + Zstd, +} +impl DepotManifest { + pub fn new() -> Self { + Self { + content: HashMap::new(), + } + } + pub fn append(&mut self, game_id: String, version_id: String, compression: CompressionOption) { + self.content.insert( + game_id, + DepotManifestGameData { + version_id, + compression, + }, + ); + } +} + +pub struct ClosureFactory +where + Writer: AsyncWrite + Unpin, + Factory: AsyncFn(String) -> Writer, + Closer: AsyncFn(Writer), +{ + writer: Factory, + closer: Closer, +} + +#[async_trait] +impl< + W: AsyncWrite + Unpin + Send + Sync, + F: AsyncFn(String) -> W + Send + Sync + 'static, + C: AsyncFn(W) + Send + Sync, +> ManifestWriterFactory for ClosureFactory +where + for<'a> F::CallRefFuture<'a>: Send, + for<'b> C::CallRefFuture<'b>: Send, +{ + type Writer = W; + + async fn create(&self, id: String) -> anyhow::Result { + let func = &self.writer; + let output = func(id).await; + Ok(output) + } + async fn close(&self, writer: Self::Writer) -> anyhow::Result<()> { + let func = &self.closer; + func(writer).await; + Ok(()) + } +} + +impl< + W: AsyncWrite + Unpin + Send + Sync, + F: AsyncFn(String) -> W + Send + Sync + 'static, + C: AsyncFn(W) + Sync, +> ClosureFactory +where + for<'a> F::CallRefFuture<'a>: Send, + for<'b> C::CallRefFuture<'b>: Send, +{ + pub fn new(f: F, c: C) -> Self { + Self { + writer: f, + closer: c, + } + } +} + +pub async fn generate_v2_manifest(dir: &Path, factory: Factory) -> anyhow::Result +where + Factory: ManifestWriterFactory, +{ + let progress_bar = ProgressBar::new(10_000).with_style( + ProgressStyle::default_bar() + .template("[{elapsed_precise}] [ETA {eta}] {bar} {percent_precise}%") + .unwrap(), + ); + + generate_manifest_rusty( + dir, + |progress| { + let progress_int = (progress * 100f32).round() as u64; + progress_bar.set_position(progress_int); + }, + |log| progress_bar.suspend(|| info!("{}", log)), + Some(&factory), + None, + ) + .await +} diff --git a/cli/src/operator_builder.rs b/cli/src/operator_builder.rs new file mode 100644 index 00000000..acc272e1 --- /dev/null +++ b/cli/src/operator_builder.rs @@ -0,0 +1,5 @@ +use opendal::Operator; + +pub trait OperatorBuilder { + fn build(&self) -> anyhow::Result; +} diff --git a/components/GameEditor/Version.vue b/components/GameEditor/Version.vue deleted file mode 100644 index d2d38ad6..00000000 --- a/components/GameEditor/Version.vue +++ /dev/null @@ -1,196 +0,0 @@ - - - - diff --git a/components/MultiItemSelector.vue b/components/MultiItemSelector.vue deleted file mode 100644 index d99fec74..00000000 --- a/components/MultiItemSelector.vue +++ /dev/null @@ -1,115 +0,0 @@ - - - diff --git a/composables/icons.ts b/composables/icons.ts deleted file mode 100644 index 247cf837..00000000 --- a/composables/icons.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IconsLinuxLogo, IconsWindowsLogo, IconsMacLogo } from "#components"; -import { PlatformClient } from "./types"; - -export const PLATFORM_ICONS = { - [PlatformClient.Linux]: IconsLinuxLogo, - [PlatformClient.Windows]: IconsWindowsLogo, - [PlatformClient.macOS]: IconsMacLogo, -}; diff --git a/composables/user.ts b/composables/user.ts deleted file mode 100644 index 68513980..00000000 --- a/composables/user.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { UserModel } from "~/prisma/client/models"; - -// undefined = haven't check -// null = check, no user -// {} = check, user - -export const useUser = () => useState(undefined); -export const updateUser = async () => { - const user = useUser(); - if (user.value === null) return; - - user.value = await $dropFetch("/api/v1/user"); -}; diff --git a/desktop/.bashrc b/desktop/.bashrc new file mode 100644 index 00000000..e69de29b diff --git a/desktop/.gitignore b/desktop/.gitignore new file mode 100644 index 00000000..b2300d25 --- /dev/null +++ b/desktop/.gitignore @@ -0,0 +1,34 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +.nuxt +.output + +src-tauri/flamegraph.svg +src-tauri/perf* + +/*.AppImage +/squashfs-root + +/target/ diff --git a/desktop/.gitlab-ci.yml b/desktop/.gitlab-ci.yml new file mode 100644 index 00000000..0fc3abe6 --- /dev/null +++ b/desktop/.gitlab-ci.yml @@ -0,0 +1,31 @@ +stages: + - build + +build-linux: + stage: build + image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/rustlang/rust:nightly + script: + - apt-get update -y + - apt-get install yarnpkg libsoup-3.0-0 libsoup-3.0-dev libatk-adaptor libgtk-3-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev -y + - yarnpkg + - export + - export RUST_LOG=warn + - yarnpkg tauri build + - cp src-tauri/target/release/bundle/deb/*.deb . + - cp src-tauri/target/release/bundle/rpm/*.rpm . + - cp src-tauri/target/release/bundle/appimage/*.AppImage . + artifacts: + paths: + - "*.{deb,rpm,AppImage}" + +build-windows: + stage: build + tags: + - windows + script: + - yarn + - yarn tauri build + - cp src-tauri/target/release/bundle/nsis/*.exe . + artifacts: + paths: + - "*.exe" diff --git a/desktop/.nvmrc b/desktop/.nvmrc new file mode 100644 index 00000000..40994076 --- /dev/null +++ b/desktop/.nvmrc @@ -0,0 +1 @@ +23 diff --git a/desktop/.vscode/extensions.json b/desktop/.vscode/extensions.json new file mode 100644 index 00000000..cf4385bd --- /dev/null +++ b/desktop/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "Vue.volar", + "tauri-apps.tauri-vscode", + "rust-lang.rust-analyzer" + ] +} diff --git a/desktop/DEBUG.md b/desktop/DEBUG.md new file mode 100644 index 00000000..2a4478f3 --- /dev/null +++ b/desktop/DEBUG.md @@ -0,0 +1,15 @@ +# How to create Flamegraph + +Run this in `src-tauri`: +``` +WEBKIT_DISABLE_DMABUF_RENDERER=1 CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --release +``` + +You can leave out `WEBKIT_DISABLE_DMABUF_RENDERER=1` if you're not on NVIDIA/Linux + +And then run this in the root dir: +``` +yarn dev --port 1432 +``` + +And then do what you want, and it'll create the flamegraph for you diff --git a/LICENSE b/desktop/LICENSE similarity index 100% rename from LICENSE rename to desktop/LICENSE diff --git a/desktop/README.md b/desktop/README.md new file mode 100644 index 00000000..87a26cc3 --- /dev/null +++ b/desktop/README.md @@ -0,0 +1,3 @@ +# Desktop + +The official desktop client for Drop. \ No newline at end of file diff --git a/desktop/build.mjs b/desktop/build.mjs new file mode 100644 index 00000000..aeb341d1 --- /dev/null +++ b/desktop/build.mjs @@ -0,0 +1,48 @@ +import fs from "fs"; +import process from "process"; +import childProcess from "child_process"; +import createLogger from "pino"; + +const OUTPUT = "./.output"; +const logger = createLogger({ transport: { target: "pino-pretty" } }); + +async function spawn(exec, opts) { + const output = childProcess.spawn(exec, { ...opts, shell: true }); + output.stdout.on("data", (data) => { + process.stdout.write(data); + }); + output.stderr.on("data", (data) => { + process.stderr.write(data); + }); + + return await new Promise((resolve, reject) => { + output.on("error", (err) => reject(err)); + output.on("exit", () => resolve()); + }); +} + +const views = fs.readdirSync(".").filter((view) => { + const expectedPath = `./${view}/package.json`; + return fs.existsSync(expectedPath); +}); + +fs.mkdirSync(OUTPUT, { recursive: true }); + +for (const view of views) { + const loggerChild = logger.child({}); + process.chdir(`./${view}`); + + loggerChild.info(`Install deps for "${view}"`); + await spawn("pnpm install"); + + loggerChild.info(`Building "${view}"`); + await spawn("pnpm run build", { + env: { ...process.env, NUXT_APP_BASE_URL: `/${view}/` }, + }); + + process.chdir(".."); + + fs.cpSync(`./${view}/.output/public`, `${OUTPUT}/${view}`, { + recursive: true, + }); +} \ No newline at end of file diff --git a/desktop/changelog.md b/desktop/changelog.md new file mode 100644 index 00000000..691c4601 --- /dev/null +++ b/desktop/changelog.md @@ -0,0 +1,463 @@ + + +## Release 0.2.0-beta + +### Fixes +- Re-enabled killing games #005bab2 +- fixed queue manipulation and waiting for downloads #01260f0 +- fix logic error in detecting dir #04368ff +- absolute executable invoke #17759c4 +- don't crash download manager if multiple errors come in #21204de +- clear stale data before requesting new #327628b +- fixed completed indexes #39f2ebd +- add file & line to console logs #4d8eadc +- Games not launching due to string semantics #4ef49cc +- Added error handling for chunk request errors #4fc0855 +- Chunk counting logic error #5ba151f +- modal stack doesn't cover whole app #5db9ae5 +- use set_file_name instead of pushing to strings #60d0a48 +- use of completed signal, and pause/resuming #64d7f64 +- add message about nonce expiration #6a8d0af +- Added "LIbrary Failed to Update" content to recover from library load fail #76bae3d +- Restored RUST_LOG env functionality #7a0cf4f +- initialise doesn't recreate default install dir #7a3841b +- update routes for new server #7ab53f3 +- use vendored flag #7c8089e +- fix poorly designed parsing for executables with spaces #7c90d2b +- assorted fixes #89ea34c +- Added Settings component #8aad64f +- windows build #8d9234f +- fix ugly scrollbars on edge webview #95f2174 +- windows shadow #9a8cc59 +- add better error message #9af0d08 +- Broken command invoke logic in settings/downloads.vue #9e29aa7 +- Accidentally was attempting to lock onto something that was already in scope #9e82a0b +- fix incorrect error assumptions & update types #a17311a +- Re-enabled uninstalling apps #a56ee25 +- types #af056c0 +- fix other metadata endpoints #c2f54c1 +- Re-enabled deep links #c3f6222 +- added console as an appender #d12bf15 +- remove unnecessary unstable feature #d5ac1b0 +- fix install button #d7b0302 +- stop loading on error #d83aae6 +- use unix timestamp to avoid invalid characters in filename #dafce24 +- Renamed game_id to id #dceaa56 +- use chrono library to generate timestamps #e22e6d8 +- clear stale data before requesting new #e72662c +- fix scrollbars on edge webview #f09605a +- update readme instructions #f0c47d8 +- Adding usize to completed_contexts_lock instead of &usize #f508186 + + +### Features +- Game kill tauri command #01e6162 +- add debug page #02f8591 +- Add signout functionality (#16) #0a0d9d6 +- queue and library UIs #0a20139 +- add note about more install dirs #139bc0c +- Using SerializeDisplay for better error management with Result #170fde5 +- add pre-launch log to file #17f8d76 +- Added option to change root directory #1aa52c0 +- add speed and time remaining information #1f899ec +- lockless tracking of downloaded chunks #2183585 +- quit button #239b8d5 +- use shift or DEBUG RUST_LOG to show Debug Info #245a84d +- Added database corruption dialog #25ba200 +- only allow downloads for supported platforms #269dcbb +- add installed ui in the library menu #2c8164e +- added file-based logging #2d4a7e8 +- automatically fetch remote data if not available #2dedfbb +- Added database recovery #32ae7d5 +- ability to add more download dirs #384f7a5 +- re-enable checksums #3ca87fc +- background processes and close/open menu #3d60fd5 +- launch games with log files #3f71149 +- Download cancelling #450bca9 +- refactoring and error message #469a2d6 +- Added UI to change download threads #4e93eb4 +- Made save button include user feedback & only allow numeric characters #53234d2 +- download widget and queue fix #532d13e +- Pausing and resuming game downloads #55b7921 +- Allow settings to update UI using fetch_settings command #5bb04da +- temporary queue ui and flamegraph instructions #5cbeb3b +- Added DownloadThreadControl struct #5e05e68 +- Added max_download_threads setting and separated settings from db #5ea47d7 +- Added generic download manager #6159319 +- Added AgentInterfaceData to get information about all downloads in queue #63c3cc1 +- debug queue interface #671d45f +- reduce scope of download agent #6a38ea3 +- Added multi-argument game launch and setup support #6ad3837 +- shared child with stop command #6b96e40 +- Added function to take and set any game state #6bc6482 +- Added line numbers to file logging and highlighting to console #7c3140e +- Separated chunk updates into individual counters #7d3c601 +- Ensure that any database issues are resolved by standalone functions #7d4651d +- ui to install games #8670bca +- Implemented spawning with umu (using umu-wrapper-lib) #88b2505 +- offer manual signin #949acfc +- better process management, including running state #a135b13 +- Added Download Manager #a1ada07 +- retry connnection on server unavailable #a53d838 +- finish download dir CRUD interface #a580a46 +- better download manager errors + modal #ad92dbe +- syncs state to disk to persist across reboots #b556842 +- prevent default context menu and emit event on elements #c560656 +- initial creation and logo update #d9a51cf +- Added manifest.json utility for persistent download progress #d9d0122 +- game uninstalling & partial compat #dd7f567 +- combined db and download interface improvements #de52dac +- update db state with ui and emit events #e4df4eb +- Generic function to set download state #f10d92d +- Convert DownloadThreadControlFlag to AtomicBool #f25bfed +- add note about more install dirs #f4ac1c8 +- Added rolling progress window #fd30b3e + + +### Other Changes +- quexeky +- Convert DATA_ROOT_DIR to Mutex #00b7179 +- Converting DB access to a trait #01b092c +- Updated changelog #022330b +- Progress on cleanup and exit #0381b8b +- library ui #03fa364 +- Scoping changes and removing qualifications #046ba64 +- Moved all files relevant to game downloads to their own directory #06d1e9e +- SLowly integrating game_download into the FE. Started with using the manifest minimal example in the server (#1) #07379b2 +- Ran cargo clippy & moved DownloadManagerInterface #075d6ec +- Made logging systems match #0a1dddf +- Some easy cleanup of the download manager #0a2ac25 +- client now fetches user information from Drop server #0c0cfeb +- Included in AppStatus (Also trying to link to Issue #1) +- Accidentally serialized AppStatus and broke everything :/ #10791ed +- Removed debugging statements #10c8344 +- Wrappers are the bane of my existence. Also here's the download cancelling logic. #13df631 +- Merge branch 'error-handling' #1520471 +- Updated README.md #165a967 +- Removed unnecessary dependencies #1724449 +- merge(download-manager) -> 'main' #172d6b0 +- More refactoring and renaming camelCase struct definitions to snake_case #1742793 +- General cleanup #182361e +- Delete pages/library.vue #1861659 +- progress on more precise download control #18b9149 +- Allowing some dead code features because they are there for future use (potentially) #191e62c +- Ensure that Downloadable is also send and sync #1a89135 +- I think that download queuing is working #1ab61c8 +- auth initiate, database and more #22b1aee +- Update .gitlab-ci.yml" #2307704 +- More fleshing out on how specifically game downloads will work (#1) #23137dd +- Removed utils.rs #270bc8b +- Fixing some references to "id" vs "game_id" #27e5a8e +- More cleanup after cargo clippy #2822b7a +- Updated contributing link #2aa5b9c +- More fleshing out on how specifically game downloads will work #2b90de9 +- Cleaning up downloads playing and pausing #2c7b5fb +- fixed multi-chunk downloads #2ec351f +- Clippy refactoring #2efe304 +- remove unpacker mod statement #32067c0 +- Progress on adding tools #3299c71 +- Fixed bug with bad initial loading into store instead of auth #3923acf +- add nvm rc #3ccd444 +- partial download manager #3dbf5ab +- Update .gitlab-ci.yml with artifacts #3e10f17 +- Removed tools/ #3eda979 +- Downloads should be fixed now #403ca65 +- transient vs synced state now defined #42c0198 +- added adenmgb's autostart feature #472eb1d +- better download defaults #4779383 +- Progress on downloads. Currently working on parsing functions to be run asynchronously #496c6a5 +- Ran cargo clippy & cargo fmt #4983b25 +- handshakes #4bb33c8 +- Convert DOWNLOAD_MAX_THREADS to const #4fc13a1 +- Merge branch 'downloads' #50ed841 +- Moved generateGameMeta.ts to composables, using PathBuf instead of String for install_dirs #50f37fd +- Added time debugging and fixed logging formatting #5243694 +- Clippy changes #553bc37 +- Queue is running game downloads sequentially now #5564d23 +- migrate to new droplet ca system #556898f +- Add LICENSE #57a5737 +- ran cargo clippy & cargo fmt #5e3d26b +- my own take on some BASED design decisions #5ed0833 +- cleanup and game UI beginnings #5ef6b8e +- Progress on terminator #5f5cbd0 +- Implement better error system and segregate errors and commands (#23) #604d5b5 +- moved to completed index arr to help serialization #64ebc19 +- Ran cargo clippy & cargo fmt #653717e +- Removed all references to anything outside of the DownloadManager #6568faa +- Merge remote-tracking branch 'origin/main' #68ca4a7 +- swap file name and to binary encoding #694f2fd +- chore(polish & cleanup) #6cc0c67 +- Update .gitlab-ci.yml #6d7630e +- Moved some variable declarations outside of the spawned download thread #6ea4cf2 +- Encoding game IDs and versions #6ef444e +- restructing and renaming #7049673 +- Converted to md5 #706f525 +- Merge branch 'main' into downloads #714b968 +- Semantic naming changes #725f16b +- Abstracted queue system #76b0975 +- Moved manifest and stored_manifest to download_manager" #78149bb +- README update #78fc668 +- Ensured everything is serializing/deserializing to camelCase #7a95b7f +- fixed some of quexeky's BASED design decisions #7e3da04 +- Progress checker works #7fec00d +- Progress on refactoring and abiding by cargo clippy #816b427 +- Added GAME_PAUSE_CHECK_INTERVAL value #8204795 +- Ran cargo clippy & fmt #82804eb +- update metadata #85a0899 +- Renamed most instances of "game" outside of actual game downloads #881fcc6 +- Debugging & starting work on parsing manifest #89d2814 +- slight ui/ux fixes and updates to auth protocol #8a2d23d +- Removed Arc requirement for DownloadableMetadata #8be1dd4 +- compliant with new APIs #8f6f184 +- Ran cargo clippy & cargo fmt #9272970 +- Added rolling_progress_updates.rs #9369ff1 +- Add files via upload #93b8b83 +- More refining info!() statements #94cf678 +- fixed windows issues #959dad3 +- Starting p2p progress #97bb1fa +- Game downloads from the client are working (multithreaded) by parsing in gameID, GameVersion, and maxThreads from FE (#1) #984472e +- Version bump & appimage build #9897698 +- Some progress on thread terminations #99beca4 +- rename files to what they contain #99c8b39 +- Created separate function to generate requests #9a184a8 +- cleanup of lib and toml #9b1cfa7 +- refactor for generic way to implement cross platform launchers #9ea2aa4 +- Updated logging format #a213765 +- fix(windows build) #a24cc8a +- Added ToolDownloadAgent #a2e63aa +- copy direct to disk #a628fc1 +- Moved manifest and stored_manifest to download_manager #a846eed +- adds nvm rc! #a881d8e +- Reordered DownloadThreadControlFlag to agree with From #ab606e8 +- ci/cd and patches for windows builds #ac1c3b6 +- patch for not draggable windows during setup #ac66b20 +- another stage of client authentication #ae4c65b +- Renamed GameDonwloadError to ApplicationDownloadError and moved #aed58e4 +- Progress on write speeds & added debug statements #b065e10 +- Updated logging #b3963b6 +- Created file settings.rs #b47b7ea +- Added Downloadable trait and replaced references to GameDownloadAgent #b4d70a3 +- Update .gitlab-ci.yml #b6a54c0 +- Moved download manager to separate directory #b6c64e5 +- Ran cargo fmt #b8cf44c +- Imported appropriate logging macros #b99ff67 +- Merge branch 'main' into download-manager #bb60942 +- Ran cargo clippy & cargo fmt #bd3deac +- beginnings of game state management #bf46dec +- Update Cargo.toml #c1fb39e +- migrated unpacking to rust zstd to conform with droplet #c46c54b +- More progress on checksums #c51e761 +- Delete pages/library.vue #c722a54 +- Merge branch 'downloads' (again) #c748aec +- migrate to nuxt and groundwork #c957744 +- More debugging because apparently checksums are the bane of my existence. But it works and I was just an idiot #c9d9d2e +- Fully separate & generic download manager #cac612b +- Progress on rolling progress window #cf19477 +- Ensured that all logs start with lowercase capital and have no trailing punctuation #cfc9d13 +- Validated that loading data works #d21b1d2 +- Mostly finished with checksums. Just merging main in at the same time #d39e7cb +- Ran cargo clippy #dcb1564 +- Add files via upload #dcb2c0f +- Theoretically adding queue support and optimistic manifest downloading (#1). Needs tests when actual functions are implemented #dcd8fa8 +- Merge remote-tracking branch 'origin/downloads' into downloads #dd23ca8 +- Debugging line #ddc585d +- Re-enabled closing the window and some more renaming #defba51 +- drop no longer freaks out if server is unavailable on startup #df88395 +- Apply stashed changes #e0ea8c9 +- Merge remote-tracking branch 'origin/downloads' into downloads #e4e605b +- convert to more sensible permission schema #e504c00 +- Update on GameDownload #e71e4cf +- reorganisation, cleanup and new nonce protocol #e828bca +- rustix fs feature #e9805a8 +- Added manage_go_signal command #ea70ec9 +- Drop will no longer crash when the server goes down #eb3311a +- Made all errors type-based #ec2f414 +- Added description on how the DownloadManager works #f029cbf +- Using more appropriate logging statements #f183a9d +- remove unnecessary compat code (#20) #f1c8bbf +- Manifests are now being parsed successfully #f28c880 +- Removed tests/ #f29e989 +- I think that downloads are working. Need to test and set decent file locations now #f388237 +- Just debugging tauri's damn Sync command features #f60ca2b +- fixes and patches for merged changes #f6476bc +- Added manage_queue_signal #f64782e +- initial commit #f6cd7c3 +- Update .gitlab-ci.yml #fc6bab9 + + +_changelog generated by_ [go-conventional-commits](https://github.com/joselitofilho/go-conventional-commits) + +## Release 0.1.0-beta + +### Fixes +- fixed queue manipulation and waiting for downloads #01260f0 +- fix logic error in detecting dir #04368ff +- absolute executable invoke #17759c4 +- Chunk counting logic error #5ba151f +- use of completed signal, and pause/resuming #64d7f64 +- initialise doesn't recreate default install dir #7a3841b +- use vendored flag #7c8089e +- windows build #8d9234f +- windows shadow #9a8cc59 +- types #af056c0 +- added console as an appender #d12bf15 +- remove unnecessary unstable feature #d5ac1b0 +- use unix timestamp to avoid invalid characters in filename #dafce24 +- use chrono library to generate timestamps #e22e6d8 +- fix scrollbars on edge webview #f09605a +- update readme instructions #f0c47d8 + + +### Features +- queue and library UIs #0a20139 +- add pre-launch log to file #17f8d76 +- Added option to change root directory #1aa52c0 +- quit button #239b8d5 +- only allow downloads for supported platforms #269dcbb +- added file-based logging #2d4a7e8 +- automatically fetch remote data if not available #2dedfbb +- ability to add more download dirs #384f7a5 +- background processes and close/open menu #3d60fd5 +- launch games with log files #3f71149 +- Download cancelling #450bca9 +- refactoring and error message #469a2d6 +- download widget and queue fix #532d13e +- Pausing and resuming game downloads #55b7921 +- temporary queue ui and flamegraph instructions #5cbeb3b +- Added DownloadThreadControl struct #5e05e68 +- Added AgentInterfaceData to get information about all downloads in queue #63c3cc1 +- debug queue interface #671d45f +- reduce scope of download agent #6a38ea3 +- Added function to take and set any game state #6bc6482 +- Separated chunk updates into individual counters #7d3c601 +- ui to install games #8670bca +- Added Download Manager #a1ada07 +- retry connnection on server unavailable #a53d838 +- finish download dir CRUD interface #a580a46 +- syncs state to disk to persist across reboots #b556842 +- prevent default context menu and emit event on elements #c560656 +- initial creation and logo update #d9a51cf +- Added manifest.json utility for persistent download progress #d9d0122 +- combined db and download interface improvements #de52dac +- update db state with ui and emit events #e4df4eb +- Generic function to set download state #f10d92d +- Convert DownloadThreadControlFlag to AtomicBool #f25bfed + + +### Other Changes +- quexeky +- Convert DATA_ROOT_DIR to Mutex #00b7179 +- Converting DB access to a trait #01b092c +- Scoping changes and removing qualifications #046ba64 +- SLowly integrating game_download into the FE. Started with using the manifest minimal example in the server (#1) #07379b2 +- Ran cargo clippy & moved DownloadManagerInterface #075d6ec +- Made logging systems match #0a1dddf +- client now fetches user information from Drop server #0c0cfeb +- Included in AppStatus (Also trying to link to Issue #1) +- Accidentally serialized AppStatus and broke everything :/ #10791ed +- Removed debugging statements #10c8344 +- Wrappers are the bane of my existence. Also here's the download cancelling logic. #13df631 +- Merge branch 'error-handling' #1520471 +- Removed unnecessary dependencies #1724449 +- merge(download-manager) -> 'main' #172d6b0 +- More refactoring and renaming camelCase struct definitions to snake_case #1742793 +- progress on more precise download control #18b9149 +- Allowing some dead code features because they are there for future use (potentially) #191e62c +- I think that download queuing is working #1ab61c8 +- auth initiate, database and more #22b1aee +- More fleshing out on how specifically game downloads will work (#1) #23137dd +- Removed utils.rs #270bc8b +- Fixing some references to "id" vs "game_id" #27e5a8e +- Updated contributing link #2aa5b9c +- More fleshing out on how specifically game downloads will work #2b90de9 +- Cleaning up downloads playing and pausing #2c7b5fb +- fixed multi-chunk downloads #2ec351f +- Clippy refactoring #2efe304 +- remove unpacker mod statement #32067c0 +- Fixed bug with bad initial loading into store instead of auth #3923acf +- partial download manager #3dbf5ab +- Downloads should be fixed now #403ca65 +- transient vs synced state now defined #42c0198 +- better download defaults #4779383 +- Progress on downloads. Currently working on parsing functions to be run asynchronously #496c6a5 +- Ran cargo clippy & cargo fmt #4983b25 +- handshakes #4bb33c8 +- Convert DOWNLOAD_MAX_THREADS to const #4fc13a1 +- Merge branch 'downloads' #50ed841 +- Added time debugging and fixed logging formatting #5243694 +- Clippy changes #553bc37 +- Queue is running game downloads sequentially now #5564d23 +- migrate to new droplet ca system #556898f +- Add LICENSE #57a5737 +- ran cargo clippy & cargo fmt #5e3d26b +- my own take on some BASED design decisions #5ed0833 +- cleanup and game UI beginnings #5ef6b8e +- moved to completed index arr to help serialization #64ebc19 +- Ran cargo clippy & cargo fmt #653717e +- Merge remote-tracking branch 'origin/main' #68ca4a7 +- swap file name and to binary encoding #694f2fd +- chore(polish & cleanup) #6cc0c67 +- Encoding game IDs and versions #6ef444e +- restructing and renaming #7049673 +- Converted to md5 #706f525 +- Merge branch 'main' into downloads #714b968 +- Semantic naming changes #725f16b +- Abstracted queue system #76b0975 +- README update #78fc668 +- Ensured everything is serializing/deserializing to camelCase #7a95b7f +- fixed some of quexeky's BASED design decisions #7e3da04 +- Progress checker works #7fec00d +- Progress on refactoring and abiding by cargo clippy #816b427 +- Added GAME_PAUSE_CHECK_INTERVAL value #8204795 +- Debugging & starting work on parsing manifest #89d2814 +- slight ui/ux fixes and updates to auth protocol #8a2d23d +- compliant with new APIs #8f6f184 +- fixed windows issues #959dad3 +- Starting p2p progress #97bb1fa +- Game downloads from the client are working (multithreaded) by parsing in gameID, GameVersion, and maxThreads from FE (#1) #984472e +- Some progress on thread terminations #99beca4 +- rename files to what they contain #99c8b39 +- cleanup of lib and toml #9b1cfa7 +- Updated logging format #a213765 +- fix(windows build) #a24cc8a +- copy direct to disk #a628fc1 +- Reordered DownloadThreadControlFlag to agree with From #ab606e8 +- ci/cd and patches for windows builds #ac1c3b6 +- patch for not draggable windows during setup #ac66b20 +- another stage of client authentication #ae4c65b +- Progress on write speeds & added debug statements #b065e10 +- Updated logging #b3963b6 +- Created file settings.rs #b47b7ea +- Ran cargo fmt #b8cf44c +- Merge branch 'main' into download-manager #bb60942 +- Ran cargo clippy & cargo fmt #bd3deac +- beginnings of game state management #bf46dec +- Update Cargo.toml #c1fb39e +- migrated unpacking to rust zstd to conform with droplet #c46c54b +- More progress on checksums #c51e761 +- Merge branch 'downloads' (again) #c748aec +- migrate to nuxt and groundwork #c957744 +- More debugging because apparently checksums are the bane of my existence. But it works and I was just an idiot #c9d9d2e +- Validated that loading data works #d21b1d2 +- Mostly finished with checksums. Just merging main in at the same time #d39e7cb +- Theoretically adding queue support and optimistic manifest downloading (#1). Needs tests when actual functions are implemented #dcd8fa8 +- Merge remote-tracking branch 'origin/downloads' into downloads #dd23ca8 +- Debugging line #ddc585d +- Re-enabled closing the window and some more renaming #defba51 +- drop no longer freaks out if server is unavailable on startup #df88395 +- Merge remote-tracking branch 'origin/downloads' into downloads #e4e605b +- convert to more sensible permission schema #e504c00 +- Update on GameDownload #e71e4cf +- reorganisation, cleanup and new nonce protocol #e828bca +- rustix fs feature #e9805a8 +- Drop will no longer crash when the server goes down #eb3311a +- Made all errors type-based #ec2f414 +- Added description on how the DownloadManager works #f029cbf +- Manifests are now being parsed successfully #f28c880 +- I think that downloads are working. Need to test and set decent file locations now #f388237 +- Just debugging tauri's damn Sync command features #f60ca2b +- fixes and patches for merged changes #f6476bc +- initial commit #f6cd7c3 + + +_changelog generated by_ [go-conventional-commits](https://github.com/joselitofilho/go-conventional-commits) diff --git a/desktop/drop.svg b/desktop/drop.svg new file mode 100644 index 00000000..34db2923 --- /dev/null +++ b/desktop/drop.svg @@ -0,0 +1,5 @@ + + + diff --git a/desktop/libs/appletrust/add-certificate.swift b/desktop/libs/appletrust/add-certificate.swift new file mode 100644 index 00000000..8ed6601a --- /dev/null +++ b/desktop/libs/appletrust/add-certificate.swift @@ -0,0 +1,72 @@ +import Foundation +import Security + +enum SecurityError: Error { + case generalError +} + +func deleteCertificateFromKeyChain(_ certificateLabel: String) -> Bool { + let delQuery: [NSString: Any] = [ + kSecClass: kSecClassCertificate, + kSecAttrLabel: certificateLabel, + ] + let delStatus: OSStatus = SecItemDelete(delQuery as CFDictionary) + + return delStatus == errSecSuccess +} + +func saveCertificateToKeyChain(_ certificate: SecCertificate, certificateLabel: String) throws { + SecKeychainSetPreferenceDomain(SecPreferencesDomain.system) + deleteCertificateFromKeyChain(certificateLabel) + + let setQuery: [NSString: AnyObject] = [ + kSecClass: kSecClassCertificate, + kSecValueRef: certificate, + kSecAttrLabel: certificateLabel as AnyObject, + kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked, + kSecAttrCanSign: true as AnyObject, + ] + let addStatus: OSStatus = SecItemAdd(setQuery as CFDictionary, nil) + + guard addStatus == errSecSuccess else { + throw SecurityError.generalError + } + + var status = SecTrustSettingsSetTrustSettings(certificate, SecTrustSettingsDomain.admin, nil) +} + +func getCertificateFromString(stringData: String) throws -> SecCertificate { + if let data = NSData(base64Encoded: stringData, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) { + if let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, data) { + return certificate + } + } + throw SecurityError.generalError +} + +if CommandLine.arguments.count != 2 { + print("Usage: \(CommandLine.arguments[0]) [cert.file]") + print("Usage: \(CommandLine.arguments[0]) --version") + exit(1) +} + +if (CommandLine.arguments[1] == "--version") { + let version = "dev" + print(version) + exit(0) +} else { + let fileURL = URL(fileURLWithPath: CommandLine.arguments[1]) + do { + let certData = try Data(contentsOf: fileURL) + let certificate = SecCertificateCreateWithData(nil, certData as CFData) + if certificate != nil { + try? saveCertificateToKeyChain(certificate!, certificateLabel: "DropOSS") + exit(0) + } else { + print("ERROR: Unknown error while reading the \(CommandLine.arguments[1]) file.") + } + } catch { + print("ERROR: Unexpected error while reading the \(CommandLine.arguments[1]) file. \(error)") + } +} +exit(1) \ No newline at end of file diff --git a/desktop/main/app.vue b/desktop/main/app.vue new file mode 100644 index 00000000..5ae90e17 --- /dev/null +++ b/desktop/main/app.vue @@ -0,0 +1,51 @@ + + + diff --git a/desktop/main/assets/main.scss b/desktop/main/assets/main.scss new file mode 100644 index 00000000..4939d2d5 --- /dev/null +++ b/desktop/main/assets/main.scss @@ -0,0 +1,85 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, +body { + -ms-overflow-style: none; /* IE and Edge / +scrollbar-width: none; / Firefox */ + overscroll-behavior: none; +} + +/* Hide scrollbar for Chrome, Safari and Opera */ +html::-webkit-scrollbar { + display: none; +} + +$motiva: ( + ("MotivaSansThin.ttf", "ttf", 100, normal), + ("MotivaSansLight.woff.ttf", "woff", 300, normal), + ("MotivaSansRegular.woff.ttf", "woff", 400, normal), + ("MotivaSansMedium.woff.ttf", "woff", 500, normal), + ("MotivaSansBold.woff.ttf", "woff", 600, normal), + ("MotivaSansExtraBold.ttf", "woff", 700, normal), + ("MotivaSansBlack.woff.ttf", "woff", 900, normal) +); + +$helvetica: ( + ("Helvetica.woff", "woff", 400, normal), + ("Helvetica-Oblique.woff", "woff", 400, italic), + ("Helvetica-Bold.woff", "woff", 600, normal), + ("Helvetica-BoldOblique.woff", "woff", 600, italic), + ("helvetica-light-587ebe5a59211.woff2", "woff2", 300, normal) +); + +@each $file, $format, $weight, $style in $motiva { + @font-face { + font-family: "Motiva Sans"; + src: url("/fonts/motiva/#{$file}") format($format); + font-weight: $weight; + font-style: $style; + } +} + +@each $file, $format, $weight, $style in $helvetica { + @font-face { + font-family: "Helvetica"; + src: url("/fonts/helvetica/#{$file}") format($format); + font-weight: $weight; + font-style: $style; + } +} + +@font-face { + font-family: "Inter"; + src: url("/fonts/inter/InterVariable.ttf"); + font-style: normal; +} + +@font-face { + font-family: "Inter"; + src: url("/fonts/inter/InterVariable-Italic.ttf"); + font-style: italic; +} + +/* ===== Scrollbar CSS ===== */ +/* Firefox */ +* { + scrollbar-width: 4px; + scrollbar-color: #52525b #00000000; +} + +/* Chrome, Edge, and Safari */ +*::-webkit-scrollbar { + width: 4px; +} + +*::-webkit-scrollbar-track { + background: transparent; +} + +*::-webkit-scrollbar-thumb { + background-color: #52525b; + border-radius: 10px; + border: 3px solid #52525b; +} diff --git a/desktop/main/assets/wallpaper.jpg b/desktop/main/assets/wallpaper.jpg new file mode 100644 index 00000000..8412d8be Binary files /dev/null and b/desktop/main/assets/wallpaper.jpg differ diff --git a/desktop/main/components/ChatRoomGroup.vue b/desktop/main/components/ChatRoomGroup.vue new file mode 100644 index 00000000..b142cd5f --- /dev/null +++ b/desktop/main/components/ChatRoomGroup.vue @@ -0,0 +1,41 @@ + + + diff --git a/desktop/main/components/CommunityAvatar.vue b/desktop/main/components/CommunityAvatar.vue new file mode 100644 index 00000000..c4338ee9 --- /dev/null +++ b/desktop/main/components/CommunityAvatar.vue @@ -0,0 +1,26 @@ + + + diff --git a/desktop/main/components/DefaultProtonButton.vue b/desktop/main/components/DefaultProtonButton.vue new file mode 100644 index 00000000..dda1f0f0 --- /dev/null +++ b/desktop/main/components/DefaultProtonButton.vue @@ -0,0 +1,30 @@ + + + diff --git a/desktop/main/components/DependencyRequiredModal.vue b/desktop/main/components/DependencyRequiredModal.vue new file mode 100644 index 00000000..ab805b65 --- /dev/null +++ b/desktop/main/components/DependencyRequiredModal.vue @@ -0,0 +1,106 @@ + + + diff --git a/desktop/main/components/GameOptions/HandlerSelector.vue b/desktop/main/components/GameOptions/HandlerSelector.vue new file mode 100644 index 00000000..3a402fa0 --- /dev/null +++ b/desktop/main/components/GameOptions/HandlerSelector.vue @@ -0,0 +1,141 @@ + + + diff --git a/desktop/main/components/GameOptions/Launch.vue b/desktop/main/components/GameOptions/Launch.vue new file mode 100644 index 00000000..1deb4d35 --- /dev/null +++ b/desktop/main/components/GameOptions/Launch.vue @@ -0,0 +1,41 @@ + + + diff --git a/desktop/main/components/GameOptions/ProtonSelector.vue b/desktop/main/components/GameOptions/ProtonSelector.vue new file mode 100644 index 00000000..39806d3a --- /dev/null +++ b/desktop/main/components/GameOptions/ProtonSelector.vue @@ -0,0 +1,189 @@ + + + diff --git a/desktop/main/components/GameOptions/Saves.vue b/desktop/main/components/GameOptions/Saves.vue new file mode 100644 index 00000000..ccae09a4 --- /dev/null +++ b/desktop/main/components/GameOptions/Saves.vue @@ -0,0 +1,195 @@ + + + diff --git a/desktop/main/components/GameOptions/Updates.vue b/desktop/main/components/GameOptions/Updates.vue new file mode 100644 index 00000000..b12ae1fc --- /dev/null +++ b/desktop/main/components/GameOptions/Updates.vue @@ -0,0 +1,36 @@ + + + diff --git a/desktop/main/components/GameOptionsModal.vue b/desktop/main/components/GameOptionsModal.vue new file mode 100644 index 00000000..7078f5ce --- /dev/null +++ b/desktop/main/components/GameOptionsModal.vue @@ -0,0 +1,149 @@ + + + diff --git a/desktop/main/components/GameStatusButton.vue b/desktop/main/components/GameStatusButton.vue new file mode 100644 index 00000000..f7ac7650 --- /dev/null +++ b/desktop/main/components/GameStatusButton.vue @@ -0,0 +1,232 @@ + + + diff --git a/desktop/main/components/Header.vue b/desktop/main/components/Header.vue new file mode 100644 index 00000000..dc8399bb --- /dev/null +++ b/desktop/main/components/Header.vue @@ -0,0 +1,85 @@ + + + diff --git a/desktop/main/components/HeaderButton.vue b/desktop/main/components/HeaderButton.vue new file mode 100644 index 00000000..0811c132 --- /dev/null +++ b/desktop/main/components/HeaderButton.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/desktop/main/components/HeaderFriendsWidget.vue b/desktop/main/components/HeaderFriendsWidget.vue new file mode 100644 index 00000000..e93e1c0b --- /dev/null +++ b/desktop/main/components/HeaderFriendsWidget.vue @@ -0,0 +1,144 @@ + + + diff --git a/desktop/main/components/HeaderNotificationsWidget.vue b/desktop/main/components/HeaderNotificationsWidget.vue new file mode 100644 index 00000000..71cb7be6 --- /dev/null +++ b/desktop/main/components/HeaderNotificationsWidget.vue @@ -0,0 +1,95 @@ + + + diff --git a/desktop/main/components/HeaderProtonSupportWidget.vue b/desktop/main/components/HeaderProtonSupportWidget.vue new file mode 100644 index 00000000..59ddec44 --- /dev/null +++ b/desktop/main/components/HeaderProtonSupportWidget.vue @@ -0,0 +1,24 @@ + + + diff --git a/desktop/main/components/HeaderQueueWidget.vue b/desktop/main/components/HeaderQueueWidget.vue new file mode 100644 index 00000000..cdcbd35d --- /dev/null +++ b/desktop/main/components/HeaderQueueWidget.vue @@ -0,0 +1,26 @@ + + + diff --git a/desktop/main/components/HeaderUserWidget.vue b/desktop/main/components/HeaderUserWidget.vue new file mode 100644 index 00000000..62821d0f --- /dev/null +++ b/desktop/main/components/HeaderUserWidget.vue @@ -0,0 +1,113 @@ + + + diff --git a/desktop/main/components/HeaderWidget.vue b/desktop/main/components/HeaderWidget.vue new file mode 100644 index 00000000..1e25b326 --- /dev/null +++ b/desktop/main/components/HeaderWidget.vue @@ -0,0 +1,34 @@ + + + diff --git a/desktop/main/components/InitiateAuthModule.vue b/desktop/main/components/InitiateAuthModule.vue new file mode 100644 index 00000000..4a1e290c --- /dev/null +++ b/desktop/main/components/InitiateAuthModule.vue @@ -0,0 +1,171 @@ + + + diff --git a/desktop/main/components/InstallDirectorySelector.vue b/desktop/main/components/InstallDirectorySelector.vue new file mode 100644 index 00000000..9fd8f0e7 --- /dev/null +++ b/desktop/main/components/InstallDirectorySelector.vue @@ -0,0 +1,87 @@ + + + diff --git a/desktop/main/components/LibrarySearch.vue b/desktop/main/components/LibrarySearch.vue new file mode 100644 index 00000000..38f927aa --- /dev/null +++ b/desktop/main/components/LibrarySearch.vue @@ -0,0 +1,367 @@ + + + + + diff --git a/desktop/main/components/Logo.vue b/desktop/main/components/Logo.vue new file mode 100644 index 00000000..6105fd17 --- /dev/null +++ b/desktop/main/components/Logo.vue @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/desktop/main/components/MiniHeader.vue b/desktop/main/components/MiniHeader.vue new file mode 100644 index 00000000..3a129c88 --- /dev/null +++ b/desktop/main/components/MiniHeader.vue @@ -0,0 +1,16 @@ + + + diff --git a/desktop/main/components/OfflineHeaderWidget.vue b/desktop/main/components/OfflineHeaderWidget.vue new file mode 100644 index 00000000..a22320ed --- /dev/null +++ b/desktop/main/components/OfflineHeaderWidget.vue @@ -0,0 +1,23 @@ + + + diff --git a/desktop/main/components/PageWidget.vue b/desktop/main/components/PageWidget.vue new file mode 100644 index 00000000..ad9428b5 --- /dev/null +++ b/desktop/main/components/PageWidget.vue @@ -0,0 +1,7 @@ + diff --git a/desktop/main/components/WindowControl.vue b/desktop/main/components/WindowControl.vue new file mode 100644 index 00000000..548ef0ea --- /dev/null +++ b/desktop/main/components/WindowControl.vue @@ -0,0 +1,24 @@ + + + diff --git a/desktop/main/components/Wordmark.vue b/desktop/main/components/Wordmark.vue new file mode 100644 index 00000000..4ab34fc6 --- /dev/null +++ b/desktop/main/components/Wordmark.vue @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/desktop/main/composables/api.ts b/desktop/main/composables/api.ts new file mode 100644 index 00000000..352cf02b --- /dev/null +++ b/desktop/main/composables/api.ts @@ -0,0 +1,51 @@ +import { invoke } from "@tauri-apps/api/core"; + +// Thin wrappers over the generic Rust bridge (community_api.rs): the client +// passes the server's own `/api/v1/...` path, Rust resolves the base URL + +// signs the request with the same auth every other client call uses. See +// desktop/src-tauri/src/community_api.rs for why this is generic rather than +// one typed Tauri command per endpoint. +// +// On a non-2xx response the Rust side rejects with a stringified +// RemoteAccessError::InvalidResponse whose message is the server's own +// `statusMessage`/`message` -- callers can show `(e as string)` directly. + +export function apiGet( + path: string, + query?: Record, +): Promise { + const q = query + ? Object.entries(query) + .filter(([, v]) => v !== undefined) + .map(([k, v]) => [k, String(v)] as [string, string]) + : undefined; + return invoke("api_get", { path, query: q }); +} + +// Same as apiGet, but for `/api/v1/client/*` routes built on +// `defineClientEventHandler` (news, library, game manifests, ...). Those +// routes authenticate with the desktop client's own short-lived JWT +// (Rust's generate_authorization_header), NOT the aclManager webtoken +// apiGet's api_get command mints -- sending them a webtoken 403s no matter +// what ACLs it carries, since defineClientEventHandler never looks at +// ACLs at all. See desktop/src-tauri/src/community_api.rs's module header +// for the full explanation of why there are two auth paths here. +export function apiGetClient( + path: string, + query?: Record, +): Promise { + const q = query + ? Object.entries(query) + .filter(([, v]) => v !== undefined) + .map(([k, v]) => [k, String(v)] as [string, string]) + : undefined; + return invoke("api_get_jwt", { path, query: q }); +} + +export function apiPost(path: string, body?: unknown): Promise { + return invoke("api_post", { path, body: body ?? {} }); +} + +export function apiDelete(path: string): Promise { + return invoke("api_delete", { path }); +} diff --git a/desktop/main/composables/app-state.ts b/desktop/main/composables/app-state.ts new file mode 100644 index 00000000..7ad06aba --- /dev/null +++ b/desktop/main/composables/app-state.ts @@ -0,0 +1,3 @@ +import type { AppState } from "~/types"; + +export const useAppState = () => useState("state"); \ No newline at end of file diff --git a/desktop/main/composables/community-ws.ts b/desktop/main/composables/community-ws.ts new file mode 100644 index 00000000..41ce8307 --- /dev/null +++ b/desktop/main/composables/community-ws.ts @@ -0,0 +1,169 @@ +import { invoke } from "@tauri-apps/api/core"; +import { listen } from "@tauri-apps/api/event"; + +// Frontend half of the community websocket bridge +// (desktop/src-tauri/src/community_ws.rs). One shared connection for the +// whole app (chat rooms, live chat.message frames, presence updates, +// friend-request pushes), matching chatService's `{t, d}` envelope exactly +// (server/server/api/v1/community/ws.get.ts). +// +// `useState` makes this a Nuxt-style singleton: every component that calls +// useCommunityWs() shares the same reactive state and the same single +// underlying socket, rather than each page opening its own connection. + +export interface ChatMessageDTO { + id: string; + roomId: string; + kind: "text" | "system"; + body: string; + deleted: boolean; + clientNonce: string | null; + replyToId: string | null; + sender: { + id: string; + username: string; + displayName: string; + avatarUrl: string | null; + } | null; + createdAt: string; + editedAt: string | null; +} + +interface PresenceUpdate { + userId: string; + state: "online" | "offline"; +} + +interface FriendRequestPush { + status: "pending" | "accepted" | "declined"; + userId: string; +} + +type WsListener = (envelope: { t: string; d: any }) => void; + +interface CommunityWsInternal { + connected: boolean; + connecting: boolean; + subscribedTopics: Set; + listeners: Set; + initialized: boolean; +} + +// Not part of the reactive useState -- a Set doesn't play well with Vue's +// deep reactivity/serialization and none of this needs to trigger renders +// directly (callers derive their own reactive state from message events). +const internal: CommunityWsInternal = { + connected: false, + connecting: false, + subscribedTopics: new Set(), + listeners: new Set(), + initialized: false, +}; + +const wsConnected = () => useState("community-ws-connected", () => false); + +async function ensureConnected() { + if (internal.connected || internal.connecting) return; + internal.connecting = true; + try { + await invoke("community_ws_connect"); + } catch (e) { + console.error("community ws: connect failed", e); + internal.connecting = false; + return; + } + internal.connecting = false; +} + +function initOnce() { + if (internal.initialized) return; + internal.initialized = true; + + listen("community/ws-message", (event) => { + let envelope: { t: string; d: any }; + try { + envelope = JSON.parse(event.payload); + } catch { + return; + } + if (envelope.t === "pong" || envelope.t === "notice") { + if (envelope.t === "notice") console.warn("community ws notice:", envelope.d); + return; + } + for (const listener of internal.listeners) listener(envelope); + }); + + listen("community/ws-closed", () => { + internal.connected = false; + wsConnected().value = false; + internal.subscribedTopics.clear(); + // Reconnect with a short, fixed backoff -- chat is supposed to feel + // live, so retry promptly rather than leaving the user silently + // disconnected until they navigate away and back. + setTimeout(() => { + ensureConnected(); + }, 3000); + }); + + // A connect can race a page mount; mark connected optimistically once the + // invoke resolves (community_ws_connect only returns Ok after the upgrade + // handshake succeeds). +} + +export function useCommunityWs() { + initOnce(); + + async function connect() { + await ensureConnected(); + internal.connected = true; + wsConnected().value = true; + } + + function send(t: string, d: unknown = {}) { + invoke("community_ws_send", { payload: JSON.stringify({ t, d }) }).catch((e) => { + console.error("community ws: send failed", e); + }); + } + + function subscribe(topics: string[]) { + const fresh = topics.filter((t) => !internal.subscribedTopics.has(t)); + if (fresh.length === 0) return; + for (const t of fresh) internal.subscribedTopics.add(t); + send("sub", { topics: fresh }); + } + + function unsubscribe(topics: string[]) { + for (const t of topics) internal.subscribedTopics.delete(t); + send("unsub", { topics }); + } + + function onMessage(listener: WsListener) { + internal.listeners.add(listener); + return () => internal.listeners.delete(listener); + } + + function sendChatMessage(roomId: string, body: string, clientNonce?: string) { + send("chat.send", { roomId, body, clientNonce }); + } + + function sendTyping(roomId: string) { + send("chat.typing", { roomId }); + } + + function sendRead(roomId: string, lastReadMessageId: string) { + send("chat.read", { roomId, lastReadMessageId }); + } + + return { + connected: wsConnected(), + connect, + subscribe, + unsubscribe, + onMessage, + sendChatMessage, + sendTyping, + sendRead, + }; +} + +export type { PresenceUpdate, FriendRequestPush }; diff --git a/desktop/main/composables/current-page-engine.ts b/desktop/main/composables/current-page-engine.ts new file mode 100644 index 00000000..0990195c --- /dev/null +++ b/desktop/main/composables/current-page-engine.ts @@ -0,0 +1,32 @@ +import type { RouteLocationNormalized } from "vue-router"; +import type { NavigationItem } from "~/types"; + +export const useCurrentNavigationIndex = ( + navigation: Array +) => { + const router = useRouter(); + const route = useRoute(); + + const currentNavigation = ref(-1); + + function calculateCurrentNavIndex(to: RouteLocationNormalized) { + const validOptions = navigation + .map((e, i) => ({ ...e, index: i })) + .filter((e) => to.fullPath.startsWith(e.prefix)); + const bestOption = validOptions + .sort((a, b) => b.route.length - a.route.length) + .at(0); + + return bestOption?.index ?? -1; + } + + currentNavigation.value = calculateCurrentNavIndex(route); + + router.afterEach((to) => { + currentNavigation.value = calculateCurrentNavIndex(to); + }); + + return {currentNavigation, recalculateNavigation: () => { + currentNavigation.value = calculateCurrentNavIndex(route); + }}; +}; diff --git a/desktop/main/composables/downloads.ts b/desktop/main/composables/downloads.ts new file mode 100644 index 00000000..a09e4235 --- /dev/null +++ b/desktop/main/composables/downloads.ts @@ -0,0 +1,54 @@ +import { listen } from "@tauri-apps/api/event"; +import type { DownloadableMetadata } from "~/types"; + +export type QueueState = { + queue: Array<{ + meta: DownloadableMetadata; + status: string; + dl_progress: number | null; + dl_current: number; + dl_max: number; + disk_progress: number | null; + disk_current: number; + disk_max: number; + }>; + status: string; +}; + +export type StatsState = { + speed: number; // Bytes per second + time: number; // Seconds, +}; + +export const useQueueState = () => + useState("queue", () => ({ queue: [], status: "Unknown" })); + +export const useStatsState = () => + useState("stats", () => ({ speed: 0, time: 0 })); + +listen("update_queue", (event) => { + const queue = useQueueState(); + queue.value = event.payload as QueueState; +}); + +listen("update_stats", (event) => { + const stats = useStatsState(); + stats.value = event.payload as StatsState; +}); + +export const useDownloadHistory = () => + useState>("history", () => []); + +export function formatKilobytes(bytes: number): string { + const units = ["K", "M", "G", "T", "P"]; + let value = bytes; + let unitIndex = 0; + const scalar = 1000; + + while (value >= scalar && unitIndex < units.length - 1) { + value /= scalar; + unitIndex++; + } + + return `${value.toFixed(1)} ${units[unitIndex]}`; +} diff --git a/desktop/main/composables/friends.ts b/desktop/main/composables/friends.ts new file mode 100644 index 00000000..94346acc --- /dev/null +++ b/desktop/main/composables/friends.ts @@ -0,0 +1,123 @@ +import { apiDelete, apiGet, apiPost } from "./api"; + +export interface CommunityUserSummary { + id: string; + username: string; + displayName: string; + avatarUrl: string | null; +} + +export interface FriendshipRequestSummary { + id: string; + createdAt: string; + user: CommunityUserSummary; +} + +export interface FriendsListResult { + friends: CommunityUserSummary[]; + incomingRequests: FriendshipRequestSummary[]; + outgoingRequests: FriendshipRequestSummary[]; +} + +export interface FriendPlaying { + platform: "drop" | "romm"; + id: string; + name: string; +} + +export interface FriendActivityEntry extends CommunityUserSummary { + online: boolean; + playing: FriendPlaying | null; +} + +function friendsState() { + return useState("friends-list", () => ({ + friends: [], + incomingRequests: [], + outgoingRequests: [], + })); +} +function activityState() { + return useState>("friends-activity", () => ({})); +} +function loadedState() { + return useState("friends-loaded", () => false); +} +function errorState() { + return useState("friends-error", () => undefined); +} + +export function useFriends() { + const friends = friendsState(); + const activity = activityState(); + const loaded = loadedState(); + const error = errorState(); + + const onlineCount = computed( + () => Object.values(activity.value).filter((f) => f.online).length, + ); + const incomingCount = computed(() => friends.value.incomingRequests.length); + + async function refresh() { + try { + const [list, act] = await Promise.all([ + apiGet("api/v1/community/friends"), + apiGet<{ friends: FriendActivityEntry[]; presenceAvailable: boolean }>( + "api/v1/community/friends/activity", + ), + ]); + friends.value = list; + const map: Record = {}; + for (const f of act.friends) map[f.id] = f; + activity.value = map; + error.value = undefined; + } catch (e) { + error.value = String(e); + } finally { + loaded.value = true; + } + } + + async function sendRequest(username: string) { + const result = await apiPost<{ status: string }>("api/v1/community/friends/requests", { + username, + }); + await refresh(); + return result; + } + + async function acceptRequest(requestId: string) { + await apiPost(`api/v1/community/friends/requests/${requestId}/accept`); + await refresh(); + } + + async function declineRequest(requestId: string) { + await apiPost(`api/v1/community/friends/requests/${requestId}/decline`); + await refresh(); + } + + async function removeFriend(userId: string) { + await apiDelete(`api/v1/community/friends/${userId}`); + await refresh(); + } + + async function searchUsers(query: string): Promise { + if (!query.trim()) return []; + return apiGet("api/v1/community/users/search", { q: query }); + } + + return { + friends, + activity, + loaded, + error, + onlineCount, + incomingCount, + refresh, + sendRequest, + acceptRequest, + declineRequest, + removeFriend, + searchUsers, + }; +} diff --git a/desktop/main/composables/game.ts b/desktop/main/composables/game.ts new file mode 100644 index 00000000..93e97af2 --- /dev/null +++ b/desktop/main/composables/game.ts @@ -0,0 +1,95 @@ +import { invoke } from "@tauri-apps/api/core"; +import { listen } from "@tauri-apps/api/event"; +import type { + Game, + GameStatus, + GameStatusEnum, + GameVersion, + RawGameStatus, +} from "~/types"; + +const gameRegistry: { [key: string]: { game: Game; version: Ref } } = + {}; + +const gameStatusRegistry: { [key: string]: Ref } = {}; + +export const parseStatus = (status: RawGameStatus): GameStatus => { + console.log(status[0]); + if (status[0]) { + return status[0]; + } + if (status[1]) { + return status[1]; + } + throw new Error("No game status: " + JSON.stringify(status)); +}; + +export const useGame = async (gameId: string) => { + if (!gameRegistry[gameId]) { + const data: { + game: Game; + status: RawGameStatus; + version?: GameVersion; + } = await invoke("fetch_game", { + gameId, + }); + gameRegistry[gameId] = { game: data.game, version: ref(data.version) }; + if (!gameStatusRegistry[gameId]) { + gameStatusRegistry[gameId] = ref(parseStatus(data.status)); + + listen(`update_game/${gameId}`, (event) => { + const payload: { + status: RawGameStatus; + version?: GameVersion; + } = event.payload as any; + gameStatusRegistry[gameId].value = parseStatus(payload.status); + + /** + * I am not super happy about this. + * + * This will mean that we will still have a version assigned if we have a game installed then uninstall it. + * It is necessary because a flag to check if we should overwrite seems excessive, and this function gets called + * on transient state updates. + */ + if (payload.version) { + gameRegistry[gameId].version.value = payload.version; + } + }); + } + } + + const game = gameRegistry[gameId]; + const status = gameStatusRegistry[gameId]; + return { ...game, status }; +}; + +export type LaunchResult = + | { result: "Success" } + | { result: "InstallRequired"; data: [string, string] }; + +export type VersionOption = { + versionId: string; + displayName?: string; + versionPath: string; + platform: string; + size: { + installSize: number; + downloadSize: number; + }; + requiredContent: Array<{ + gameId: string; + versionId: string; + name: string; + iconObjectId: string; + shortDescription: string; + size: { + installSize: number; + downloadSize: number; + }; + }>; +}; + +export type ProtonPath = { + path: string; + name: string; +}; diff --git a/desktop/main/composables/generateGameMeta.ts b/desktop/main/composables/generateGameMeta.ts new file mode 100644 index 00000000..fdfc4b7a --- /dev/null +++ b/desktop/main/composables/generateGameMeta.ts @@ -0,0 +1,9 @@ +import { type DownloadableMetadata, DownloadableType } from '~/types' + +export default function generateGameMeta(gameId: string, version: string): DownloadableMetadata { + return { + id: gameId, + version, + downloadType: DownloadableType.Game + } +} \ No newline at end of file diff --git a/desktop/main/composables/notifications.ts b/desktop/main/composables/notifications.ts new file mode 100644 index 00000000..df5552ea --- /dev/null +++ b/desktop/main/composables/notifications.ts @@ -0,0 +1,94 @@ +// Notifications: polling, not the websocket. `/api/v1/notifications/ws` +// exists server-side, but it authenticates the same way the community +// websocket does (Authorization header on the upgrade request, unreachable +// from a plain browser WebSocket) and would need its own Rust bridge +// identical in shape to community_ws.rs. Given the community chat socket +// was the hard requirement ("do not poll for chat") and notifications was +// explicitly flagged as an acceptable-to-poll first pass, this polls +// `GET /api/v1/notifications` on an interval instead of standing up a +// second websocket bridge. Revisit if live push turns out to matter here +// too -- the Rust-side pattern to copy is already written. + +import { apiGet, apiPost } from "./api"; + +export interface DropNotification { + id: string; + userId: string; + nonce: string | null; + created: string; + title: string; + description: string; + actions: string[]; + read: boolean; + acls: string[]; +} + +const POLL_INTERVAL_MS = 20_000; + +function notificationsState() { + return useState("notifications-list", () => []); +} +function loadedState() { + return useState("notifications-loaded", () => false); +} +function errorState() { + return useState("notifications-error", () => undefined); +} +function pollHandle() { + return useState | undefined>( + "notifications-poll-handle", + () => undefined, + ); +} + +export function useNotifications() { + const notifications = notificationsState(); + const loaded = loadedState(); + const error = errorState(); + + const unreadCount = computed( + () => notifications.value.filter((n) => !n.read).length, + ); + + async function refresh() { + try { + const result = await apiGet("api/v1/notifications"); + notifications.value = result; + error.value = undefined; + } catch (e) { + error.value = String(e); + } finally { + loaded.value = true; + } + } + + function startPolling() { + if (pollHandle().value) return; + refresh(); + pollHandle().value = setInterval(refresh, POLL_INTERVAL_MS); + } + + async function markRead(id: string) { + const target = notifications.value.find((n) => n.id === id); + if (target) target.read = true; // optimistic + try { + await apiPost(`api/v1/notifications/${id}/read`); + } catch (e) { + error.value = String(e); + await refresh(); + } + } + + async function markAllRead() { + const prior = notifications.value.map((n) => ({ ...n })); + for (const n of notifications.value) n.read = true; // optimistic + try { + await apiPost("api/v1/notifications/readall"); + } catch (e) { + notifications.value = prior; + error.value = String(e); + } + } + + return { notifications, loaded, error, unreadCount, refresh, startPolling, markRead, markAllRead }; +} diff --git a/desktop/main/composables/proton.ts b/desktop/main/composables/proton.ts new file mode 100644 index 00000000..eb91d3af --- /dev/null +++ b/desktop/main/composables/proton.ts @@ -0,0 +1,32 @@ +import { invoke } from "@tauri-apps/api/core"; + +interface ProtonPaths { + data: Ref<{ + autodiscovered: ProtonPath[]; + custom: ProtonPath[]; + default?: string; + }>; + refresh: () => Promise; +} + +const protonPaths = useState( + "proton_paths", + undefined, +); + +export const useProtonPaths = async (): Promise => { + const refresh = async () => { + protonPaths.value = await invoke("fetch_proton_paths"); + }; + if (protonPaths.value) + return { + data: protonPaths, + refresh, + }; + + await refresh(); + return { + data: protonPaths, + refresh, + }; +}; diff --git a/desktop/main/composables/state-navigation.ts b/desktop/main/composables/state-navigation.ts new file mode 100644 index 00000000..738fdf59 --- /dev/null +++ b/desktop/main/composables/state-navigation.ts @@ -0,0 +1,93 @@ +import { invoke } from "@tauri-apps/api/core"; +import { listen } from "@tauri-apps/api/event"; +import { data } from "autoprefixer"; +import { AppStatus, type AppState } from "~/types"; + +export function setupHooks() { + const router = useRouter(); + const state = useAppState(); + + listen("auth/processing", (event) => { + router.push("/auth/processing"); + }); + + listen("auth/failed", (event) => { + router.push( + `/auth/failed?error=${encodeURIComponent(event.payload as string)}` + ); + }); + + listen("auth/finished", async (event) => { + router.push("/library"); + state.value = JSON.parse(await invoke("fetch_state")); + }); + + listen("download_error", (event) => { + createModal( + ModalType.Notification, + { + title: "Drop encountered an error while downloading", + description: `Drop encountered an error while downloading your game: "${( + event.payload as unknown as string + ).toString()}"`, + buttonText: "Close", + }, + (e, c) => c() + ); + }); + + // This is for errors that (we think) aren't our fault + listen("launch_external_error", (event) => { + createModal( + ModalType.Confirmation, + { + title: "Did something go wrong?", + description: + "Drop detected that something might've gone wrong with launching your game. Do you want to open the log directory?", + buttonText: "Open", + }, + async (e, c) => { + if (e == "confirm") { + await invoke("open_process_logs", { gameId: event.payload }); + } + c(); + } + ); + }); + + /* + + document.addEventListener("contextmenu", (event) => { + event.target?.dispatchEvent(new Event("contextmenu")); + event.preventDefault(); + }); + + */ +} + +export function initialNavigation(state: ReturnType) { + if (!state.value) + throw createError({ + statusCode: 500, + statusMessage: "App state not valid", + fatal: true, + }); + const router = useRouter(); + + switch (state.value.status) { + case AppStatus.NotConfigured: + router.push({ path: "/setup" }); + break; + case AppStatus.SignedOut: + router.push("/auth"); + break; + case AppStatus.SignedInNeedsReauth: + router.push("/auth/signedout"); + break; + case AppStatus.ServerUnavailable: + router.push("/error/serverunavailable"); + break; + default: + router.push("/library"); + } +} diff --git a/desktop/main/composables/use-object.ts b/desktop/main/composables/use-object.ts new file mode 100644 index 00000000..642fd664 --- /dev/null +++ b/desktop/main/composables/use-object.ts @@ -0,0 +1,5 @@ +import { convertFileSrc } from "@tauri-apps/api/core"; + +export const useObject = (id: string) => { + return convertFileSrc(id, "object"); +}; diff --git a/desktop/main/error.vue b/desktop/main/error.vue new file mode 100644 index 00000000..65285ba9 --- /dev/null +++ b/desktop/main/error.vue @@ -0,0 +1,98 @@ + + + diff --git a/desktop/main/layouts/default.vue b/desktop/main/layouts/default.vue new file mode 100644 index 00000000..a65fce09 --- /dev/null +++ b/desktop/main/layouts/default.vue @@ -0,0 +1,82 @@ + + + diff --git a/desktop/main/layouts/mini.vue b/desktop/main/layouts/mini.vue new file mode 100644 index 00000000..31aa1407 --- /dev/null +++ b/desktop/main/layouts/mini.vue @@ -0,0 +1,8 @@ + diff --git a/desktop/main/nuxt.config.ts b/desktop/main/nuxt.config.ts new file mode 100644 index 00000000..72098882 --- /dev/null +++ b/desktop/main/nuxt.config.ts @@ -0,0 +1,22 @@ +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + compatibilityDate: "2024-04-03", + + postcss: { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, + }, + + css: ["~/assets/main.scss"], + + ssr: false, + devtools: false, + + extends: [["../../libraries/base"]], + + app: { + baseURL: "/main", + } +}); diff --git a/desktop/main/package.json b/desktop/main/package.json new file mode 100644 index 00000000..d27bf7dc --- /dev/null +++ b/desktop/main/package.json @@ -0,0 +1,41 @@ +{ + "name": "view", + "private": true, + "version": "0.3.4", + "type": "module", + "scripts": { + "build": "nuxt generate", + "dev": "nuxt dev", + "postinstall": "nuxt prepare", + "tauri": "tauri", + "typecheck": "nuxt typecheck" + }, + "dependencies": { + "@headlessui/vue": "^1.7.23", + "@heroicons/vue": "^2.1.5", + "@nuxtjs/tailwindcss": "^6.12.2", + "@tauri-apps/api": "^2.9.1", + "@tauri-apps/plugin-dialog": "^2.6.0", + "@tauri-apps/plugin-os": "^2.3.2", + "@tauri-apps/plugin-shell": "^2.3.3", + "@types/node": "^25.5.0", + "koa": "^2.16.1", + "markdown-it": "^14.2.0", + "micromark": "^4.0.1", + "nuxt": "^4.4.8", + "scss": "^0.2.4", + "vue-router": "latest", + "vuedraggable": "^4.1.0" + }, + "devDependencies": { + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/typography": "^0.5.15", + "@types/markdown-it": "^14.1.2", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.18", + "sass-embedded": "^1.79.4", + "tailwindcss": "^3.4.13", + "typescript": "^5.8.3", + "vue-tsc": "^2.2.10" + } +} diff --git a/desktop/main/pages/auth/code.vue b/desktop/main/pages/auth/code.vue new file mode 100644 index 00000000..1a9b953b --- /dev/null +++ b/desktop/main/pages/auth/code.vue @@ -0,0 +1,37 @@ + + + diff --git a/desktop/main/pages/auth/failed.vue b/desktop/main/pages/auth/failed.vue new file mode 100644 index 00000000..1eca3c0d --- /dev/null +++ b/desktop/main/pages/auth/failed.vue @@ -0,0 +1,34 @@ + + + diff --git a/desktop/main/pages/auth/index.vue b/desktop/main/pages/auth/index.vue new file mode 100644 index 00000000..059586f9 --- /dev/null +++ b/desktop/main/pages/auth/index.vue @@ -0,0 +1,18 @@ + + + diff --git a/desktop/main/pages/auth/processing.vue b/desktop/main/pages/auth/processing.vue new file mode 100644 index 00000000..db46b71b --- /dev/null +++ b/desktop/main/pages/auth/processing.vue @@ -0,0 +1,41 @@ + + + diff --git a/desktop/main/pages/auth/signedout.vue b/desktop/main/pages/auth/signedout.vue new file mode 100644 index 00000000..284cba0d --- /dev/null +++ b/desktop/main/pages/auth/signedout.vue @@ -0,0 +1,18 @@ + + + diff --git a/desktop/main/pages/community/chat.vue b/desktop/main/pages/community/chat.vue new file mode 100644 index 00000000..8bfd7fd3 --- /dev/null +++ b/desktop/main/pages/community/chat.vue @@ -0,0 +1,252 @@ + + + diff --git a/desktop/main/pages/community/friends.vue b/desktop/main/pages/community/friends.vue new file mode 100644 index 00000000..644bb372 --- /dev/null +++ b/desktop/main/pages/community/friends.vue @@ -0,0 +1,247 @@ + + + diff --git a/desktop/main/pages/community/index.vue b/desktop/main/pages/community/index.vue new file mode 100644 index 00000000..a9648cde --- /dev/null +++ b/desktop/main/pages/community/index.vue @@ -0,0 +1,143 @@ + + + diff --git a/desktop/main/pages/community/profile/[username].vue b/desktop/main/pages/community/profile/[username].vue new file mode 100644 index 00000000..45dcb3c8 --- /dev/null +++ b/desktop/main/pages/community/profile/[username].vue @@ -0,0 +1,233 @@ + + + diff --git a/desktop/main/pages/error/serverunavailable.vue b/desktop/main/pages/error/serverunavailable.vue new file mode 100644 index 00000000..cfbb1785 --- /dev/null +++ b/desktop/main/pages/error/serverunavailable.vue @@ -0,0 +1,88 @@ + + + diff --git a/desktop/main/pages/index.vue b/desktop/main/pages/index.vue new file mode 100644 index 00000000..52bc1df6 --- /dev/null +++ b/desktop/main/pages/index.vue @@ -0,0 +1,7 @@ +