mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-06-22 04:11:32 +10:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 40be0ae461 | |||
| cbecd1161d | |||
| 9185089c99 | |||
| 0290718ee0 | |||
| 2e86422004 | |||
| 796abf478f | |||
| 062ddc0c24 | |||
| ac1e0230ae | |||
| 8637ff52ef | |||
| 18471a1d35 | |||
| c8bb84e0d8 | |||
| a3974f6137 | |||
| 0b4e20bd0f | |||
| 546f47e40e | |||
| 516eaade4f | |||
| 784e42f177 | |||
| 758baa9bbb | |||
| bf7ce5927f | |||
| ff1144e016 | |||
| 5bbe406e4c | |||
| 6bb7eca145 | |||
| 2dd90fbc44 |
@@ -0,0 +1,6 @@
|
||||
/sites
|
||||
/cli
|
||||
/desktop
|
||||
/backend # go backend
|
||||
|
||||
node_modules
|
||||
@@ -1,7 +1,12 @@
|
||||
name: "publish"
|
||||
name: "Build and release desktop"
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
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
|
||||
@@ -33,13 +38,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
- name: setup node
|
||||
@@ -58,7 +61,7 @@ jobs:
|
||||
- name: Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src-tauri -> target'
|
||||
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.
|
||||
@@ -80,6 +83,10 @@ jobs:
|
||||
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"
|
||||
|
||||
@@ -115,14 +122,19 @@ jobs:
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
# 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: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
|
||||
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'
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,12 +1,24 @@
|
||||
name: CI
|
||||
name: Server CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
branches: [develop]
|
||||
paths:
|
||||
- "server/**"
|
||||
- "libraries/base/**"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
- "pnpm-workspace.yaml"
|
||||
- ".github/workflows/server-ci.yml"
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
branches: [develop]
|
||||
paths:
|
||||
- "server/**"
|
||||
- "libraries/base/**"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
- "pnpm-workspace.yaml"
|
||||
- ".github/workflows/server-ci.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -18,8 +30,6 @@ jobs:
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -34,6 +44,7 @@ jobs:
|
||||
run: pnpm install
|
||||
|
||||
- name: Typecheck
|
||||
working-directory: server
|
||||
run: pnpm run typecheck
|
||||
|
||||
lint:
|
||||
@@ -42,8 +53,6 @@ jobs:
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -58,4 +67,5 @@ jobs:
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint
|
||||
working-directory: server
|
||||
run: pnpm run lint
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Release Workflow
|
||||
name: Build and release server
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
@@ -29,7 +29,6 @@ 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 }}
|
||||
@@ -0,0 +1,2 @@
|
||||
dist/
|
||||
node_modules/
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# 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 . .
|
||||
RUN cargo build --release --manifest-path ./torrential/Cargo.toml
|
||||
|
||||
### BUILD APP
|
||||
FROM base AS build-system
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NUXT_TELEMETRY_DISABLED=1
|
||||
|
||||
## 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 . .
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
|
||||
|
||||
ARG BUILD_DROP_VERSION
|
||||
ARG BUILD_GIT_REF
|
||||
|
||||
## build
|
||||
RUN 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
|
||||
## 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/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/target/release/torrential /usr/bin/
|
||||
|
||||
ENV LIBRARY="/library"
|
||||
ENV DATA="/data"
|
||||
ENV NGINX_CONFIG="/nginx.conf"
|
||||
# Nuxt's port
|
||||
ENV PORT=4000
|
||||
|
||||
CMD ["sh", "/app/startup/launch.sh"]
|
||||
@@ -6,7 +6,7 @@
|
||||
# Drop
|
||||
|
||||
[](https://droposs.org)
|
||||
[](https://docs.droposs.org/)
|
||||
[](https://droposs.org/docs)
|
||||
[](https://forum.droposs.org)
|
||||
[](LICENSE)
|
||||
[](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
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/bin
|
||||
@@ -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())
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
module drop/core
|
||||
|
||||
go 1.26.1
|
||||
|
||||
require (
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.9.1 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
github.com/davecgh/go-spew v1.1.0/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.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
|
||||
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
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=
|
||||
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=
|
||||
@@ -0,0 +1,5 @@
|
||||
module drop
|
||||
|
||||
go 1.26.1
|
||||
|
||||
require github.com/gorilla/mux v1.8.1
|
||||
@@ -0,0 +1,2 @@
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
@@ -0,0 +1,3 @@
|
||||
go 1.26.1
|
||||
|
||||
use ./core
|
||||
@@ -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=
|
||||
@@ -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()
|
||||
}
|
||||
Generated
+277
-69
@@ -2,6 +2,21 @@
|
||||
# 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"
|
||||
@@ -105,10 +120,10 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
"synstructure 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -117,10 +132,10 @@ version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
"synstructure 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -129,9 +144,9 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -140,9 +155,9 @@ version = "0.1.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -190,6 +205,21 @@ dependencies = [
|
||||
"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"
|
||||
@@ -295,9 +325,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -534,9 +564,9 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -577,26 +607,37 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "droplet-rs"
|
||||
version = "0.14.1"
|
||||
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"
|
||||
@@ -640,6 +681,28 @@ dependencies = [
|
||||
"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"
|
||||
@@ -737,9 +800,9 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -809,6 +872,18 @@ dependencies = [
|
||||
"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"
|
||||
@@ -1193,9 +1268,9 @@ version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1261,6 +1336,24 @@ 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"
|
||||
@@ -1335,6 +1428,15 @@ 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"
|
||||
@@ -1421,6 +1523,15 @@ dependencies = [
|
||||
"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"
|
||||
@@ -1530,6 +1641,12 @@ 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"
|
||||
@@ -1569,6 +1686,15 @@ 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"
|
||||
@@ -1654,13 +1780,22 @@ dependencies = [
|
||||
"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",
|
||||
"proc-macro2 1.0.103",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1887,6 +2022,12 @@ dependencies = [
|
||||
"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"
|
||||
@@ -2084,9 +2225,9 @@ version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2180,6 +2321,15 @@ dependencies = [
|
||||
"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"
|
||||
@@ -2198,14 +2348,36 @@ 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",
|
||||
"quote",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@@ -2218,15 +2390,27 @@ 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",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2263,6 +2447,18 @@ dependencies = [
|
||||
"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"
|
||||
@@ -2287,9 +2483,9 @@ version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2298,9 +2494,9 @@ version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2390,9 +2586,9 @@ version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2507,6 +2703,18 @@ 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"
|
||||
@@ -2627,7 +2835,7 @@ version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"quote 1.0.43",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
@@ -2638,9 +2846,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -2748,9 +2956,9 @@ version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2759,9 +2967,9 @@ version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3095,10 +3303,10 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
"synstructure 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3116,9 +3324,9 @@ version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3136,10 +3344,10 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
"synstructure 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3176,9 +3384,9 @@ version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2 1.0.103",
|
||||
"quote 1.0.43",
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ clap = { version = "4.5.54", features = ["derive"] }
|
||||
console = "0.16.2"
|
||||
dialoguer = "0.12.0"
|
||||
dirs = "6.0.0"
|
||||
droplet-rs = { path = "../droplet-rs", version = "0.14" }
|
||||
droplet-rs = { path = "../libraries/droplet" }
|
||||
fern = { version = "0.7.1", features = ["colored"] }
|
||||
futures = "0.3.31"
|
||||
indicatif = "0.18.3"
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::path::Path;
|
||||
use crate::{
|
||||
cli::UploadInfo,
|
||||
commands::connect::{config::Config, config_option::ConfigOption},
|
||||
manifest::{CompressionOption, DepotManifest, generate_v2_manifest},
|
||||
manifest::{ClosureFactory, CompressionOption, DepotManifest, generate_v2_manifest},
|
||||
operator_builder::OperatorBuilder,
|
||||
};
|
||||
use futures::AsyncWriteExt;
|
||||
@@ -12,13 +12,13 @@ use opendal::{FuturesAsyncWriter, Operator};
|
||||
use tokio_util::compat::{Compat, FuturesAsyncWriteCompatExt};
|
||||
|
||||
pub async fn upload(
|
||||
info: &UploadInfo,
|
||||
upload_info: &UploadInfo,
|
||||
config: Config,
|
||||
name: &Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
let game_id = &info.game_id;
|
||||
let path = &info.path;
|
||||
let version_id = &info.version_id;
|
||||
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)?;
|
||||
|
||||
@@ -27,28 +27,30 @@ pub async fn upload(
|
||||
info!("Uploading chunks");
|
||||
|
||||
let v2_manifest = generate_v2_manifest(
|
||||
Path::new(path),
|
||||
async |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<FuturesAsyncWriter>| async {
|
||||
writer.into_inner().close().await.unwrap();
|
||||
},
|
||||
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<FuturesAsyncWriter>| async {
|
||||
writer.into_inner().close().await.unwrap();
|
||||
},
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Finished uploading chunks");
|
||||
|
||||
existing_depot_manifest.append(
|
||||
game_id.to_string(),
|
||||
version_id.to_string(),
|
||||
upload_info.game_id.to_string(),
|
||||
upload_info.version_id.to_string(),
|
||||
CompressionOption::None,
|
||||
);
|
||||
Ok(())
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![feature(async_fn_traits)]
|
||||
|
||||
use crate::commands::connect::config::manage_configuration;
|
||||
use crate::{
|
||||
cli::{Cli, Commands},
|
||||
|
||||
+58
-10
@@ -1,8 +1,7 @@
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use droplet_rs::manifest::{
|
||||
Manifest, generate_manifest_rusty, generate_manifest_rusty_v2,
|
||||
};
|
||||
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};
|
||||
@@ -40,11 +39,60 @@ impl DepotManifest {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn generate_v2_manifest<W, F, CloseF>(dir: &Path, factory: F, closer: CloseF) -> anyhow::Result<Manifest>
|
||||
pub struct ClosureFactory<Writer, Factory, Closer>
|
||||
where
|
||||
W: AsyncWrite + Unpin,
|
||||
F: AsyncFn(String) -> W,
|
||||
CloseF: AsyncFn(W)
|
||||
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<W, F, C>
|
||||
where
|
||||
for<'a> F::CallRefFuture<'a>: Send,
|
||||
for<'b> C::CallRefFuture<'b>: Send,
|
||||
{
|
||||
type Writer = W;
|
||||
|
||||
async fn create(&self, id: String) -> anyhow::Result<Self::Writer> {
|
||||
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<W, F, C>
|
||||
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<Factory>(dir: &Path, factory: Factory) -> anyhow::Result<Manifest>
|
||||
where
|
||||
Factory: ManifestWriterFactory,
|
||||
{
|
||||
let progress_bar = ProgressBar::new(10_000).with_style(
|
||||
ProgressStyle::default_bar()
|
||||
@@ -52,15 +100,15 @@ where
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
generate_manifest_rusty_v2(
|
||||
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)),
|
||||
factory,
|
||||
closer
|
||||
Some(&factory),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Arch Linux, Windows]
|
||||
- App Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
Vendored
-23
@@ -1,23 +0,0 @@
|
||||
on: push
|
||||
name: Clippy check
|
||||
jobs:
|
||||
clippy_check:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: install dependencies (ubuntu only)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: clippy
|
||||
override: true
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --manifest-path ./src-tauri/Cargo.toml
|
||||
@@ -1,6 +0,0 @@
|
||||
[submodule "src-tauri/tailscale/libtailscale"]
|
||||
path = src-tauri/tailscale/libtailscale
|
||||
url = https://github.com/tailscale/libtailscale.git
|
||||
[submodule "libs/drop-base"]
|
||||
path = libs/drop-base
|
||||
url = https://github.com/drop-oss/drop-base.git
|
||||
@@ -21,13 +21,6 @@ async function spawn(exec, opts) {
|
||||
});
|
||||
}
|
||||
|
||||
const expectedLibs = ["drop-base/package.json"];
|
||||
|
||||
for (const lib of expectedLibs) {
|
||||
const path = `./libs/${lib}`;
|
||||
if (!fs.existsSync(path)) throw `Missing "${expectedLibs}". Run "git submodule update --init --recursive"`;
|
||||
}
|
||||
|
||||
const views = fs.readdirSync(".").filter((view) => {
|
||||
const expectedPath = `./${view}/package.json`;
|
||||
return fs.existsSync(expectedPath);
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<Listbox
|
||||
as="div"
|
||||
v-model="model.overrideHandler"
|
||||
class="mt-6"
|
||||
v-if="handlers.length > 1"
|
||||
>
|
||||
<ListboxLabel class="block text-sm/6 font-medium text-white"
|
||||
>Launch method</ListboxLabel
|
||||
>
|
||||
<div class="relative mt-2">
|
||||
<ListboxButton
|
||||
class="grid w-full cursor-default grid-cols-1 rounded-md bg-white/5 py-1.5 pr-2 pl-3 text-left text-white outline-1 -outline-offset-1 outline-white/10 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-500 sm:text-sm/6"
|
||||
>
|
||||
<span
|
||||
v-if="currentHandler"
|
||||
class="col-start-1 row-start-1 truncate pr-6"
|
||||
>{{ currentHandler.name }}</span
|
||||
>
|
||||
<span
|
||||
v-else
|
||||
class="col-start-1 row-start-1 truncate pr-6 italic text-zinc-400"
|
||||
>Automatic</span
|
||||
>
|
||||
<ChevronUpDownIcon
|
||||
class="col-start-1 row-start-1 size-5 self-center justify-self-end text-zinc-400 sm:size-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</ListboxButton>
|
||||
|
||||
<transition
|
||||
leave-active-class="transition ease-in duration-100"
|
||||
leave-from-class=""
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<ListboxOptions
|
||||
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-zinc-800 py-1 text-base outline-1 -outline-offset-1 outline-white/10 sm:text-sm"
|
||||
>
|
||||
<ListboxOption
|
||||
as="template"
|
||||
:value="undefined"
|
||||
v-slot="{ active, selected }"
|
||||
>
|
||||
<li
|
||||
:class="[
|
||||
active ? 'bg-blue-500 text-white outline-hidden' : 'text-white',
|
||||
'relative cursor-default py-2 pr-9 pl-3 select-none',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
selected ? 'font-semibold' : 'font-normal',
|
||||
'block truncate italic',
|
||||
]"
|
||||
>Automatic</span
|
||||
>
|
||||
<span class="block truncate text-xs text-zinc-400"
|
||||
>Pick the best method for this game.</span
|
||||
>
|
||||
|
||||
<span
|
||||
v-if="selected"
|
||||
:class="[
|
||||
active ? 'text-white' : 'text-blue-400',
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4',
|
||||
]"
|
||||
>
|
||||
<CheckIcon class="size-5" aria-hidden="true" />
|
||||
</span>
|
||||
</li>
|
||||
</ListboxOption>
|
||||
<ListboxOption
|
||||
as="template"
|
||||
v-for="handler in handlers"
|
||||
:key="handler.id"
|
||||
:value="handler.id"
|
||||
v-slot="{ active, selected }"
|
||||
>
|
||||
<li
|
||||
:class="[
|
||||
active ? 'bg-blue-500 text-white outline-hidden' : 'text-white',
|
||||
'relative cursor-default py-2 pr-9 pl-3 select-none',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
selected ? 'font-semibold' : 'font-normal',
|
||||
'block truncate',
|
||||
]"
|
||||
>{{ handler.name }}</span
|
||||
>
|
||||
<span class="block truncate text-xs text-zinc-400">{{
|
||||
handler.description
|
||||
}}</span>
|
||||
|
||||
<span
|
||||
v-if="selected"
|
||||
:class="[
|
||||
active ? 'text-white' : 'text-blue-400',
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4',
|
||||
]"
|
||||
>
|
||||
<CheckIcon class="size-5" aria-hidden="true" />
|
||||
</span>
|
||||
</li>
|
||||
</ListboxOption>
|
||||
</ListboxOptions>
|
||||
</transition>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-zinc-400">
|
||||
Override how this game is launched.
|
||||
</p>
|
||||
</Listbox>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
ListboxLabel,
|
||||
ListboxOption,
|
||||
ListboxOptions,
|
||||
} from "@headlessui/vue";
|
||||
import { ChevronUpDownIcon } from "@heroicons/vue/16/solid";
|
||||
import { CheckIcon } from "@heroicons/vue/20/solid";
|
||||
import type { GameVersion } from "~/types";
|
||||
|
||||
type ProcessHandlerOption = { id: string; name: string; description: string };
|
||||
|
||||
const model = defineModel<GameVersion["userConfiguration"]>({ required: true });
|
||||
const props = defineProps<{ gameId: string }>();
|
||||
|
||||
const handlers = await invoke<ProcessHandlerOption[]>("get_process_handlers", {
|
||||
id: props.gameId,
|
||||
});
|
||||
|
||||
const currentHandler = computed(() =>
|
||||
handlers.find((v) => v.id == model.value.overrideHandler),
|
||||
);
|
||||
</script>
|
||||
@@ -23,16 +23,19 @@
|
||||
</p>
|
||||
|
||||
<ProtonSelector v-model="model" v-if="$props.protonEnabled" />
|
||||
<HandlerSelector v-model="model" :game-id="$props.gameId" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { GameVersion } from "~/types";
|
||||
import ProtonSelector from "./ProtonSelector.vue";
|
||||
import HandlerSelector from "./HandlerSelector.vue";
|
||||
|
||||
const model = defineModel<GameVersion["userConfiguration"]>({ required: true });
|
||||
|
||||
const props = defineProps<{
|
||||
protonEnabled: boolean;
|
||||
gameId: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ModalTemplate size-class="max-w-4xl" v-model="open">
|
||||
<template #default>
|
||||
<div class="flex flex-row gap-x-4 h-96">
|
||||
<div class="flex flex-row gap-x-4 min-h-96">
|
||||
<nav class="flex flex-1 flex-col" aria-label="Sidebar">
|
||||
<ul role="list" class="-mx-2 space-y-1">
|
||||
<li v-for="(tab, tabIdx) in tabs" :key="tab.name">
|
||||
@@ -29,11 +29,12 @@
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="border-l-2 border-zinc-800 w-full grow pl-4 overflow-y-scroll">
|
||||
<div class="border-l-2 border-zinc-800 w-full grow pl-4">
|
||||
<component
|
||||
v-model="configuration"
|
||||
:is="tabs[currentTabIndex]?.page"
|
||||
:proton-enabled="protonEnabled"
|
||||
:game-id="props.gameId"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
as="div"
|
||||
v-for="(nav, navIndex) in filteredNavigation"
|
||||
:key="nav.id"
|
||||
:class="['first:pt-0 last:pb-0', nav.tools ? 'mt-auto' : '']"
|
||||
:class="['first:pt-0 last:pb-0', nav.tools && !filteredNavigation[navIndex - 1].tools ? 'mt-auto' : '']"
|
||||
v-slot="{ open }"
|
||||
:default-open="nav.deft"
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"koa": "^2.16.1",
|
||||
"markdown-it": "^14.1.0",
|
||||
"micromark": "^4.0.1",
|
||||
"nuxt": "^3.16.0",
|
||||
"nuxt": "^4.4.8",
|
||||
"scss": "^0.2.4",
|
||||
"vue-router": "latest",
|
||||
"vuedraggable": "^4.1.0"
|
||||
|
||||
Generated
+2957
-2333
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,7 @@
|
||||
const path = require('path');
|
||||
|
||||
const dropbase = path.join(__dirname, "../../libraries/base")
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
@@ -7,7 +11,7 @@ export default {
|
||||
"./plugins/**/*.{js,ts}",
|
||||
"./app.vue",
|
||||
"./error.vue",
|
||||
"../libs/drop-base/**/*.{js,vue,ts}",
|
||||
`${dropbase}/components/**/*.{js,vue,ts}`,
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
|
||||
@@ -53,6 +53,7 @@ export type GameVersion = {
|
||||
userConfiguration: {
|
||||
launchTemplate: string;
|
||||
overrideProtonPath: string;
|
||||
overrideHandler: string | undefined;
|
||||
enableUpdates: boolean
|
||||
};
|
||||
setups: Array<{ platform: string }>;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"pino": "^9.7.0",
|
||||
"pino": "^9.14.0",
|
||||
"pino-pretty": "^13.1.1",
|
||||
"tauri": "^0.15.0"
|
||||
},
|
||||
|
||||
Generated
-5583
File diff suppressed because it is too large
Load Diff
@@ -1,21 +0,0 @@
|
||||
onlyBuiltDependencies:
|
||||
- sharp
|
||||
|
||||
overrides:
|
||||
cross-spawn@<6.0.6: '>=6.0.6'
|
||||
cross-spawn@>=7.0.0 <7.0.5: '>=7.0.5'
|
||||
form-data@<2.5.4: '>=2.5.4'
|
||||
got@<11.8.5: '>=11.8.5'
|
||||
http-cache-semantics@<4.1.1: '>=4.1.1'
|
||||
lodash@<4.17.21: '>=4.17.21'
|
||||
lodash@>=4.0.0 <4.17.21: '>=4.17.21'
|
||||
minimist@>=1.0.0 <1.2.6: '>=1.2.6'
|
||||
nth-check@<2.0.1: '>=2.0.1'
|
||||
semver-regex@<3.1.3: '>=3.1.3'
|
||||
semver-regex@<3.1.4: '>=3.1.4'
|
||||
semver@>=7.0.0 <7.5.2: '>=7.5.2'
|
||||
sharp@<0.30.5: '>=0.30.5'
|
||||
sharp@<0.32.6: '>=0.32.6'
|
||||
tmp@<=0.2.3: '>=0.2.4'
|
||||
tough-cookie@<4.1.3: '>=4.1.3'
|
||||
trim-newlines@<3.0.1: '>=3.0.1'
|
||||
Generated
+140
-535
File diff suppressed because it is too large
Load Diff
@@ -41,7 +41,6 @@ database = { path = "./database" } # database
|
||||
deranged = "=0.4.0"
|
||||
dirs = "6.0.0"
|
||||
download_manager = { path = "./download_manager", version = "0.1.0" } # download manager
|
||||
droplet-rs = "0.7.3"
|
||||
filetime = "0.2.25"
|
||||
futures-core = "0.3.31"
|
||||
futures-lite = "2.6.0"
|
||||
@@ -83,7 +82,7 @@ sha1 = "0.10.6"
|
||||
shared_child = "1.0.1"
|
||||
slice-deque = "0.3.0"
|
||||
sysinfo = "0.36.1"
|
||||
tar = "0.4.44"
|
||||
tar = "0.4.46"
|
||||
tauri-plugin-autostart = "*"
|
||||
tauri-plugin-deep-link = "*"
|
||||
tauri-plugin-dialog = "*"
|
||||
@@ -137,6 +136,9 @@ tauri-build = { version = "*", features = [] }
|
||||
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies]
|
||||
tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] }
|
||||
|
||||
[target."cfg(target_os = \"linux\")".dependencies]
|
||||
libloading = "0.7"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
panic = "abort"
|
||||
|
||||
@@ -12,7 +12,7 @@ rustix = "1.1.2"
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.145"
|
||||
serde_with = "3.15.0"
|
||||
tar = "0.4.44"
|
||||
tar = "0.4.46"
|
||||
tempfile = "3.23.0"
|
||||
uuid = "1.18.1"
|
||||
whoami = "1.6.1"
|
||||
|
||||
@@ -79,6 +79,7 @@ pub mod data {
|
||||
UserConfiguration {
|
||||
launch_template: "{}".to_owned(),
|
||||
override_proton_path: None,
|
||||
override_handler: None,
|
||||
enable_updates: false,
|
||||
}
|
||||
}
|
||||
@@ -88,6 +89,8 @@ pub mod data {
|
||||
pub struct UserConfiguration {
|
||||
pub launch_template: String,
|
||||
pub override_proton_path: Option<String>,
|
||||
#[serde(default)]
|
||||
pub override_handler: Option<String>,
|
||||
pub enable_updates: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
sync::RwLock,
|
||||
time::{Duration, Instant}, usize,
|
||||
time::{Duration, Instant},
|
||||
usize,
|
||||
};
|
||||
|
||||
use futures_util::StreamExt;
|
||||
@@ -34,7 +36,7 @@ struct Depot {
|
||||
manifest: Option<DepotManifest>,
|
||||
latest_speed: Option<usize>, // bytes per second
|
||||
current_downloads: SyncSemaphore,
|
||||
enabled: bool
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
pub struct DepotManager {
|
||||
@@ -113,10 +115,16 @@ impl DepotManager {
|
||||
for depot in &mut new_depots {
|
||||
if let Err(sync_error) = self.sync_depot(depot).await {
|
||||
warn!("failed to sync depot {}: {:?}", depot.endpoint, sync_error);
|
||||
depot.enabled = false;
|
||||
if env::var("FORCE_ENABLE_DEPOTS")
|
||||
.map(|v| !v.is_empty())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
} else {
|
||||
depot.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let enabled = new_depots.iter().filter(|v| v.enabled).count();
|
||||
if enabled == 0 {
|
||||
return Err(RemoteAccessError::NoDepots);
|
||||
|
||||
@@ -14,7 +14,7 @@ crossbeam-channel = "0.5.15"
|
||||
ctr = "0.9.2"
|
||||
database = { path = "../database", version = "0.1.0" }
|
||||
download_manager = { path = "../download_manager", version = "0.1.0" }
|
||||
droplet-rs = { path = "../../../libraries/droplet" }
|
||||
droplet_types = { path = "../../../libraries/droplet_types" }
|
||||
futures-util = "*"
|
||||
hex = "0.4.3"
|
||||
log = "0.4.28"
|
||||
|
||||
@@ -11,7 +11,7 @@ use download_manager::util::download_thread_control_flag::{
|
||||
DownloadThreadControl, DownloadThreadControlFlag,
|
||||
};
|
||||
use download_manager::util::progress_object::{ProgressHandle, ProgressObject, ProgressType};
|
||||
use droplet_rs::manifest::{ChunkData, Manifest};
|
||||
use droplet_types::{ChunkData, Manifest};
|
||||
use futures_util::StreamExt;
|
||||
use futures_util::stream::FuturesUnordered;
|
||||
use log::{debug, error, info, warn};
|
||||
|
||||
@@ -13,7 +13,7 @@ use download_manager::util::download_thread_control_flag::{
|
||||
DownloadThreadControl, DownloadThreadControlFlag,
|
||||
};
|
||||
use download_manager::util::progress_object::ProgressHandle;
|
||||
use droplet_rs::manifest::ChunkData;
|
||||
use droplet_types::ChunkData;
|
||||
use futures_util::StreamExt as _;
|
||||
use log::{debug, info};
|
||||
use remote::auth::generate_authorization_header;
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use std::{fs::create_dir_all, path::PathBuf, process::Command};
|
||||
use std::{
|
||||
fs::create_dir_all,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use client::compat::{COMPAT_INFO, UMU_LAUNCHER_EXECUTABLE};
|
||||
use database::{
|
||||
Database, DownloadableMetadata, GameVersion, db::DATA_ROOT_DIR, platform::Platform,
|
||||
};
|
||||
|
||||
use crate::{error::ProcessError, process_manager::ProcessHandler};
|
||||
use crate::{error::ProcessError, parser::ParsedCommand, process_manager::ProcessHandler};
|
||||
|
||||
pub struct MacLauncher;
|
||||
impl ProcessHandler for MacLauncher {
|
||||
@@ -25,11 +29,89 @@ impl ProcessHandler for MacLauncher {
|
||||
}
|
||||
|
||||
fn modify_command(&self, _command: &mut Command) {}
|
||||
|
||||
fn id(&self) -> &'static str {
|
||||
"macos"
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Direct"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Launches the game directly on macOS."
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
|
||||
#[cfg_attr(not(target_os = "windows"), allow(unused_variables))]
|
||||
fn apply_no_window(command: &mut Command) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
command.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
}
|
||||
|
||||
enum WindowsLaunchStrategy {
|
||||
Direct,
|
||||
Cmd,
|
||||
Powershell,
|
||||
}
|
||||
|
||||
// Wrap a launch command for Windows; with no strategy, detect it from the file extension.
|
||||
fn windows_launch_command(
|
||||
launch_command: String,
|
||||
current_dir: &str,
|
||||
strategy: Option<WindowsLaunchStrategy>,
|
||||
) -> Result<String, ProcessError> {
|
||||
let mut parsed = ParsedCommand::parse(launch_command)?;
|
||||
|
||||
let strategy = strategy.unwrap_or_else(|| {
|
||||
let extension = Path::new(&parsed.command)
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.map(str::to_ascii_lowercase);
|
||||
match extension.as_deref() {
|
||||
Some("ps1") => WindowsLaunchStrategy::Powershell,
|
||||
Some("exe") | Some("com") => WindowsLaunchStrategy::Direct,
|
||||
_ => WindowsLaunchStrategy::Cmd,
|
||||
}
|
||||
});
|
||||
|
||||
match strategy {
|
||||
// PowerShell scripts
|
||||
WindowsLaunchStrategy::Powershell => {
|
||||
parsed.make_absolute(PathBuf::from(current_dir));
|
||||
let script = std::mem::replace(&mut parsed.command, "powershell".to_owned());
|
||||
let mut args = vec![
|
||||
"-NoProfile".to_owned(),
|
||||
"-ExecutionPolicy".to_owned(),
|
||||
"Bypass".to_owned(),
|
||||
"-File".to_owned(),
|
||||
script,
|
||||
];
|
||||
args.append(&mut parsed.args);
|
||||
parsed.args = args;
|
||||
}
|
||||
// Direct executables
|
||||
WindowsLaunchStrategy::Direct => {
|
||||
parsed.make_absolute(PathBuf::from(current_dir));
|
||||
}
|
||||
// cmd.exe, for batch files, builtins, PATHEXT resolution, %VAR% expansion, etc.
|
||||
WindowsLaunchStrategy::Cmd => {
|
||||
let command = std::mem::replace(&mut parsed.command, "cmd".to_owned());
|
||||
let mut args = vec!["/C".to_owned(), command];
|
||||
args.append(&mut parsed.args);
|
||||
parsed.args = args;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(parsed.reconstruct())
|
||||
}
|
||||
|
||||
pub struct WindowsLauncher;
|
||||
impl ProcessHandler for WindowsLauncher {
|
||||
fn create_launch_process(
|
||||
@@ -37,22 +119,169 @@ impl ProcessHandler for WindowsLauncher {
|
||||
_meta: &DownloadableMetadata,
|
||||
launch_command: String,
|
||||
_game_version: &GameVersion,
|
||||
_current_dir: &str,
|
||||
current_dir: &str,
|
||||
_database: &Database,
|
||||
) -> Result<String, ProcessError> {
|
||||
Ok(format!("cmd /C \"{}\"", launch_command))
|
||||
windows_launch_command(launch_command, current_dir, None)
|
||||
}
|
||||
|
||||
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn modify_command(&self, command: &mut Command) {
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::os::windows::process::CommandExt;
|
||||
#[cfg(target_os = "windows")]
|
||||
command.creation_flags(CREATE_NO_WINDOW);
|
||||
apply_no_window(command);
|
||||
}
|
||||
|
||||
fn id(&self) -> &'static str {
|
||||
"windows-auto"
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Automatic"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Detects the file type and launches it directly, or through cmd or PowerShell."
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowsDirectLauncher;
|
||||
impl ProcessHandler for WindowsDirectLauncher {
|
||||
fn create_launch_process(
|
||||
&self,
|
||||
_meta: &DownloadableMetadata,
|
||||
launch_command: String,
|
||||
_game_version: &GameVersion,
|
||||
current_dir: &str,
|
||||
_database: &Database,
|
||||
) -> Result<String, ProcessError> {
|
||||
windows_launch_command(launch_command, current_dir, Some(WindowsLaunchStrategy::Direct))
|
||||
}
|
||||
|
||||
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn modify_command(&self, command: &mut Command) {
|
||||
apply_no_window(command);
|
||||
}
|
||||
|
||||
fn id(&self) -> &'static str {
|
||||
"windows-direct"
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Direct executable"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Runs the executable directly, without a shell."
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowsCmdLauncher;
|
||||
impl ProcessHandler for WindowsCmdLauncher {
|
||||
fn create_launch_process(
|
||||
&self,
|
||||
_meta: &DownloadableMetadata,
|
||||
launch_command: String,
|
||||
_game_version: &GameVersion,
|
||||
current_dir: &str,
|
||||
_database: &Database,
|
||||
) -> Result<String, ProcessError> {
|
||||
windows_launch_command(launch_command, current_dir, Some(WindowsLaunchStrategy::Cmd))
|
||||
}
|
||||
|
||||
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn modify_command(&self, command: &mut Command) {
|
||||
apply_no_window(command);
|
||||
}
|
||||
|
||||
fn id(&self) -> &'static str {
|
||||
"windows-cmd"
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Command Prompt (cmd)"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Launches through cmd.exe. Supports batch files, builtins and %VAR% expansion."
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowsPowershellLauncher;
|
||||
impl ProcessHandler for WindowsPowershellLauncher {
|
||||
fn create_launch_process(
|
||||
&self,
|
||||
_meta: &DownloadableMetadata,
|
||||
launch_command: String,
|
||||
_game_version: &GameVersion,
|
||||
current_dir: &str,
|
||||
_database: &Database,
|
||||
) -> Result<String, ProcessError> {
|
||||
windows_launch_command(
|
||||
launch_command,
|
||||
current_dir,
|
||||
Some(WindowsLaunchStrategy::Powershell),
|
||||
)
|
||||
}
|
||||
|
||||
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn modify_command(&self, command: &mut Command) {
|
||||
apply_no_window(command);
|
||||
}
|
||||
|
||||
fn id(&self) -> &'static str {
|
||||
"windows-powershell"
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"PowerShell"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Runs the command as a PowerShell script (-File)."
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LinuxNativeLauncher;
|
||||
impl ProcessHandler for LinuxNativeLauncher {
|
||||
fn create_launch_process(
|
||||
&self,
|
||||
_meta: &DownloadableMetadata,
|
||||
launch_command: String,
|
||||
_game_version: &GameVersion,
|
||||
_current_dir: &str,
|
||||
_database: &Database,
|
||||
) -> Result<String, ProcessError> {
|
||||
// Run native Linux games directly, no umu-run wrapper
|
||||
Ok(launch_command)
|
||||
}
|
||||
|
||||
fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn modify_command(&self, _command: &mut Command) {}
|
||||
|
||||
fn id(&self) -> &'static str {
|
||||
"linux-native"
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Native (direct)"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Runs the native Linux game directly on the host."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +330,18 @@ impl ProcessHandler for UMUNativeLauncher {
|
||||
}
|
||||
|
||||
fn modify_command(&self, _command: &mut Command) {}
|
||||
|
||||
fn id(&self) -> &'static str {
|
||||
"linux-umu"
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Steam Linux Runtime (umu-run)"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Runs the native Linux game inside umu-run's Steam Linux Runtime."
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UMUCompatLauncher;
|
||||
@@ -168,6 +409,18 @@ impl ProcessHandler for UMUCompatLauncher {
|
||||
}
|
||||
|
||||
fn modify_command(&self, _command: &mut Command) {}
|
||||
|
||||
fn id(&self) -> &'static str {
|
||||
"proton-umu"
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Proton (umu-run)"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Runs the Windows game through Proton using umu-run."
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AsahiMuvmLauncher;
|
||||
@@ -228,4 +481,16 @@ impl ProcessHandler for AsahiMuvmLauncher {
|
||||
}
|
||||
|
||||
fn modify_command(&self, _command: &mut Command) {}
|
||||
|
||||
fn id(&self) -> &'static str {
|
||||
"proton-muvm"
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"Proton + muvm (Asahi)"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Runs through Proton inside a muvm microVM, for Apple Silicon / Asahi Linux."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ use crate::{
|
||||
format::DropFormatArgs,
|
||||
parser::{LaunchParameters, ParsedCommand},
|
||||
process_handlers::{
|
||||
AsahiMuvmLauncher, MacLauncher, UMUCompatLauncher, UMUNativeLauncher, WindowsLauncher,
|
||||
AsahiMuvmLauncher, LinuxNativeLauncher, MacLauncher, UMUCompatLauncher, UMUNativeLauncher,
|
||||
WindowsCmdLauncher, WindowsDirectLauncher, WindowsLauncher, WindowsPowershellLauncher,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -54,6 +55,13 @@ pub struct LaunchOption {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ProcessHandlerOption {
|
||||
id: String,
|
||||
name: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
impl ProcessManager<'_> {
|
||||
pub fn new(app_handle: AppHandle) -> Self {
|
||||
let log_output_dir = DATA_ROOT_DIR.join("logs");
|
||||
@@ -76,6 +84,22 @@ impl ProcessManager<'_> {
|
||||
(Platform::Windows, Platform::Windows),
|
||||
&WindowsLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||
),
|
||||
(
|
||||
(Platform::Windows, Platform::Windows),
|
||||
&WindowsDirectLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||
),
|
||||
(
|
||||
(Platform::Windows, Platform::Windows),
|
||||
&WindowsCmdLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||
),
|
||||
(
|
||||
(Platform::Windows, Platform::Windows),
|
||||
&WindowsPowershellLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||
),
|
||||
(
|
||||
(Platform::Linux, Platform::Linux),
|
||||
&LinuxNativeLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||
),
|
||||
(
|
||||
(Platform::Linux, Platform::Linux),
|
||||
&UMUNativeLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
|
||||
@@ -101,7 +125,7 @@ impl ProcessManager<'_> {
|
||||
match self.processes.get_mut(&game_id) {
|
||||
Some(process) => {
|
||||
process.manually_killed = true;
|
||||
process.handle.kill()?;
|
||||
kill_process_tree(&process.handle)?;
|
||||
let exit_status = process.handle.wait()?;
|
||||
info!("exit status: {:?}", exit_status);
|
||||
Ok(())
|
||||
@@ -188,7 +212,21 @@ impl ProcessManager<'_> {
|
||||
&self,
|
||||
db_lock: &Database,
|
||||
target_platform: &Platform,
|
||||
override_id: Option<&str>,
|
||||
) -> Result<&(dyn ProcessHandler + Send + Sync), ProcessError> {
|
||||
// An explicit override wins, as long as it's valid for the current platform.
|
||||
if let Some(override_id) = override_id
|
||||
&& let Some(handler) = self.game_launchers.iter().find(|e| {
|
||||
let (e_current, e_target) = e.0;
|
||||
e_current == self.current_platform
|
||||
&& e_target == *target_platform
|
||||
&& e.1.id() == override_id
|
||||
&& e.1.valid_for_platform(db_lock, target_platform)
|
||||
})
|
||||
{
|
||||
return Ok(handler.1);
|
||||
}
|
||||
|
||||
Ok(self
|
||||
.game_launchers
|
||||
.iter()
|
||||
@@ -204,10 +242,44 @@ impl ProcessManager<'_> {
|
||||
|
||||
pub fn valid_platform(&self, platform: &Platform) -> bool {
|
||||
let db_lock = borrow_db_checked();
|
||||
let process_handler = self.fetch_process_handler(&db_lock, platform);
|
||||
let process_handler = self.fetch_process_handler(&db_lock, platform, None);
|
||||
process_handler.is_ok()
|
||||
}
|
||||
|
||||
pub fn get_process_handlers(
|
||||
&self,
|
||||
game_id: String,
|
||||
) -> Result<Vec<ProcessHandlerOption>, ProcessError> {
|
||||
let db_lock = borrow_db_checked();
|
||||
|
||||
let meta = db_lock
|
||||
.applications
|
||||
.installed_game_version
|
||||
.get(&game_id)
|
||||
.cloned()
|
||||
.ok_or(ProcessError::NotInstalled)?;
|
||||
|
||||
let target_platform = meta.target_platform;
|
||||
|
||||
let handlers = self
|
||||
.game_launchers
|
||||
.iter()
|
||||
.filter(|e| {
|
||||
let (e_current, e_target) = e.0;
|
||||
e_current == self.current_platform
|
||||
&& e_target == target_platform
|
||||
&& e.1.valid_for_platform(&db_lock, &target_platform)
|
||||
})
|
||||
.map(|e| ProcessHandlerOption {
|
||||
id: e.1.id().to_string(),
|
||||
name: e.1.name().to_string(),
|
||||
description: e.1.description().to_string(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(handlers)
|
||||
}
|
||||
|
||||
pub fn get_launch_options(game_id: String) -> Result<Vec<LaunchOption>, ProcessError> {
|
||||
let db_lock = borrow_db_checked();
|
||||
|
||||
@@ -310,7 +382,12 @@ impl ProcessManager<'_> {
|
||||
|
||||
let target_platform = meta.target_platform;
|
||||
|
||||
let process_handler = self.fetch_process_handler(&db_lock, &target_platform)?;
|
||||
let process_handler = self.fetch_process_handler(
|
||||
&db_lock,
|
||||
&target_platform,
|
||||
game_version.user_configuration.override_handler.as_deref(),
|
||||
)?;
|
||||
debug!("using process handler {:?}", process_handler.id());
|
||||
|
||||
let (target_command, emulator) = match game_status {
|
||||
GameDownloadStatus::Installed {
|
||||
@@ -516,6 +593,30 @@ impl ProcessManager<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn kill_process_tree(handle: &SharedChild) -> io::Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// handle.kill() only terminates the launched process (often a cmd or
|
||||
// powershell wrapper), orphaning the actual game. taskkill /T kills the
|
||||
// whole process tree.
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
let pid = handle.id().to_string();
|
||||
let killed = Command::new("taskkill")
|
||||
.args(["/F", "/T", "/PID", pid.as_str()])
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.status()
|
||||
.map(|status| status.success())
|
||||
.unwrap_or(false);
|
||||
if killed {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
handle.kill()
|
||||
}
|
||||
|
||||
pub trait ProcessHandler: Send + 'static {
|
||||
fn create_launch_process(
|
||||
&self,
|
||||
@@ -529,4 +630,8 @@ pub trait ProcessHandler: Send + 'static {
|
||||
fn valid_for_platform(&self, db: &Database, target: &Platform) -> bool;
|
||||
|
||||
fn modify_command(&self, command: &mut Command);
|
||||
|
||||
fn id(&self) -> &'static str;
|
||||
fn name(&self) -> &'static str;
|
||||
fn description(&self) -> &'static str;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ bytes = "1.11.0"
|
||||
chrono = "0.4.42"
|
||||
client = { path = "../client", version = "0.1.0" }
|
||||
database = { path = "../database", version = "0.1.0" }
|
||||
droplet-rs = "0.7.3"
|
||||
gethostname = "1.0.2"
|
||||
hex = "0.4.3"
|
||||
http = "1.3.1"
|
||||
|
||||
@@ -8,8 +8,17 @@
|
||||
#![deny(clippy::all)]
|
||||
|
||||
use std::{
|
||||
env, fs::File, io::Write, panic::PanicHookInfo, path::Path, str::FromStr,
|
||||
sync::nonpoison::Mutex, time::SystemTime,
|
||||
env,
|
||||
fs::File,
|
||||
io::Write,
|
||||
panic::PanicHookInfo,
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
nonpoison::Mutex,
|
||||
},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use ::client::{
|
||||
@@ -260,6 +269,7 @@ pub fn run() {
|
||||
get_autostart_enabled,
|
||||
open_process_logs,
|
||||
get_launch_options,
|
||||
get_process_handlers,
|
||||
#[cfg(target_os = "linux")]
|
||||
::process::compat::fetch_proton_paths,
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -359,8 +369,17 @@ pub fn run() {
|
||||
)
|
||||
.expect("Failed to generate menu");
|
||||
|
||||
if env::var("NO_TRAY_ICON").is_ok_and(|value| value.to_lowercase() == "true") {
|
||||
TRAY_DISABLED.store(true, Ordering::Relaxed);
|
||||
} else if !tray_icon_supported() {
|
||||
warn!(
|
||||
"appindicator library not available at runtime, disabling system tray icon"
|
||||
);
|
||||
TRAY_DISABLED.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
run_on_tray(|| {
|
||||
TrayIconBuilder::new()
|
||||
let tray = TrayIconBuilder::new()
|
||||
.icon(
|
||||
app.default_window_icon()
|
||||
.expect("Failed to get default window icon")
|
||||
@@ -383,8 +402,12 @@ pub fn run() {
|
||||
warn!("menu event not handled: {:?}", event.id);
|
||||
}
|
||||
})
|
||||
.build(app)
|
||||
.expect("error while setting up tray menu");
|
||||
.build(app);
|
||||
|
||||
if let Err(e) = tray {
|
||||
warn!("failed to set up system tray icon, disabling tray: {e}");
|
||||
TRAY_DISABLED.store(true, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
@@ -445,13 +468,30 @@ pub fn run() {
|
||||
});
|
||||
}
|
||||
|
||||
static TRAY_DISABLED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn tray_icon_supported() -> bool {
|
||||
[
|
||||
"libayatana-appindicator3.so.1",
|
||||
"libappindicator3.so.1",
|
||||
"libayatana-appindicator3.so",
|
||||
"libappindicator3.so",
|
||||
]
|
||||
.iter()
|
||||
.any(|name| unsafe { libloading::Library::new(name) }.is_ok())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn tray_icon_supported() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run_on_tray<T: FnOnce()>(f: T) {
|
||||
if match std::env::var("NO_TRAY_ICON") {
|
||||
Ok(s) => s.to_lowercase() != "true",
|
||||
Err(_) => true,
|
||||
} {
|
||||
(f)();
|
||||
if TRAY_DISABLED.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
(f)();
|
||||
}
|
||||
|
||||
// TODO: Refactor
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use process::{
|
||||
PROCESS_MANAGER,
|
||||
error::ProcessError,
|
||||
process_manager::{LaunchOption, ProcessManager},
|
||||
process_manager::{LaunchOption, ProcessHandlerOption, ProcessManager},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use tauri::AppHandle;
|
||||
@@ -16,6 +16,11 @@ pub fn get_launch_options(id: String) -> Result<Vec<LaunchOption>, ProcessError>
|
||||
Ok(launch_options)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_process_handlers(id: String) -> Result<Vec<ProcessHandlerOption>, ProcessError> {
|
||||
PROCESS_MANAGER.lock().get_process_handlers(id)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "result", content = "data")]
|
||||
pub enum LaunchResult {
|
||||
|
||||
Submodule desktop/src-tauri/tailscale/libtailscale deleted from 78294ac1d6
@@ -27,7 +27,7 @@
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": ["nsis", "deb", "rpm", "dmg", "appimage"],
|
||||
"targets": ["nsis", "deb", "rpm", "dmg"],
|
||||
"windows": {
|
||||
"nsis": {
|
||||
"installMode": "both"
|
||||
|
||||
Generated
-4205
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
- sharp
|
||||
@@ -14,7 +14,7 @@
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint": "latest",
|
||||
"eslint": "^9.17.0",
|
||||
"nuxt": "^3.14.1592",
|
||||
"nuxt": "^3.21.7",
|
||||
"typescript": "^5.7.2",
|
||||
"vue": "latest"
|
||||
},
|
||||
|
||||
Generated
-8465
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
onlyBuiltDependencies:
|
||||
- '@parcel/watcher'
|
||||
- esbuild
|
||||
- unrs-resolver
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
name: Rust CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: Build, Test, Lint
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
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: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Install libarchive
|
||||
run: |
|
||||
sudo apt-get install libarchive-dev -y
|
||||
|
||||
|
||||
- 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
|
||||
@@ -21,6 +21,7 @@ target/
|
||||
/target
|
||||
|
||||
perf.data
|
||||
perf.data.old
|
||||
flamegraph.svg
|
||||
*.json
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "libarchive-rust"]
|
||||
path = libarchive-rust
|
||||
url = https://github.com/Drop-OSS/libarchive-rust.git
|
||||
Generated
+416
-159
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ license = "AGPL-3.0-only"
|
||||
description = "Droplet is a `napi.rs` Rust/Node.js package full of high-performance and low-level utils for Drop"
|
||||
|
||||
[dependencies]
|
||||
droplet_types = { path = "../droplet_types" }
|
||||
hex = "0.4.3"
|
||||
time = "0.3.41"
|
||||
ring = "0.17.14"
|
||||
|
||||
Submodule libraries/droplet/libarchive-rust deleted from fdb73ef2de
@@ -1,13 +1,12 @@
|
||||
#![deny(clippy::all)]
|
||||
#![feature(impl_trait_in_bindings)]
|
||||
|
||||
pub mod file_utils;
|
||||
pub mod manifest;
|
||||
pub mod ssl;
|
||||
pub mod versions;
|
||||
pub mod vm;
|
||||
|
||||
extern crate libarchive_drop;
|
||||
pub use manifest::{CHUNK_SIZE, MAX_FILE_COUNT};
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
use droplet_rs::manifest::generate_manifest_rusty;
|
||||
use droplet_rs::manifest::{generate_manifest_rusty, ManifestWriterFactory};
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
struct SinkFactory {}
|
||||
#[async_trait::async_trait]
|
||||
impl ManifestWriterFactory for SinkFactory {
|
||||
type Writer = tokio::io::Sink;
|
||||
async fn create(&self, _id: String) -> anyhow::Result<Self::Writer> {
|
||||
Ok(tokio::io::sink())
|
||||
}
|
||||
|
||||
async fn close(&self, _writer: Self::Writer) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
let mut args = env::args();
|
||||
@@ -17,6 +30,7 @@ pub async fn main() {
|
||||
|message| {
|
||||
println!("{}", message);
|
||||
},
|
||||
Some(&SinkFactory {}),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
||||
+216
-206
@@ -1,162 +1,171 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
mem,
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use std::{collections::HashMap, ops::Not, path::Path};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
pub use droplet_types::{ChunkData, FileEntry, Manifest};
|
||||
use futures::StreamExt;
|
||||
use hex::ToHex as _;
|
||||
use humansize::{format_size, BINARY};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest as _, Sha256};
|
||||
use tokio::{
|
||||
io::AsyncReadExt as _,
|
||||
join,
|
||||
sync::{Mutex, Semaphore},
|
||||
task::JoinSet,
|
||||
};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::{AsyncReadExt as _, AsyncWrite};
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct FileEntry {
|
||||
pub filename: String,
|
||||
pub start: usize,
|
||||
pub length: usize,
|
||||
pub permissions: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ChunkData {
|
||||
pub files: Vec<FileEntry>,
|
||||
pub checksum: String,
|
||||
pub iv: [u8; 16],
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Manifest {
|
||||
pub version: String,
|
||||
pub chunks: HashMap<String, ChunkData>,
|
||||
pub size: u64,
|
||||
pub key: [u8; 16],
|
||||
}
|
||||
|
||||
const CHUNK_SIZE: u64 = 1024 * 1024 * 64;
|
||||
const MAX_FILE_COUNT: usize = 512;
|
||||
pub const CHUNK_SIZE: u64 = 1024 * 1024 * 64;
|
||||
pub const MAX_FILE_COUNT: usize = 512;
|
||||
|
||||
use crate::versions::{
|
||||
create_backend_constructor,
|
||||
types::{VersionBackend, VersionFile},
|
||||
};
|
||||
|
||||
pub async fn generate_manifest_rusty<T: Fn(String), V: Fn(f32)>(
|
||||
dir: &Path,
|
||||
progress_sfn: V,
|
||||
log_sfn: T,
|
||||
reader_semaphore: Option<Arc<Semaphore>>,
|
||||
) -> anyhow::Result<Manifest> {
|
||||
let backend =
|
||||
create_backend_constructor(dir).ok_or(anyhow!("Could not create backend for path."))?()?;
|
||||
|
||||
let required_single_file = backend.require_whole_files();
|
||||
#[async_trait]
|
||||
pub trait ManifestWriterFactory: Send + Sync {
|
||||
type Writer: AsyncWrite + Unpin;
|
||||
async fn create(&self, id: String) -> anyhow::Result<Self::Writer>;
|
||||
async fn close(&self, writer: Self::Writer) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
pub async fn generate_manifest_rusty<P, LogFn, ProgFn, Writer>(
|
||||
dir: P,
|
||||
progress_sfn: ProgFn,
|
||||
log_sfn: LogFn,
|
||||
factory: Option<&dyn ManifestWriterFactory<Writer = Writer>>,
|
||||
semaphore: Option<&Semaphore>,
|
||||
) -> anyhow::Result<Manifest>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
LogFn: Fn(String) + Clone,
|
||||
ProgFn: Fn(f32),
|
||||
Writer: AsyncWrite + Unpin,
|
||||
{
|
||||
let backend = create_backend_constructor(dir).ok_or(anyhow!(
|
||||
"Could not create backend for path. Is this structure supported?"
|
||||
))?()?;
|
||||
let mut files = backend.list_files().await?;
|
||||
files.sort_by_key(|b| std::cmp::Reverse(b.size));
|
||||
// Filepath to chunk data
|
||||
let mut chunks: Vec<Vec<(VersionFile, u64, u64)>> = Vec::new();
|
||||
let mut current_chunk: Vec<(VersionFile, u64, u64)> = Vec::new();
|
||||
|
||||
log_sfn("organizing files into chunks...".to_string());
|
||||
log_sfn("organising files into chunks...".to_string());
|
||||
|
||||
if required_single_file {
|
||||
for version_file in files {
|
||||
if version_file.size >= CHUNK_SIZE {
|
||||
let size = version_file.size;
|
||||
chunks.push(vec![(version_file, 0, size)]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut current_size = current_chunk.iter().map(|v| v.2).sum::<u64>();
|
||||
|
||||
let size = version_file.size;
|
||||
current_chunk.push((version_file, 0, size));
|
||||
|
||||
current_size += size;
|
||||
|
||||
if current_size >= CHUNK_SIZE {
|
||||
// Pop current and add, then reset
|
||||
let new_chunk = std::mem::take(&mut current_chunk);
|
||||
chunks.push(new_chunk);
|
||||
}
|
||||
|
||||
if current_chunk.len() >= MAX_FILE_COUNT {
|
||||
chunks.push(std::mem::take(&mut current_chunk));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
for version_file in files {
|
||||
if current_chunk.len() >= MAX_FILE_COUNT {
|
||||
chunks.push(std::mem::take(&mut current_chunk));
|
||||
}
|
||||
|
||||
let current_size = current_chunk.iter().map(|v| v.2).sum::<u64>();
|
||||
|
||||
if version_file.size + current_size < CHUNK_SIZE {
|
||||
let size = version_file.size;
|
||||
current_chunk.push((version_file, 0, size));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fill up current chunk
|
||||
let remaining = CHUNK_SIZE - current_size;
|
||||
current_chunk.push((version_file.clone(), 0, remaining));
|
||||
chunks.push(std::mem::take(&mut current_chunk));
|
||||
|
||||
// This is our offset in our current file
|
||||
let mut offset = remaining;
|
||||
while offset < version_file.size {
|
||||
let length = CHUNK_SIZE.min(version_file.size - offset);
|
||||
if length == CHUNK_SIZE {
|
||||
chunks.push(vec![(version_file.clone(), offset, length)]);
|
||||
} else {
|
||||
current_chunk.push((version_file.clone(), offset, length));
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !current_chunk.is_empty() {
|
||||
chunks.push(current_chunk);
|
||||
}
|
||||
let chunks = organise_files(files, backend.require_whole_files());
|
||||
|
||||
log_sfn(format!(
|
||||
"organized into {} chunks, generating checksums...",
|
||||
chunks.len()
|
||||
));
|
||||
let manifest = read_chunks_and_generate_manifest(
|
||||
backend.as_ref(),
|
||||
chunks,
|
||||
progress_sfn,
|
||||
&log_sfn,
|
||||
factory,
|
||||
semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let manifest: Arc<Mutex<HashMap<String, ChunkData>>> = Arc::new(Mutex::new(HashMap::new()));
|
||||
let total_manifest_length = Arc::new(AtomicU64::new(0));
|
||||
let mut key = [0u8; 16];
|
||||
getrandom::fill(&mut key).map_err(|err| anyhow!("failed to generate key: {:?}", err))?;
|
||||
|
||||
// SAFETY: we .join_all() the futures using this
|
||||
let backend: &'static (dyn VersionBackend + Send + Sync) = unsafe { mem::transmute(&*backend) };
|
||||
let total_manifest_length = manifest
|
||||
.values()
|
||||
.map(|value| value.files.iter().map(|f| f.length as u64).sum::<u64>())
|
||||
.sum::<u64>();
|
||||
|
||||
let mut futures: JoinSet<Result<(), anyhow::Error>> = JoinSet::new();
|
||||
let (send_log, mut recieve_log) = tokio::sync::mpsc::channel(16);
|
||||
let chunks_length = chunks.len();
|
||||
for (index, chunk) in chunks.into_iter().enumerate() {
|
||||
let send_log = send_log.clone();
|
||||
let total_manifest_length = total_manifest_length.clone();
|
||||
let manifest = manifest.clone();
|
||||
let reader_semaphore = reader_semaphore.clone();
|
||||
futures.spawn(async move {
|
||||
let mut read_buf = vec![0u8; 1024 * 1024 * 8];
|
||||
Ok(Manifest {
|
||||
version: "2".to_string(),
|
||||
chunks: manifest,
|
||||
size: total_manifest_length,
|
||||
key,
|
||||
})
|
||||
}
|
||||
|
||||
fn organise_files(
|
||||
files: Vec<VersionFile>,
|
||||
require_whole_files: bool,
|
||||
) -> Vec<Vec<(VersionFile, u64, u64)>> {
|
||||
let mut chunks = Vec::new();
|
||||
let mut current_chunk = Vec::new();
|
||||
|
||||
for version_file in files {
|
||||
if current_chunk.len() >= MAX_FILE_COUNT {
|
||||
// Pop current chunk
|
||||
chunks.push(std::mem::take(&mut current_chunk));
|
||||
println!("Chunks: {}", chunks.len());
|
||||
}
|
||||
let current_chunk_size = current_chunk
|
||||
.iter()
|
||||
.map(|(_, _, length)| *length)
|
||||
.sum::<u64>();
|
||||
let version_file_size = version_file.size;
|
||||
|
||||
if require_whole_files {
|
||||
// If the current chunk is larger than chunk size, there's no point adding
|
||||
// it to the current_chunk. Just push it by itself
|
||||
if version_file_size >= CHUNK_SIZE {
|
||||
chunks.push(vec![(version_file, 0, version_file_size)]);
|
||||
println!("Chunks: {}", chunks.len());
|
||||
continue;
|
||||
}
|
||||
|
||||
current_chunk.push((version_file, 0, version_file_size));
|
||||
if current_chunk_size + version_file_size >= CHUNK_SIZE {
|
||||
// Pop current chunk
|
||||
chunks.push(std::mem::take(&mut current_chunk));
|
||||
println!("Chunks: {}", chunks.len());
|
||||
}
|
||||
} else {
|
||||
// Enough space for it to be put in immediately
|
||||
if version_file_size + current_chunk_size < CHUNK_SIZE {
|
||||
current_chunk.push((version_file, 0, version_file_size));
|
||||
continue;
|
||||
}
|
||||
|
||||
let bytes_free_in_existing_chunk = CHUNK_SIZE - current_chunk_size;
|
||||
current_chunk.push((version_file.clone(), 0, bytes_free_in_existing_chunk));
|
||||
chunks.push(std::mem::take(&mut current_chunk));
|
||||
|
||||
// Loop over remaining data and create sufficient chunks to use it
|
||||
let mut offset = bytes_free_in_existing_chunk;
|
||||
while offset < version_file_size {
|
||||
let length = CHUNK_SIZE.min(version_file_size - offset);
|
||||
if length == CHUNK_SIZE {
|
||||
chunks.push(vec![(version_file.clone(), offset, length)]);
|
||||
println!("Chunks: {}", chunks.len());
|
||||
} else {
|
||||
current_chunk.push((version_file.clone(), offset, length));
|
||||
println!("Chunks: {}", chunks.len());
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
if current_chunk.is_empty().not() {
|
||||
chunks.push(current_chunk);
|
||||
println!("Pushed final chunk: {}", chunks.len());
|
||||
}
|
||||
println!("Chunks: {}", chunks.len());
|
||||
chunks
|
||||
}
|
||||
|
||||
async fn read_chunks_and_generate_manifest<LogFn, ProgFn, Writer>(
|
||||
backend: &(dyn VersionBackend + Send + Sync),
|
||||
chunks: Vec<Vec<(VersionFile, u64, u64)>>,
|
||||
progress_sfn: ProgFn,
|
||||
log_sfn: &LogFn,
|
||||
factory: Option<&dyn ManifestWriterFactory<Writer = Writer>>,
|
||||
semaphore: Option<&Semaphore>,
|
||||
) -> anyhow::Result<HashMap<String, ChunkData>>
|
||||
where
|
||||
LogFn: Fn(String),
|
||||
ProgFn: Fn(f32),
|
||||
Writer: AsyncWrite + Unpin,
|
||||
{
|
||||
let total_chunk_count = chunks.len();
|
||||
|
||||
let futures = chunks.into_iter().enumerate().map(|(index, chunk)| {
|
||||
// To make the borrow checker happy
|
||||
async move {
|
||||
let mut read_buf = vec![0; 1024 * 1024 * 64];
|
||||
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
let mut hasher = Sha256::new();
|
||||
@@ -168,91 +177,92 @@ pub async fn generate_manifest_rusty<T: Fn(String), V: Fn(f32)>(
|
||||
checksum: String::new(),
|
||||
iv,
|
||||
};
|
||||
|
||||
let mut chunk_length = 0;
|
||||
|
||||
let mut chunk_length = 0u64;
|
||||
let mut writer = match factory {
|
||||
Some(factory) => Some(factory.create(uuid.clone()).await?),
|
||||
None => None,
|
||||
};
|
||||
for (file, start, length) in chunk {
|
||||
let permit = if let Some(reader_semaphore) = &reader_semaphore {
|
||||
Some(reader_semaphore.acquire().await?)
|
||||
let permit = if let Some(semaphore) = &semaphore {
|
||||
Some(semaphore.acquire().await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut reader = backend.reader(&file, start, start + length).await?;
|
||||
|
||||
let mut total = 0;
|
||||
|
||||
loop {
|
||||
let amount = reader.read(&mut read_buf).await?;
|
||||
if amount == 0 {
|
||||
break;
|
||||
}
|
||||
total += amount;
|
||||
hasher.update(&read_buf[0..amount]);
|
||||
}
|
||||
|
||||
if total as u64 > length {
|
||||
panic!("read too much: target {}, got {}", length, total);
|
||||
}
|
||||
|
||||
chunk_data.files.push(
|
||||
read_and_generate_chunk_file_data(
|
||||
backend,
|
||||
&file,
|
||||
start,
|
||||
length,
|
||||
&mut hasher,
|
||||
&mut read_buf,
|
||||
&mut writer,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
chunk_length += length;
|
||||
|
||||
chunk_data.files.push(FileEntry {
|
||||
filename: file.relative_filename,
|
||||
start: start.try_into().unwrap(),
|
||||
length: length.try_into().unwrap(),
|
||||
permissions: file.permission,
|
||||
});
|
||||
|
||||
drop(permit);
|
||||
}
|
||||
|
||||
send_log
|
||||
.send(format!(
|
||||
"created chunk of size {} ({}b) from {} files (index {})",
|
||||
format_size(chunk_length, BINARY),
|
||||
chunk_length,
|
||||
chunk_data.files.len(),
|
||||
index
|
||||
))
|
||||
.await?;
|
||||
|
||||
total_manifest_length.fetch_add(chunk_length, Ordering::Relaxed);
|
||||
|
||||
if let Some(factory) = factory {
|
||||
factory.close(writer.expect("Failed to get writer")).await?;
|
||||
}
|
||||
let hash: String = hasher.finalize().encode_hex();
|
||||
chunk_data.checksum = hash;
|
||||
{
|
||||
let mut manifest_lock = manifest.lock().await;
|
||||
manifest_lock.insert(uuid, chunk_data);
|
||||
};
|
||||
|
||||
Ok(())
|
||||
});
|
||||
log_sfn(format!(
|
||||
"created chunk of size {} ({}b) from {} files (index {})",
|
||||
format_size(chunk_length, BINARY),
|
||||
chunk_length,
|
||||
chunk_data.files.len(),
|
||||
index
|
||||
));
|
||||
|
||||
Ok::<_, anyhow::Error>((uuid, chunk_data))
|
||||
}
|
||||
});
|
||||
let mut stream = futures::stream::iter(futures)
|
||||
.buffer_unordered(semaphore.map(|s| s.available_permits()).unwrap_or(4))
|
||||
.enumerate();
|
||||
let mut results = HashMap::new();
|
||||
let mut current_progress = 0f32;
|
||||
while let Some((_, res)) = stream.next().await {
|
||||
let (id, data) = res?;
|
||||
current_progress += 1.0;
|
||||
progress_sfn((current_progress / total_chunk_count as f32) * 100.0f32);
|
||||
results.insert(id, data);
|
||||
}
|
||||
drop(send_log);
|
||||
join!(
|
||||
async move {
|
||||
let mut current_progress = 0f32;
|
||||
let total_progress = chunks_length as f32;
|
||||
while let Some(message) = recieve_log.recv().await {
|
||||
log_sfn(message);
|
||||
current_progress += 1.0f32;
|
||||
progress_sfn((current_progress / total_progress) * 100.0f32)
|
||||
}
|
||||
},
|
||||
futures.join_all()
|
||||
);
|
||||
Ok(results)
|
||||
}
|
||||
async fn read_and_generate_chunk_file_data<Writer>(
|
||||
backend: &(dyn VersionBackend + Sync + Send),
|
||||
file: &VersionFile,
|
||||
start: u64,
|
||||
length: u64,
|
||||
hasher: &mut Sha256,
|
||||
read_buf: &mut [u8],
|
||||
writer: &mut Option<Writer>,
|
||||
) -> anyhow::Result<FileEntry>
|
||||
where
|
||||
Writer: AsyncWrite + Unpin,
|
||||
{
|
||||
let mut reader = backend.reader(file, start, start + length).await?;
|
||||
|
||||
let manifest = manifest.lock().await;
|
||||
let manifest = manifest.clone();
|
||||
loop {
|
||||
let amount = reader.read(read_buf).await?;
|
||||
|
||||
let mut key = [0u8; 16];
|
||||
getrandom::fill(&mut key).map_err(|err| anyhow!("failed to generate key: {:?}", err))?;
|
||||
if amount == 0 {
|
||||
break;
|
||||
}
|
||||
if let Some(writer) = writer.as_mut() {
|
||||
writer.write_all(&read_buf[0..amount]).await?;
|
||||
}
|
||||
hasher.update(&read_buf[0..amount]);
|
||||
}
|
||||
|
||||
Ok(Manifest {
|
||||
version: "2".to_string(),
|
||||
chunks: manifest,
|
||||
size: total_manifest_length.fetch_add(0, Ordering::Relaxed),
|
||||
key,
|
||||
Ok(FileEntry {
|
||||
filename: file.relative_filename.clone(),
|
||||
start: start.try_into().unwrap(),
|
||||
length: length.try_into().unwrap(),
|
||||
permissions: file.permission,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ extern crate test_generator;
|
||||
use std::path::Path;
|
||||
|
||||
use test_generator::test_resources;
|
||||
use tokio::io::SimplexStream;
|
||||
|
||||
use crate::manifest::generate_manifest_rusty;
|
||||
use crate::manifest::{generate_manifest_rusty, ManifestWriterFactory};
|
||||
|
||||
#[test_resources("testfiles/**/*.7z")]
|
||||
fn manifest_gen(resource: &str) {
|
||||
@@ -22,6 +23,7 @@ fn manifest_gen(resource: &str) {
|
||||
|message| {
|
||||
println!("({}) {}", filepath.display(), message);
|
||||
},
|
||||
None::<&dyn ManifestWriterFactory<Writer = SimplexStream>>, // Dummy type signature, not actually used
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -28,12 +28,12 @@ impl ZipVersionBackend {
|
||||
}
|
||||
}
|
||||
|
||||
struct ArchiveReader<'a> {
|
||||
struct ArchiveReader {
|
||||
archive: FileReader,
|
||||
prev_block: Option<&'a [u8]>,
|
||||
prev_block: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<'a> AsyncRead for ArchiveReader<'a> {
|
||||
impl AsyncRead for ArchiveReader {
|
||||
fn poll_read(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
@@ -41,9 +41,8 @@ impl<'a> AsyncRead for ArchiveReader<'a> {
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
if let Some(block) = &mut self.prev_block {
|
||||
let to_read = buf.remaining().min(block.len());
|
||||
let result = block.split_off(..to_read);
|
||||
let result = result.unwrap(); // SAFETY: above .min statement
|
||||
buf.put_slice(result);
|
||||
let result = block.split_off(to_read);
|
||||
buf.put_slice(&result);
|
||||
|
||||
// If the block is empty, we can read more
|
||||
if block.is_empty() {
|
||||
@@ -52,27 +51,31 @@ impl<'a> AsyncRead for ArchiveReader<'a> {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
}
|
||||
let block = match self.archive.read_block() {
|
||||
Ok(v) => v,
|
||||
Err(err) => return Poll::Ready(Err(std::io::Error::other(err.to_string()))),
|
||||
};
|
||||
let prev_block_update = {
|
||||
let block = match self.archive.read_block() {
|
||||
Ok(v) => v,
|
||||
Err(err) => return Poll::Ready(Err(std::io::Error::other(err.to_string()))),
|
||||
};
|
||||
|
||||
let mut block = match block {
|
||||
Some(v) => v,
|
||||
None => return Poll::Ready(Ok(())),
|
||||
};
|
||||
let mut block = match block {
|
||||
Some(v) => v,
|
||||
None => return Poll::Ready(Ok(())),
|
||||
};
|
||||
|
||||
let write_amount = buf.remaining().min(block.len());
|
||||
let to_write = block.split_off(..write_amount);
|
||||
let to_write = to_write.unwrap(); // SAFETY: above .min statement
|
||||
buf.put_slice(to_write);
|
||||
let write_amount = buf.remaining().min(block.len());
|
||||
let to_write = block.split_off(..write_amount);
|
||||
let to_write = to_write.unwrap(); // SAFETY: above .min statement
|
||||
buf.put_slice(to_write);
|
||||
|
||||
if !block.is_empty() {
|
||||
#[cfg(debug_assertions)]
|
||||
if self.prev_block.is_some() {
|
||||
panic!("replacing prev_block while it contains data")
|
||||
if !block.is_empty() {
|
||||
Some(block[buf.remaining()..].to_vec())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.prev_block.replace(&block[buf.remaining()..]);
|
||||
};
|
||||
|
||||
if let Some(prev_block) = prev_block_update {
|
||||
self.prev_block.replace(prev_block);
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
|
||||
@@ -5,10 +5,11 @@ use std::{
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::versions::{
|
||||
archive_backend::ZipVersionBackend, path_backend::PathVersionBackend, types::VersionBackend,
|
||||
};
|
||||
use crate::versions::{path_backend::PathVersionBackend, types::VersionBackend};
|
||||
|
||||
use crate::versions::archive_backend::ZipVersionBackend;
|
||||
|
||||
// libarchive backend is Linux-only for now
|
||||
pub mod archive_backend;
|
||||
pub mod path_backend;
|
||||
|
||||
@@ -33,10 +34,15 @@ const SUPPORTED_FILE_EXTENSIONS: [&str; 11] = [
|
||||
];
|
||||
|
||||
pub mod types;
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn create_backend_constructor<'a>(
|
||||
path: &Path,
|
||||
) -> Option<Box<dyn FnOnce() -> Result<Box<dyn VersionBackend + Send + Sync + 'a>>>> {
|
||||
|
||||
type BackendConstructor<'a> =
|
||||
Box<dyn FnOnce() -> Result<Box<dyn VersionBackend + Send + Sync + 'a>>>;
|
||||
|
||||
pub fn create_backend_constructor<'a, P>(path: P) -> Option<BackendConstructor<'a>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let path = path.as_ref();
|
||||
if !path.exists() {
|
||||
return None;
|
||||
}
|
||||
@@ -50,7 +56,6 @@ pub fn create_backend_constructor<'a>(
|
||||
};
|
||||
|
||||
let file_extension = path.extension().and_then(|v| v.to_str())?;
|
||||
|
||||
if SUPPORTED_FILE_EXTENSIONS.contains(&file_extension) {
|
||||
let buf = path.to_path_buf();
|
||||
return Some(Box::new(move || Ok(Box::new(ZipVersionBackend::new(buf)?))));
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
|
||||
perf.data
|
||||
perf.data.old
|
||||
flamegraph.svg
|
||||
*.json
|
||||
|
||||
.direnv
|
||||
Generated
+75
@@ -0,0 +1,75 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "droplet_types"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[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",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "droplet_types"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "*", features = ["derive"] }
|
||||
@@ -0,0 +1,6 @@
|
||||
# droplet_types
|
||||
|
||||
Shared types between the cross-platform client and the droplet-rs crate.
|
||||
|
||||
|
||||
Split off from droplet-rs because of cross-compiling issues with the desktop client, and there's no need to compile the entirety of droplet-rs if we're only using a few types.
|
||||
@@ -0,0 +1,26 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct FileEntry {
|
||||
pub filename: String,
|
||||
pub start: usize,
|
||||
pub length: usize, // TODO: Replace with u64 for 32 bit clients
|
||||
pub permissions: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ChunkData {
|
||||
pub files: Vec<FileEntry>,
|
||||
pub checksum: String,
|
||||
pub iv: [u8; 16],
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Manifest {
|
||||
pub version: String,
|
||||
pub chunks: HashMap<String, ChunkData>,
|
||||
pub size: u64,
|
||||
pub key: [u8; 16],
|
||||
}
|
||||
@@ -3,10 +3,10 @@ name = "libarchive-drop"
|
||||
version = "0.1.1"
|
||||
authors = ["Jamie Winsor <reset@chef.io>", "Drop OSS"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/Drop-OSS/libarchive-rust"
|
||||
repository = "https://lab.droposs.org/drop-oss/drop/-/tree/develop/libraries/libarchive"
|
||||
description = "A safe Rust API for authoring and extracting archives with libarchive"
|
||||
keywords = ["libarchive", "archive", "tar", "zip"]
|
||||
|
||||
[dependencies]
|
||||
libc = ">= 0.2.0"
|
||||
libarchive3-sys = "0.1"
|
||||
libarchive3-sys = "0.1.2"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
extern crate libarchive;
|
||||
extern crate libarchive_drop;
|
||||
|
||||
pub mod util;
|
||||
|
||||
use libarchive::archive::{self, ReadFilter, ReadFormat};
|
||||
use libarchive::reader::{self, Reader};
|
||||
use libarchive::writer;
|
||||
use libarchive_drop::archive::{self, ReadFilter, ReadFormat};
|
||||
use libarchive_drop::reader::{self, Reader};
|
||||
use libarchive_drop::writer;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "drop",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
|
||||
}
|
||||
Generated
+24833
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,72 @@
|
||||
packages:
|
||||
- "server/"
|
||||
- "libraries/base/"
|
||||
- "sites/*"
|
||||
- "desktop/"
|
||||
|
||||
onlyBuiltDependencies:
|
||||
- "@bufbuild/buf"
|
||||
- "@parcel/watcher"
|
||||
- "@prisma/client"
|
||||
- "@prisma/engines"
|
||||
- "@tailwindcss/oxide"
|
||||
- argon2
|
||||
- esbuild
|
||||
- prisma
|
||||
- sharp
|
||||
- unrs-resolver
|
||||
- contentlayer
|
||||
- contentlayer2
|
||||
- protobufjs
|
||||
|
||||
overrides:
|
||||
"@isaacs/brace-expansion@<=5.0.0": ">=5.0.1"
|
||||
ajv@<6.14.0: ">=6.14.0"
|
||||
devalue@<=5.6.2: ">=5.6.3"
|
||||
devalue@>=5.1.0 <5.6.2: ">=5.6.2"
|
||||
devalue@>=5.3.0 <=5.6.1: ">=5.6.2"
|
||||
diff@>=6.0.0 <8.0.3: ">=8.0.3"
|
||||
hono@<4.11.10: ">=4.11.10"
|
||||
hono@<4.11.7: ">=4.11.7"
|
||||
lodash-es@>=4.0.0 <=4.17.22: ">=4.17.23"
|
||||
lodash@>=4.0.0 <=4.17.22: ">=4.17.23"
|
||||
minimatch@<3.1.3: ">=3.1.3"
|
||||
minimatch@<3.1.4: ">=3.1.4"
|
||||
minimatch@>=10.0.0 <10.2.1: ">=10.2.1"
|
||||
minimatch@>=10.0.0 <10.2.3: ">=10.2.3"
|
||||
minimatch@>=5.0.0 <5.1.7: ">=5.1.7"
|
||||
minimatch@>=5.0.0 <5.1.8: ">=5.1.8"
|
||||
minimatch@>=9.0.0 <9.0.6: ">=9.0.6"
|
||||
minimatch@>=9.0.0 <9.0.7: ">=9.0.7"
|
||||
node-forge@<1.3.2: ">=1.3.2"
|
||||
qs@<6.14.1: ">=6.14.1"
|
||||
qs@>=6.7.0 <=6.14.1: ">=6.14.2"
|
||||
rollup@>=4.0.0 <4.59.0: ">=4.59.0"
|
||||
serialize-javascript@<=7.0.2: ">=7.0.3"
|
||||
seroval@<1.4.1: ">=1.4.1"
|
||||
seroval@<=1.4.0: ">=1.4.1"
|
||||
tar@<7.5.7: ">=7.5.7"
|
||||
tar@<7.5.8: ">=7.5.8"
|
||||
tar@<=7.5.2: ">=7.5.3"
|
||||
tar@<=7.5.3: ">=7.5.4"
|
||||
undici@>=7.0.0 <7.18.2: ">=7.18.2"
|
||||
|
||||
cross-spawn@<6.0.6: ">=6.0.6"
|
||||
cross-spawn@>=7.0.0 <7.0.5: ">=7.0.5"
|
||||
form-data@<2.5.4: ">=2.5.4"
|
||||
got@<11.8.5: ">=11.8.5"
|
||||
http-cache-semantics@<4.1.1: ">=4.1.1"
|
||||
lodash@<4.17.21: ">=4.17.21"
|
||||
lodash@>=4.0.0 <4.17.21: ">=4.17.21"
|
||||
minimist@>=1.0.0 <1.2.6: ">=1.2.6"
|
||||
nth-check@<2.0.1: ">=2.0.1"
|
||||
semver-regex@<3.1.3: ">=3.1.3"
|
||||
semver-regex@<3.1.4: ">=3.1.4"
|
||||
semver@>=7.0.0 <7.5.2: ">=7.5.2"
|
||||
sharp@<0.30.5: ">=0.30.5"
|
||||
sharp@<0.32.6: ">=0.32.6"
|
||||
tmp@<=0.2.3: ">=0.2.4"
|
||||
tough-cookie@<4.1.3: ">=4.1.3"
|
||||
trim-newlines@<3.0.1: ">=3.0.1"
|
||||
|
||||
shamefullyHoist: true
|
||||
Vendored
-80
@@ -1,80 +0,0 @@
|
||||
name: Deploy Next.js site to Pages
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the main branch
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
# 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, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Setup Pages
|
||||
id: setup_pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
.next/cache
|
||||
# Generate a new cache whenever packages or source files change.
|
||||
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[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 with Next.js
|
||||
run: pnpm run build
|
||||
env:
|
||||
PAGES_BASE_PATH: ${{ steps.setup_pages.outputs.base_path }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./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
|
||||
Generated
-5985
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
onlyBuiltDependencies:
|
||||
- '@tailwindcss/oxide'
|
||||
- contentlayer
|
||||
- contentlayer2
|
||||
- esbuild
|
||||
- protobufjs
|
||||
- sharp
|
||||
- unrs-resolver
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.4 MiB |
@@ -1,143 +0,0 @@
|
||||
import { BentoCard } from '@/components/bento-card'
|
||||
import { Button } from '@/components/button'
|
||||
import { Container } from '@/components/container'
|
||||
import { Footer } from '@/components/footer'
|
||||
import { Gradient } from '@/components/gradient'
|
||||
import { LogoCluster } from '@/components/logo-cluster'
|
||||
import { Navbar } from '@/components/navbar'
|
||||
import { Screenshot } from '@/components/screenshot'
|
||||
import { Heading, Subheading } from '@/components/text'
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
description:
|
||||
'Drop is an open-source, self-hostabled alternative to platforms like Steam and Epic Games.',
|
||||
}
|
||||
|
||||
function Hero() {
|
||||
return (
|
||||
<div className="relative">
|
||||
<Gradient className="absolute inset-2 bottom-0 rounded-4xl ring-1 ring-black/5 ring-inset" />
|
||||
<Container className="relative">
|
||||
<Navbar />
|
||||
<div className="pt-16 pb-24 sm:pt-24 sm:pb-32 md:pt-32 md:pb-48">
|
||||
<h1 className="font-display text-6xl/[0.9] font-medium tracking-tight text-balance text-gray-950 sm:text-8xl/[0.8] md:text-9xl/[0.8]">
|
||||
An open Steam.
|
||||
</h1>
|
||||
<p className="mt-8 max-w-lg text-xl/7 font-medium text-gray-950/75 sm:text-2xl/8">
|
||||
Drop is an open-source, self-hosted alternative to platforms like
|
||||
Steam and Epic.
|
||||
</p>
|
||||
<div className="mt-12 flex flex-col gap-x-6 gap-y-4 sm:flex-row">
|
||||
<Button href="https://docs.droposs.org/docs/guides/quickstart">Get started</Button>
|
||||
<Button variant="secondary" href="/about">
|
||||
About
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FeatureSection() {
|
||||
return (
|
||||
<div className="overflow-hidden">
|
||||
<Container className="pb-24">
|
||||
<Heading as="h2" className="max-w-3xl">
|
||||
A better experience for DRM‑free games.
|
||||
</Heading>
|
||||
<Screenshot
|
||||
width={3408}
|
||||
height={1846}
|
||||
src="/screenshots/app.webp"
|
||||
className="mt-16 h-144 sm:h-auto sm:w-304"
|
||||
/>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BentoSection() {
|
||||
return (
|
||||
<Container>
|
||||
<Subheading>Features</Subheading>
|
||||
<Heading as="h3" className="mt-2 max-w-3xl">
|
||||
Upgrade your games library.
|
||||
</Heading>
|
||||
|
||||
<div className="mt-10 grid grid-cols-1 gap-4 sm:mt-16 lg:grid-cols-6 lg:grid-rows-2">
|
||||
<BentoCard
|
||||
eyebrow="Metadata"
|
||||
title="Rich metadata editing"
|
||||
description="Drop has a rich metadata editor - you can use Markdown, images, and update icons, descriptions, and names."
|
||||
graphic={
|
||||
<div className="flex h-full w-full items-center justify-center p-4">
|
||||
<div className="bg-position-center h-full w-full grow rounded-lg bg-[url(/screenshots/metadata.webp)] bg-cover bg-no-repeat" />
|
||||
</div>
|
||||
}
|
||||
fade={['bottom']}
|
||||
className="max-lg:rounded-t-4xl lg:col-span-3 lg:rounded-tl-4xl"
|
||||
/>
|
||||
<BentoCard
|
||||
eyebrow="Store"
|
||||
title="Let your users discover games"
|
||||
description="Drop has a fully featured store, where your users can discover and filter your game library, and create collections of their favourite games."
|
||||
graphic={
|
||||
<div className="flex h-full w-full items-center justify-center p-4">
|
||||
<div className="bg-position-center h-full w-full grow rounded-lg bg-[url(/screenshots/storepage.png)] bg-cover bg-no-repeat" />
|
||||
</div>
|
||||
}
|
||||
fade={['bottom']}
|
||||
className="lg:col-span-3 lg:rounded-tr-4xl"
|
||||
/>
|
||||
<BentoCard
|
||||
eyebrow="Authentication"
|
||||
title="Flexible authentication"
|
||||
description="Drop supports both simple and SSO authentication, with more features like SCIM on the way."
|
||||
graphic={
|
||||
<div className="flex h-full w-full items-center justify-center p-4">
|
||||
<div className="bg-position-center h-full w-full grow rounded-lg bg-[url(/screenshots/authentication.png)] bg-cover bg-no-repeat" />
|
||||
</div>
|
||||
}
|
||||
fade={['bottom']}
|
||||
className="lg:col-span-2 lg:rounded-bl-4xl"
|
||||
/>
|
||||
<BentoCard
|
||||
eyebrow="Metadata"
|
||||
title="Automatically import metadata"
|
||||
description="Drop can import metadata for your games from platforms like IGDB, GiantBomb, and PCGamingWiki."
|
||||
graphic={<LogoCluster />}
|
||||
className="lg:col-span-2"
|
||||
/>
|
||||
<BentoCard
|
||||
eyebrow="News"
|
||||
title="Keep your users up-to-date with server news"
|
||||
description="Admins can write news articles that appear in-browser and client, to keep users up-to-date."
|
||||
fade={['bottom']}
|
||||
graphic={
|
||||
<div className="flex h-full w-full items-center justify-center p-4">
|
||||
<div className="bg-position-center h-full w-full grow rounded-lg bg-[url(/screenshots/news.png)] bg-cover bg-no-repeat" />
|
||||
</div>
|
||||
}
|
||||
className="max-lg:rounded-b-4xl lg:col-span-2 lg:rounded-br-4xl"
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="overflow-hidden">
|
||||
<Hero />
|
||||
<main>
|
||||
<div className="bg-linear-to-b from-white from-50% to-gray-100 py-32">
|
||||
<FeatureSection />
|
||||
<BentoSection />
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "server"
|
||||
},
|
||||
{
|
||||
"path": "torrential"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"i18n-ally.extract.autoDetect": true,
|
||||
"i18n-ally.extract.ignored": [
|
||||
"string >= 14",
|
||||
"string.alphanumeric >= 5"
|
||||
],
|
||||
"i18n-ally.extract.ignoredByFiles": {
|
||||
"components/NewsArticleCreateButton.vue": [
|
||||
"[",
|
||||
"`",
|
||||
"Enter"
|
||||
],
|
||||
"pages/admin/library/sources/index.vue": [
|
||||
"Filesystem"
|
||||
],
|
||||
"server/api/v1/auth/signin/simple.post.ts": [
|
||||
"boolean | undefined"
|
||||
]
|
||||
},
|
||||
"i18n-ally.keepFulfilled": true,
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"prisma.pinToPrisma6": false,
|
||||
"typescript.experimental.useTsgo": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
|
||||
{
|
||||
"containerEnv": {
|
||||
"DATABASE_URL": "postgres://drop:drop@db:5432/drop",
|
||||
"EXTERNAL_URL": "http://localhost:4000",
|
||||
"NUXT_PORT": "4000"
|
||||
},
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"lokalise.i18n-ally",
|
||||
"esbenp.prettier-vscode",
|
||||
"Prisma.prisma",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"Vue.volar",
|
||||
"arktypeio.arkdark",
|
||||
"EditorConfig.EditorConfig",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-extra/features/protoc:1": {},
|
||||
"ghcr.io/devcontainers/features/node:1": {},
|
||||
"ghcr.io/devcontainers/features/rust:1": {
|
||||
"version": "nightly-2026-03-29"
|
||||
}
|
||||
},
|
||||
|
||||
"name": "Node.js",
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "git submodule update --init --recursive",
|
||||
"postStartCommand": "pnpm install && pnpm prisma migrate deploy && cd torrential && cargo build --release && cd ..",
|
||||
|
||||
"runServices": ["db", "app"],
|
||||
"service": "app",
|
||||
"shutdownAction": "stopCompose",
|
||||
"workspaceFolder": "/workspaces/drop"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
services:
|
||||
app:
|
||||
image: mcr.microsoft.com/devcontainers/base:noble
|
||||
command: sleep infinity
|
||||
volumes:
|
||||
- ..:/workspaces/drop:cached
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
db:
|
||||
image: postgres:14-alpine
|
||||
environment:
|
||||
POSTGRES_USER: drop
|
||||
POSTGRES_PASSWORD: drop
|
||||
POSTGRES_DB: drop
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U drop -d drop"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
@@ -1,75 +0,0 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM node:lts-alpine AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
WORKDIR /app
|
||||
|
||||
## so corepack knows pnpm's version
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
## 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
|
||||
FROM rustlang/rust:nightly-alpine AS torrential-build
|
||||
RUN apk add musl-dev
|
||||
WORKDIR /build
|
||||
COPY torrential .
|
||||
RUN apk add protoc
|
||||
RUN cargo build --release
|
||||
|
||||
### BUILD APP
|
||||
FROM base AS build-system
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NUXT_TELEMETRY_DISABLED=1
|
||||
|
||||
## add git so drop can determine its git ref at build
|
||||
RUN apk add --no-cache git
|
||||
|
||||
## copy deps and rest of project files
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
ARG BUILD_DROP_VERSION
|
||||
ARG BUILD_GIT_REF
|
||||
|
||||
## build
|
||||
RUN pnpm run postinstall && pnpm run build
|
||||
|
||||
|
||||
# create run environment for Drop
|
||||
FROM base AS run-system
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NUXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# 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 7zip nginx
|
||||
RUN pnpm install prisma@7.3.0
|
||||
# init prisma to download all required files
|
||||
RUN pnpm prisma init
|
||||
|
||||
COPY --from=build-system /app/prisma.config.ts ./
|
||||
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/build/nginx.conf /nginx.conf
|
||||
COPY --from=torrential-build /build/target/release/torrential /usr/bin/
|
||||
|
||||
ENV LIBRARY="/library"
|
||||
ENV DATA="/data"
|
||||
ENV NGINX_CONFIG="/nginx.conf"
|
||||
# NGINX's port
|
||||
ENV PORT=4000
|
||||
|
||||
CMD ["sh", "/app/startup/launch.sh"]
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# Server
|
||||
|
||||
The hosted, accessible portion of Drop. Exposes a web UI and API for applications to use.
|
||||
The hosted, accessible portion of Drop. Exposes a web UI and API for applications to use.
|
||||
|
||||
+1
-1
@@ -5,4 +5,4 @@ plugins:
|
||||
opt: target=ts
|
||||
|
||||
inputs:
|
||||
- directory: ../
|
||||
- directory: ../
|
||||
|
||||
@@ -21,6 +21,8 @@ http {
|
||||
scgi_temp_path scgi_temp;
|
||||
uwsgi_temp_path uwsgi_temp;
|
||||
|
||||
proxy_buffering off;
|
||||
|
||||
server {
|
||||
listen 3000;
|
||||
server_name localhost;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</p>
|
||||
<NuxtLink
|
||||
class="mt-4 rounded-md inline-flex items-center text-sm font-semibold text-blue-500 hover:text-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
|
||||
href="https://docs.droposs.org/docs/authentication/simple"
|
||||
href="https://droposs.org/docs/admin/authentication/simple"
|
||||
target="_blank"
|
||||
>
|
||||
<i18n-t
|
||||
@@ -74,7 +74,7 @@
|
||||
</p>
|
||||
<NuxtLink
|
||||
class="mt-4 rounded-md inline-flex items-center text-sm font-semibold text-blue-500 hover:text-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
|
||||
href="https://docs.droposs.org/docs/authentication/oidc"
|
||||
href="https://droposs.org/docs/admin/authentication/oidc"
|
||||
target="_blank"
|
||||
>
|
||||
<i18n-t
|
||||
|
||||
@@ -174,13 +174,14 @@ const optionsMetadata: {
|
||||
Filesystem: {
|
||||
title: t("library.admin.sources.fsTitle"),
|
||||
description: t("library.admin.sources.fsDesc"),
|
||||
docsLink: "https://docs.droposs.org/docs/library#drop-style",
|
||||
docsLink: "https://droposs.org/docs/reference/library-sources#drop-style",
|
||||
icon: DropLogo,
|
||||
},
|
||||
FlatFilesystem: {
|
||||
title: t("library.admin.sources.fsFlatTitle"),
|
||||
description: t("library.admin.sources.fsFlatDesc"),
|
||||
docsLink: "https://docs.droposs.org/docs/library#flat-style-or-compat",
|
||||
docsLink:
|
||||
"https://droposs.org/docs/reference/library-sources#compatibility-flat-style",
|
||||
icon: BackwardIcon,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -134,11 +134,11 @@ const navigation = computed(() => ({
|
||||
// { name: t("footer.api"), href: "https://api.droposs.org/" },
|
||||
{
|
||||
name: t("footer.docs.server"),
|
||||
href: "https://docs.droposs.org/docs/guides/quickstart",
|
||||
href: "https://droposs.org/docs/admin/quickstart",
|
||||
},
|
||||
{
|
||||
name: t("footer.docs.client"),
|
||||
href: "https://docs.droposs.org/docs/guides/client",
|
||||
href: "https://droposs.org/docs/user",
|
||||
},
|
||||
],
|
||||
about: [
|
||||
|
||||
Submodule server/drop-base deleted from dad3487be6
@@ -11,7 +11,9 @@ export default withNuxt([
|
||||
eslintConfigPrettier,
|
||||
|
||||
// vue-i18n plugin
|
||||
// @ts-expect-error
|
||||
...vueI18n.configs.recommended,
|
||||
// @ts-expect-error
|
||||
{
|
||||
rules: {
|
||||
// Optional.
|
||||
@@ -34,6 +36,9 @@ export default withNuxt([
|
||||
messageSyntaxVersion: "^11.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
{
|
||||
plugins: {
|
||||
drop: { rules: { "no-prisma-delete": noPrismaDelete } },
|
||||
},
|
||||
|
||||
+8
-9
@@ -29,8 +29,8 @@
|
||||
"@nuxt/image": "^1.10.0",
|
||||
"@nuxt/kit": "^3.20.1",
|
||||
"@nuxtjs/i18n": "^9.5.5",
|
||||
"@prisma/adapter-pg": "^7.3.0",
|
||||
"@prisma/client": "^7.3.0",
|
||||
"@prisma/adapter-pg": "7.7.0",
|
||||
"@prisma/client": "7.7.0",
|
||||
"@simplewebauthn/browser": "^13.2.2",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@tailwindcss/vite": "^4.0.6",
|
||||
@@ -52,13 +52,13 @@
|
||||
"luxon": "^3.6.1",
|
||||
"micromark": "^4.0.1",
|
||||
"normalize-url": "^8.0.2",
|
||||
"nuxt": "^3.20.1",
|
||||
"nuxt": "^3.21.7",
|
||||
"nuxt-security": "2.2.0",
|
||||
"otp-io": "^1.2.7",
|
||||
"parse-cosekey": "^1.0.2",
|
||||
"pino": "^9.7.0",
|
||||
"pino-pretty": "^13.0.0",
|
||||
"prisma": "7.3.0",
|
||||
"pino": "^9.14.0",
|
||||
"pino-pretty": "^13.1.1",
|
||||
"prisma": "7.7.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"semver": "^7.7.1",
|
||||
"shescape": "^2.1.10",
|
||||
@@ -90,7 +90,7 @@
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"golar": "^0.0.13",
|
||||
"h3": "^1.15.5",
|
||||
"nitropack": "^2.11.12",
|
||||
"nitropack": "^2.13.4",
|
||||
"ofetch": "^1.4.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
@@ -103,6 +103,5 @@
|
||||
"vue3-carousel-nuxt": {
|
||||
"vue3-carousel": "^0.16.0"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.29.1+sha512.48dae233635a645768a3028d19545cacc1688639eeb1f3734e42d6d6b971afbf22aa1ac9af52a173d9c3a20c15857cfa400f19994d79a2f626fcc73fccda9bbc"
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user