Compare commits

..

6 Commits

Author SHA1 Message Date
DecDuck 107ee40f43 Merge branch 'develop' into v0.4.0 2026-03-30 22:22:56 +11:00
DecDuck ee2e3e5e32 Merge remote-tracking branch 'server/libarchive-backend' into v0.4.0 2026-03-30 19:19:54 +11:00
DecDuck 9ce40297ea Merge remote-tracking branch 'promo/v0.4.0' into v0.4.0 2026-03-30 19:15:58 +11:00
DecDuck 6a9d74ef84 feat: move to libarchive backend 2026-03-01 22:57:22 +11:00
DecDuck 581198f9e6 fix: vulns 2026-03-01 22:11:57 +11:00
DecDuck 1a6b11df68 feat: dark mode 2026-02-18 15:23:24 +11:00
272 changed files with 41610 additions and 29272 deletions
-6
View File
@@ -1,6 +0,0 @@
/sites
/cli
/desktop
/backend # go backend
node_modules
-56
View File
@@ -1,56 +0,0 @@
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
-100
View File
@@ -1,100 +0,0 @@
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
-2
View File
@@ -1,2 +0,0 @@
dist/
node_modules/
-105
View File
@@ -1,105 +0,0 @@
# 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"]
+2 -2
View File
@@ -6,7 +6,7 @@
# Drop
[![Website](https://img.shields.io/badge/website-000000?style=for-the-badge&logo=About.me&logoColor=white)](https://droposs.org)
[![Docs](https://img.shields.io/badge/DOCS-black?style=for-the-badge&logo=docusaurus)](https://droposs.org/docs)
[![Docs](https://img.shields.io/badge/DOCS-black?style=for-the-badge&logo=docusaurus)](https://docs.droposs.org/)
[![Static Badge](https://img.shields.io/badge/FORUM-blue?style=for-the-badge)](https://forum.droposs.org)
[![GitHub License](https://img.shields.io/badge/AGPL--3.0-red?style=for-the-badge)](LICENSE)
[![Discord](https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/ACq4qZp4a9)
@@ -28,7 +28,7 @@ Drop is an open-source game distribution platform, similar to GameVault or Steam
## Deployment
See our documentation on how to [deploy Drop](https://droposs.org/docs/admin/quickstart) for more information.
See our documentation on how to [deploy Drop](https://docs.droposs.org/docs/guides/quickstart) for more information.
## Contributing
-1
View File
@@ -1 +0,0 @@
/bin
-19
View File
@@ -1,19 +0,0 @@
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())
}
-10
View File
@@ -1,10 +0,0 @@
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
)
-15
View File
@@ -1,15 +0,0 @@
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=
-5
View File
@@ -1,5 +0,0 @@
module drop
go 1.26.1
require github.com/gorilla/mux v1.8.1
-2
View File
@@ -1,2 +0,0 @@
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
-3
View File
@@ -1,3 +0,0 @@
go 1.26.1
use ./core
-9
View File
@@ -1,9 +0,0 @@
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=
-37
View File
@@ -1,37 +0,0 @@
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()
}
+69 -277
View File
@@ -2,21 +2,6 @@
# 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"
@@ -120,10 +105,10 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"synstructure 0.13.2",
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
@@ -132,10 +117,10 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"synstructure 0.13.2",
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
@@ -144,9 +129,9 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -155,9 +140,9 @@ version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -205,21 +190,6 @@ 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"
@@ -325,9 +295,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
dependencies = [
"heck",
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -564,9 +534,9 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -607,37 +577,26 @@ dependencies = [
[[package]]
name = "droplet-rs"
version = "0.16.3"
version = "0.14.1"
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"
@@ -681,28 +640,6 @@ 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"
@@ -800,9 +737,9 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -872,18 +809,6 @@ 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"
@@ -1268,9 +1193,9 @@ version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -1336,24 +1261,6 @@ 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"
@@ -1428,15 +1335,6 @@ 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"
@@ -1523,15 +1421,6 @@ 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"
@@ -1641,12 +1530,6 @@ 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"
@@ -1686,15 +1569,6 @@ 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"
@@ -1780,22 +1654,13 @@ 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 1.0.103",
"proc-macro2",
]
[[package]]
@@ -2022,12 +1887,6 @@ 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"
@@ -2225,9 +2084,9 @@ version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -2321,15 +2180,6 @@ 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"
@@ -2348,36 +2198,14 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "0.15.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
dependencies = [
"proc-macro2 0.4.30",
"quote 0.6.13",
"unicode-xid 0.1.0",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"proc-macro2",
"quote",
"unicode-ident",
]
@@ -2390,27 +2218,15 @@ dependencies = [
"futures-core",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 1.0.109",
"unicode-xid 0.2.6",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -2447,18 +2263,6 @@ 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"
@@ -2483,9 +2287,9 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -2494,9 +2298,9 @@ version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -2586,9 +2390,9 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -2703,18 +2507,6 @@ 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"
@@ -2835,7 +2627,7 @@ version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
dependencies = [
"quote 1.0.43",
"quote",
"wasm-bindgen-macro-support",
]
@@ -2846,9 +2638,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
dependencies = [
"bumpalo",
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
@@ -2956,9 +2748,9 @@ version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -2967,9 +2759,9 @@ version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -3303,10 +3095,10 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"synstructure 0.13.2",
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
@@ -3324,9 +3116,9 @@ version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -3344,10 +3136,10 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"synstructure 0.13.2",
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
@@ -3384,9 +3176,9 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2 1.0.103",
"quote 1.0.43",
"syn 2.0.114",
"proc-macro2",
"quote",
"syn",
]
[[package]]
+1 -1
View File
@@ -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 = "../libraries/droplet" }
droplet-rs = { path = "../droplet-rs", version = "0.14" }
fern = { version = "0.7.1", features = ["colored"] }
futures = "0.3.31"
indicatif = "0.18.3"
+21 -23
View File
@@ -3,7 +3,7 @@ use std::path::Path;
use crate::{
cli::UploadInfo,
commands::connect::{config::Config, config_option::ConfigOption},
manifest::{ClosureFactory, CompressionOption, DepotManifest, generate_v2_manifest},
manifest::{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(
upload_info: &UploadInfo,
info: &UploadInfo,
config: Config,
name: &Option<String>,
) -> anyhow::Result<()> {
let game_id = upload_info.game_id.clone();
let path = upload_info.path.clone();
let version_id = upload_info.version_id.clone();
let game_id = &info.game_id;
let path = &info.path;
let version_id = &info.version_id;
let operator = get_operator(config, name)?;
@@ -27,30 +27,28 @@ pub async fn upload(
info!("Uploading chunks");
let v2_manifest = generate_v2_manifest(
Path::new(&path),
ClosureFactory::new(
async move |id: String| {
info!("Uploading chunk id {id}");
let writer = operator
.writer(&format!("{game_id}/{version_id}/{id}"))
.await
.unwrap()
.into_futures_async_write()
.compat_write();
writer
},
|writer: Compat<FuturesAsyncWriter>| async {
writer.into_inner().close().await.unwrap();
},
),
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();
},
)
.await?;
info!("Finished uploading chunks");
existing_depot_manifest.append(
upload_info.game_id.to_string(),
upload_info.version_id.to_string(),
game_id.to_string(),
version_id.to_string(),
CompressionOption::None,
);
Ok(())
-2
View File
@@ -1,5 +1,3 @@
#![feature(async_fn_traits)]
use crate::commands::connect::config::manage_configuration;
use crate::{
cli::{Cli, Commands},
+10 -58
View File
@@ -1,7 +1,8 @@
use std::{collections::HashMap, path::Path};
use async_trait::async_trait;
use droplet_rs::manifest::{Manifest, ManifestWriterFactory, generate_manifest_rusty};
use droplet_rs::manifest::{
Manifest, generate_manifest_rusty, generate_manifest_rusty_v2,
};
use indicatif::{ProgressBar, ProgressStyle};
use log::info;
use serde::{Deserialize, Serialize};
@@ -39,60 +40,11 @@ impl DepotManifest {
}
}
pub struct ClosureFactory<Writer, Factory, Closer>
pub async fn generate_v2_manifest<W, F, CloseF>(dir: &Path, factory: F, closer: CloseF) -> anyhow::Result<Manifest>
where
Writer: AsyncWrite + Unpin,
Factory: AsyncFn(String) -> Writer,
Closer: AsyncFn(Writer),
{
writer: Factory,
closer: Closer,
}
#[async_trait]
impl<
W: AsyncWrite + Unpin + Send + Sync,
F: AsyncFn(String) -> W + Send + Sync + 'static,
C: AsyncFn(W) + Send + Sync,
> ManifestWriterFactory for ClosureFactory<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,
W: AsyncWrite + Unpin,
F: AsyncFn(String) -> W,
CloseF: AsyncFn(W)
{
let progress_bar = ProgressBar::new(10_000).with_style(
ProgressStyle::default_bar()
@@ -100,15 +52,15 @@ where
.unwrap(),
);
generate_manifest_rusty(
generate_manifest_rusty_v2(
dir,
|progress| {
let progress_int = (progress * 100f32).round() as u64;
progress_bar.set_position(progress_int);
},
|log| progress_bar.suspend(|| info!("{}", log)),
Some(&factory),
None,
factory,
closer
)
.await
}
+31
View File
@@ -0,0 +1,31 @@
---
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.
+20
View File
@@ -0,0 +1,20 @@
---
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.
+23
View File
@@ -0,0 +1,23 @@
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,12 +1,7 @@
name: "Build and release desktop"
name: "publish"
on:
workflow_dispatch:
inputs:
tagName:
required: false
type: string
description: "tagName to be associated with this release."
workflow_dispatch: {}
release:
types: [published]
# This can be used to automatically publish nightlies at UTC nighttime
@@ -38,11 +33,13 @@ 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
@@ -61,7 +58,7 @@ jobs:
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: './desktop/src-tauri -> target'
workspaces: './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.
@@ -83,10 +80,6 @@ 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"
@@ -122,19 +115,14 @@ jobs:
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Do NOT set APPLE_CERTIFICATE / APPLE_CERTIFICATE_PASSWORD here. Doing so
# makes tauri-action import the cert into its own throwaway keychain and
# look up the identity by Apple-only name prefixes (e.g.
# "Developer ID Application:"), which never matches our "Drop OSS" cert
# and fails with "failed to resolve signing identity". Instead we rely on
# the build.keychain prepared above and only pass the resolved identity.
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
NO_STRIP: true
with:
tagName: ${{ inputs.print_tags || 'v__VERSION__' }} # the action automatically replaces \_\_VERSION\_\_ with the app version.
tagName: 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'
+6
View File
@@ -0,0 +1,6 @@
[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
+7
View File
@@ -21,6 +21,13 @@ 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);
@@ -1,141 +0,0 @@
<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,19 +23,16 @@
</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>
+2 -3
View File
@@ -1,7 +1,7 @@
<template>
<ModalTemplate size-class="max-w-4xl" v-model="open">
<template #default>
<div class="flex flex-row gap-x-4 min-h-96">
<div class="flex flex-row gap-x-4 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,12 +29,11 @@
</li>
</ul>
</nav>
<div class="border-l-2 border-zinc-800 w-full grow pl-4">
<div class="border-l-2 border-zinc-800 w-full grow pl-4 overflow-y-scroll">
<component
v-model="configuration"
:is="tabs[currentTabIndex]?.page"
:proton-enabled="protonEnabled"
:game-id="props.gameId"
/>
</div>
</div>
+1 -1
View File
@@ -36,7 +36,7 @@
as="div"
v-for="(nav, navIndex) in filteredNavigation"
:key="nav.id"
:class="['first:pt-0 last:pb-0', nav.tools && !filteredNavigation[navIndex - 1].tools ? 'mt-auto' : '']"
:class="['first:pt-0 last:pb-0', nav.tools ? 'mt-auto' : '']"
v-slot="{ open }"
:default-open="nav.deft"
>
+1 -1
View File
@@ -22,7 +22,7 @@
"koa": "^2.16.1",
"markdown-it": "^14.1.0",
"micromark": "^4.0.1",
"nuxt": "^4.4.8",
"nuxt": "^3.16.0",
"scss": "^0.2.4",
"vue-router": "latest",
"vuedraggable": "^4.1.0"
+2276 -2900
View File
File diff suppressed because it is too large Load Diff
+1 -5
View File
@@ -1,7 +1,3 @@
const path = require('path');
const dropbase = path.join(__dirname, "../../libraries/base")
/** @type {import('tailwindcss').Config} */
export default {
content: [
@@ -11,7 +7,7 @@ export default {
"./plugins/**/*.{js,ts}",
"./app.vue",
"./error.vue",
`${dropbase}/components/**/*.{js,vue,ts}`,
"../libs/drop-base/**/*.{js,vue,ts}",
],
theme: {
extend: {
-1
View File
@@ -53,7 +53,6 @@ export type GameVersion = {
userConfiguration: {
launchTemplate: string;
overrideProtonPath: string;
overrideHandler: string | undefined;
enableUpdates: boolean
};
setups: Array<{ platform: string }>;
+1 -1
View File
@@ -7,7 +7,7 @@
"tauri": "tauri"
},
"dependencies": {
"pino": "^9.14.0",
"pino": "^9.7.0",
"pino-pretty": "^13.1.1",
"tauri": "^0.15.0"
},
+5583
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
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'
+535 -140
View File
File diff suppressed because it is too large Load Diff
+2 -4
View File
@@ -41,6 +41,7 @@ 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"
@@ -82,7 +83,7 @@ sha1 = "0.10.6"
shared_child = "1.0.1"
slice-deque = "0.3.0"
sysinfo = "0.36.1"
tar = "0.4.46"
tar = "0.4.44"
tauri-plugin-autostart = "*"
tauri-plugin-deep-link = "*"
tauri-plugin-dialog = "*"
@@ -136,9 +137,6 @@ 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"
+1 -1
View File
@@ -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.46"
tar = "0.4.44"
tempfile = "3.23.0"
uuid = "1.18.1"
whoami = "1.6.1"
-3
View File
@@ -79,7 +79,6 @@ pub mod data {
UserConfiguration {
launch_template: "{}".to_owned(),
override_proton_path: None,
override_handler: None,
enable_updates: false,
}
}
@@ -89,8 +88,6 @@ 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,9 +1,7 @@
use std::{
collections::HashMap,
env,
sync::RwLock,
time::{Duration, Instant},
usize,
time::{Duration, Instant}, usize,
};
use futures_util::StreamExt;
@@ -36,7 +34,7 @@ struct Depot {
manifest: Option<DepotManifest>,
latest_speed: Option<usize>, // bytes per second
current_downloads: SyncSemaphore,
enabled: bool,
enabled: bool
}
pub struct DepotManager {
@@ -115,16 +113,10 @@ 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);
if env::var("FORCE_ENABLE_DEPOTS")
.map(|v| !v.is_empty())
.unwrap_or(false)
{
} else {
depot.enabled = false;
}
depot.enabled = false;
}
}
let enabled = new_depots.iter().filter(|v| v.enabled).count();
if enabled == 0 {
return Err(RemoteAccessError::NoDepots);
+1 -1
View File
@@ -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_types = { path = "../../../libraries/droplet_types" }
droplet-rs = { path = "../../../libraries/droplet" }
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_types::{ChunkData, Manifest};
use droplet_rs::manifest::{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_types::ChunkData;
use droplet_rs::manifest::ChunkData;
use futures_util::StreamExt as _;
use log::{debug, info};
use remote::auth::generate_authorization_header;
@@ -1,15 +1,11 @@
use std::{
fs::create_dir_all,
path::{Path, PathBuf},
process::Command,
};
use std::{fs::create_dir_all, 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, parser::ParsedCommand, process_manager::ProcessHandler};
use crate::{error::ProcessError, process_manager::ProcessHandler};
pub struct MacLauncher;
impl ProcessHandler for MacLauncher {
@@ -29,231 +25,13 @@ 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(
&self,
_meta: &DownloadableMetadata,
launch_command: String,
_game_version: &GameVersion,
current_dir: &str,
_database: &Database,
) -> Result<String, ProcessError> {
windows_launch_command(launch_command, current_dir, None)
}
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-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,
@@ -262,26 +40,19 @@ impl ProcessHandler for LinuxNativeLauncher {
_current_dir: &str,
_database: &Database,
) -> Result<String, ProcessError> {
// Run native Linux games directly, no umu-run wrapper
Ok(launch_command)
Ok(format!("cmd /C \"{}\"", 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."
#[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);
}
}
@@ -330,18 +101,6 @@ 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;
@@ -409,18 +168,6 @@ 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;
@@ -481,16 +228,4 @@ 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,8 +28,7 @@ use crate::{
format::DropFormatArgs,
parser::{LaunchParameters, ParsedCommand},
process_handlers::{
AsahiMuvmLauncher, LinuxNativeLauncher, MacLauncher, UMUCompatLauncher, UMUNativeLauncher,
WindowsCmdLauncher, WindowsDirectLauncher, WindowsLauncher, WindowsPowershellLauncher,
AsahiMuvmLauncher, MacLauncher, UMUCompatLauncher, UMUNativeLauncher, WindowsLauncher,
},
};
@@ -55,13 +54,6 @@ 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");
@@ -84,22 +76,6 @@ 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),
@@ -125,7 +101,7 @@ impl ProcessManager<'_> {
match self.processes.get_mut(&game_id) {
Some(process) => {
process.manually_killed = true;
kill_process_tree(&process.handle)?;
process.handle.kill()?;
let exit_status = process.handle.wait()?;
info!("exit status: {:?}", exit_status);
Ok(())
@@ -212,21 +188,7 @@ 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()
@@ -242,44 +204,10 @@ 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, None);
let process_handler = self.fetch_process_handler(&db_lock, platform);
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();
@@ -382,12 +310,7 @@ impl ProcessManager<'_> {
let target_platform = meta.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 process_handler = self.fetch_process_handler(&db_lock, &target_platform)?;
let (target_command, emulator) = match game_status {
GameDownloadStatus::Installed {
@@ -593,30 +516,6 @@ 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,
@@ -630,8 +529,4 @@ 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;
}
+1
View File
@@ -10,6 +10,7 @@ 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"
+10 -50
View File
@@ -8,17 +8,8 @@
#![deny(clippy::all)]
use std::{
env,
fs::File,
io::Write,
panic::PanicHookInfo,
path::Path,
str::FromStr,
sync::{
atomic::{AtomicBool, Ordering},
nonpoison::Mutex,
},
time::SystemTime,
env, fs::File, io::Write, panic::PanicHookInfo, path::Path, str::FromStr,
sync::nonpoison::Mutex, time::SystemTime,
};
use ::client::{
@@ -269,7 +260,6 @@ 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")]
@@ -369,17 +359,8 @@ 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(|| {
let tray = TrayIconBuilder::new()
TrayIconBuilder::new()
.icon(
app.default_window_icon()
.expect("Failed to get default window icon")
@@ -402,12 +383,8 @@ pub fn run() {
warn!("menu event not handled: {:?}", event.id);
}
})
.build(app);
if let Err(e) = tray {
warn!("failed to set up system tray icon, disabling tray: {e}");
TRAY_DISABLED.store(true, Ordering::Relaxed);
}
.build(app)
.expect("error while setting up tray menu");
});
{
@@ -468,30 +445,13 @@ 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 TRAY_DISABLED.load(Ordering::Relaxed) {
return;
if match std::env::var("NO_TRAY_ICON") {
Ok(s) => s.to_lowercase() != "true",
Err(_) => true,
} {
(f)();
}
(f)();
}
// TODO: Refactor
+1 -6
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use process::{
PROCESS_MANAGER,
error::ProcessError,
process_manager::{LaunchOption, ProcessHandlerOption, ProcessManager},
process_manager::{LaunchOption, ProcessManager},
};
use serde::Serialize;
use tauri::AppHandle;
@@ -16,11 +16,6 @@ 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 added at 78294ac1d6
+1 -1
View File
@@ -27,7 +27,7 @@
},
"bundle": {
"active": true,
"targets": ["nsis", "deb", "rpm", "dmg"],
"targets": ["nsis", "deb", "rpm", "dmg", "appimage"],
"windows": {
"nsis": {
"installMode": "both"
@@ -41,7 +41,7 @@ export default defineConfig({
{ slug: "user" },
{
label: "Install",
items: [{ autogenerate: { directory: "user/install" } }],
autogenerate: { directory: "user/install" },
},
{
label: "Usage",
@@ -65,26 +65,25 @@ export default defineConfig({
},
{
label: "Going further",
items: [{ autogenerate: { directory: "admin/going-further" } }],
autogenerate: { directory: "admin/going-further" },
},
{
label: "Metadata",
items: [{ autogenerate: { directory: "admin/metadata" } }],
autogenerate: { directory: "admin/metadata" },
},
{
label: "Authentication",
items: [{ autogenerate: { directory: "admin/authentication" } }],
autogenerate: { directory: "admin/authentication" },
},
],
},
{
label: "Reference",
items: [{ autogenerate: { directory: "reference" } }],
autogenerate: { directory: "reference" },
},
],
customCss: ["./src/styles/drop.css"],
}),
],
site: "https://droposs.org",
base: "/docs",
site: "https://docs-next.droposs.org/",
});
@@ -10,11 +10,11 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/starlight": "^0.40.0",
"astro": "^6.4.8",
"sharp": "^0.35.2",
"starlight-image-zoom": "^0.14.2",
"starlight-links-validator": "^0.24.1",
"@astrojs/starlight": "^0.37.4",
"astro": "^5.6.1",
"sharp": "^0.34.2",
"starlight-image-zoom": "^0.13.2",
"starlight-links-validator": "^0.19.2",
"starlight-theme-rapide": "^0.5.2"
}
}
+4205
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
onlyBuiltDependencies:
- esbuild
- sharp

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 696 B

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 436 B

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

@@ -28,7 +28,7 @@ For convenience's sake, we can also specify file extensions for Drop's auto-dete
Add a launch executable for every platform you want to support. The options are fairly self-explanatory, but make sure to use the `{rom}` placeholder, and optionally add the file extensions.
Read the [Command Parsing](/docs/reference/command-parsing/) article to understand how it's parsed and substituted.
Read the [Command Parsing](/reference/command-parsing/) article to understand how it's parsed and substituted.
3. ## Import your game
@@ -18,7 +18,7 @@ If you're using a library source that supports versioning, you can add and impor
2. ### Follow the import guide again
Follow the [import guide again](/docs/admin/guides/import-version/), but this time for your new version folder.
Follow the [import guide again](/admin/guides/import-version/), but this time for your new version folder.
</Steps>
@@ -39,7 +39,7 @@ You can stack many "update mode" versions on top of each other, and they will pi
2. ### Follow the import guide again
Follow the [import guide again](/docs/admin/guides/import-version/), but this time for your new version folder.
Follow the [import guide again](/admin/guides/import-version/), but this time for your new version folder.
3. ### Before import, enable update mode
@@ -3,7 +3,7 @@ title: Setting up OIDC
---
:::note
You can find reference information in the [OIDC authentication docs](/docs/admin/authentication/oidc/).
You can find reference information in the [OIDC authentication docs](/admin/authentication/oidc/).
:::
## Authentik
@@ -10,7 +10,7 @@ To import games and start using Drop, you must first create a library to import
1. **Decide on a library layout.**
Drop supports different layouts for your files on disk, you can read more about them in the [Library Sources](/docs/reference/library-sources) reference section.
Drop supports different layouts for your files on disk, you can read more about them in the [Library Sources](/reference/library-sources) reference section.
2. **Mount your library in the Docker container.**
@@ -28,7 +28,7 @@ To import games and start using Drop, you must first create a library to import
- `/mnt/media/my-drop-library` is the path to your library.
- `/library` is a **unique** path inside the container. **Use something else if another volume mounts to `/library`**.
If you followed the [Quickstart](/docs/admin/quickstart/) guide, you'll have already set up a library at `./library` pointing to `/library` within the container. You may want to instead edit that line in the `volumes` section to point to where your library is located.
If you followed the [Quickstart](/admin/quickstart/) guide, you'll have already set up a library at `./library` pointing to `/library` within the container. You may want to instead edit that line in the `volumes` section to point to where your library is located.
3. **Open library source interface in Admin Dashboard.**
@@ -16,7 +16,7 @@ Drop automatically parses and formats the URL, so there are no requirements on t
## LAN
The `compose.yaml` provided in the [Quickstart guide](/docs/admin/quickstart/) already exposes the Drop instance on port 3000. If you're on the same LAN as your Drop instance, you can find it's IP and then use:
The `compose.yaml` provided in the [Quickstart guide](/admin/quickstart/) already exposes the Drop instance on port 3000. If you're on the same LAN as your Drop instance, you can find it's IP and then use:
```
http://[instance IP]:3000
@@ -60,7 +60,7 @@ Once you've got a library set up, and have imported a game, you can import a ver
A installer version uses "setup mode". Enable the option, and then add the installer executable in setup commands.
:::note
Setup and launch commands are parsed in a cross-platform, POSIX style. It's not relevant for simple setups, but useful to know. Read more about it in [Command Parsing](/docs/reference/command-parsing/).
Setup and launch commands are parsed in a cross-platform, POSIX style. It's not relevant for simple setups, but useful to know. Read more about it in [Command Parsing](/reference/command-parsing/).
:::
6. ### **Wait for import.**

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

@@ -40,7 +40,7 @@ services:
**The main things in this `compose.yaml` is the volumes attached to the `drop` service:**
1. `./library` is where you will put your games to be imported into Drop. See '[Creating a library](/docs/admin/guides/creating-library/)' once you're set up.
1. `./library` is where you will put your games to be imported into Drop. See '[Creating a library](/admin/guides/creating-library/)' once you're set up.
2. `./data` is where Drop will store anything that's using the default file-system backed storage system. Typically, these are objects.
:::tip
@@ -7,7 +7,7 @@ hero:
file: ../../assets/drop.svg
actions:
- text: Quickstart
link: /docs/admin/quickstart
link: /admin/quickstart
icon: right-arrow
- text: Download client
link: https://droposs.org/download
@@ -89,7 +89,7 @@ Drop's [dockerfile](https://github.com/Drop-OSS/drop/blob/develop/Dockerfile) pr
:::
```bash
npm install prisma@7.7.0 dotenv # dotenv is required
npm install prisma@7.3.0 dotenv # dotenv is required
```
Then, with your database running:
@@ -21,40 +21,12 @@ Then, what happens with this, depends on the type of game we're launching:
## Normal (no emulator)
Drop reconstructs the original shell string, and passes it into a platform-specific command wrapper, called a **launch method**. Drop picks a sensible launch method automatically, but you can override it per-game for troubleshooting — see [Launch methods](#launch-methods) below.
By default, on Windows the command is launched based on its file type: `.exe` files run directly, `.bat` and `.cmd` files run through `cmd`, `.ps1` files run through PowerShell, and anything else is handed to `cmd` so builtins, `PATHEXT` resolution and `%VAR%` expansion all work. On Linux, native games run directly on the host, while games targeting Windows are wrapped in `umu-run` (with Proton).
Drop reconstructs the original shell string, and passes it into platform-specific command wrappers. For Windows, this means nothing. For Linux, it gets wrapped in `umu-run`.
It is then parsed again, and then passed into process creation, mapping the environment variable, command, and arguments into their respective platform-dependent places.
Drop logs out it's final parsed command, if you want to look at it in the client logs.
## Launch methods
The wrapper Drop uses to start a game is called a **launch method** (a *process handler* internally). Drop automatically selects the best available method for each game, but if a game won't launch you can override it under **Game Options → Launch → Launch method**.
Only methods supported by your current platform (and the game's target platform) are listed, each with a short description in the client.
### Windows
| Method | Description |
| ------ | ----------- |
| **Automatic** *(default)* | Detects the file type and launches it directly, or through `cmd` or PowerShell. |
| **Direct executable** | Runs the executable directly, without a shell. |
| **Command Prompt (cmd)** | Launches through `cmd.exe`. Supports batch files, builtins and `%VAR%` expansion. |
| **PowerShell** | Runs the command as a PowerShell script (`-File`). |
### Linux
| Method | Description |
| ------ | ----------- |
| **Native (direct)** *(default for Linux games)* | Runs the native Linux game directly on the host. |
| **Steam Linux Runtime (umu-run)** | Runs the native Linux game inside `umu-run`'s Steam Linux Runtime. Requires [UMU launcher](/docs/user/usage/proton/). |
| **Proton (umu-run)** *(default for Windows games)* | Runs a Windows game through Proton, using `umu-run`. Requires [Proton](/docs/user/usage/proton/). |
| **Proton + muvm (Asahi)** | Runs a Windows game through Proton inside a muvm microVM, for Apple Silicon / Asahi Linux. |
On macOS, games are always launched directly.
## Emulators
For emulators, we have the "emulator version" (version containing the emulator), and the "emulated version" (version containing the ROM).
@@ -48,7 +48,7 @@ In the UI, you'll be prompted to "import" each folder separately:
So your game has gotten an update and you've got new files. All you need to do is create a new version folder inside the game folder, and move all the files you have into that folder. Then, import it within the Drop admin UI.
If you have files that you're supposed to **paste over the previous version**, Drop supports that! Read [Update mode](/docs/reference/update-mode/) to find out more.
If you have files that you're supposed to **paste over the previous version**, Drop supports that! Read [Update mode](/reference/update-mode/) to find out more.
# Compatibility (flat-style)
@@ -4,4 +4,4 @@ title: Getting Started
Drop clients are available for download from [our website](https://droposs.org/download), or follow one of our installation guides on the sidebar. Download the correct version for your platform, and open it up.
The client will walk you through the setup and sign-in process to get started. You'll need a Drop instance you can connect to, and an account on the server. If you don't have one, you can follow the [Quickstart](/docs/admin/quickstart/) guide to set up your own.
The client will walk you through the setup and sign-in process to get started. You'll need a Drop instance you can connect to, and an account on the server. If you don't have one, you can follow the [Quickstart](/admin/quickstart/) guide to set up your own.

Some files were not shown because too many files have changed in this diff Show More