New v0.4.0 website

This commit is contained in:
DecDuck
2026-04-03 01:25:10 +00:00
parent 50106d5fa2
commit 2dd90fbc44
71 changed files with 2126 additions and 1579 deletions
+1
View File
@@ -0,0 +1 @@
/bin
+19
View File
@@ -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())
}
+10
View File
@@ -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
)
+15
View File
@@ -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=
+5
View File
@@ -0,0 +1,5 @@
module drop
go 1.26.1
require github.com/gorilla/mux v1.8.1
+2
View File
@@ -0,0 +1,2 @@
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+3
View File
@@ -0,0 +1,3 @@
go 1.26.1
use ./core
+9
View File
@@ -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=
+37
View File
@@ -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()
}
+269 -69
View File
@@ -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,7 +607,7 @@ dependencies = [
[[package]]
name = "droplet-rs"
version = "0.14.1"
version = "0.16.3"
dependencies = [
"anyhow",
"async-trait",
@@ -586,11 +616,14 @@ dependencies = [
"getrandom 0.3.4",
"hex",
"humansize",
"libarchive-drop",
"rcgen",
"ring",
"serde",
"serde_json",
"sha2",
"speedometer",
"test-generator",
"time",
"tokio",
"uuid",
@@ -640,6 +673,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 +792,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 +864,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 +1260,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 +1328,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 +1420,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 +1515,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 +1633,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 +1678,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 +1772,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 +2014,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 +2217,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 +2313,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 +2340,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 +2382,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 +2439,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 +2475,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 +2486,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 +2578,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 +2695,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 +2827,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 +2838,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 +2948,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 +2959,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 +3295,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 +3316,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 +3336,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 +3376,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
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 = "../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"
+23 -21
View File
@@ -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(())
+2
View File
@@ -1,3 +1,5 @@
#![feature(async_fn_traits)]
use crate::commands::connect::config::manage_configuration;
use crate::{
cli::{Cli, Commands},
+58 -10
View File
@@ -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
}
-6
View File
@@ -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
-7
View File
@@ -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);
+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 ? 'mt-auto' : '']"
:class="['first:pt-0 last:pb-0', nav.tools && !filteredNavigation[navIndex - 1].tools ? 'mt-auto' : '']"
v-slot="{ open }"
:default-open="nav.deft"
>
+5 -1
View File
@@ -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: {
+133 -528
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -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"
@@ -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);
+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-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
View File
@@ -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"
+1 -1
View File
@@ -27,7 +27,7 @@
},
"bundle": {
"active": true,
"targets": ["nsis", "deb", "rpm", "dmg", "appimage"],
"targets": ["nsis", "deb", "rpm", "dmg"],
"windows": {
"nsis": {
"installMode": "both"
+1
View File
@@ -21,6 +21,7 @@ target/
/target
perf.data
perf.data.old
flamegraph.svg
*.json
-3
View File
@@ -1,3 +0,0 @@
[submodule "libarchive-rust"]
path = libarchive-rust
url = https://github.com/Drop-OSS/libarchive-rust.git
+416 -159
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -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
+3 -2
View File
@@ -1,13 +1,14 @@
#![deny(clippy::all)]
#![feature(impl_trait_in_bindings)]
#![feature(nonpoison_mutex)]
#![feature(sync_nonpoison)]
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;
+15 -1
View File
@@ -1,8 +1,21 @@
use std::{env, path::PathBuf};
use droplet_rs::manifest::generate_manifest_rusty;
use droplet_rs::manifest::{ManifestWriterFactory, generate_manifest_rusty};
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
+217 -206
View File
@@ -1,162 +1,172 @@
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;
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;
pub use droplet_types::{ChunkData, FileEntry, Manifest};
#[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();
files.sort_by(|a, b| b.size.cmp(&a.size));
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 +178,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,
})
}
+3 -1
View File
@@ -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(()))
+12 -9
View File
@@ -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,13 @@ 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>>>> {
pub fn create_backend_constructor<'a, P>(
path: P,
) -> Option<Box<dyn FnOnce() -> Result<Box<dyn VersionBackend + Send + Sync + 'a>>>>
where
P: AsRef<Path>,
{
let path = path.as_ref();
if !path.exists() {
return None;
}
@@ -49,8 +53,7 @@ pub fn create_backend_constructor<'a>(
}));
};
let file_extension = path.extension().and_then(|v| v.to_str())?;
let file_extension = path.extension().map(|v| v.to_str()).flatten()?;
if SUPPORTED_FILE_EXTENSIONS.contains(&file_extension) {
let buf = path.to_path_buf();
return Some(Box::new(move || Ok(Box::new(ZipVersionBackend::new(buf)?))));
+28
View File
@@ -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
+75
View File
@@ -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"
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "droplet_types"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "*", features = ["derive"] }
+6
View File
@@ -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.
+27
View File
@@ -0,0 +1,27 @@
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],
}
+2 -2
View File
@@ -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"
+4 -4
View File
@@ -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]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

+13 -13
View File
@@ -25,14 +25,14 @@ function Header() {
<section className="mt-16 grid grid-cols-1 lg:grid-cols-2 lg:gap-12">
<div className="max-w-lg">
<h2 className="text-2xl font-medium tracking-tight">Our mission</h2>
<p className="mt-6 text-sm/6 text-gray-600">
<p className="mt-6 text-sm/6 text-zinc-400">
We aim to build a &quot;Steam-like experience for DRM-free
games&quot;. One of the major sticking points of DRM games, and why
platforms like Steam have done so well, is that they provide awesome
features like worldwide play-together, social features, and useful
overlays and tools. We aim to replicate this for <i>any</i> game.
</p>
<p className="mt-8 text-sm/6 text-gray-600">
<p className="mt-8 text-sm/6 text-zinc-400">
Don&apos;t get us wrong, we don&apos;t think Steam or Valve is bad.
They are unusually consumer-friendly, and provide a great service.
They will always have a place, providing a marketplace for DRM
@@ -41,16 +41,16 @@ function Header() {
</div>
<div className="max-lg:mt-16 lg:col-span-1">
<Subheading>The Numbers</Subheading>
<hr className="mt-6 border-t border-gray-200" />
<hr className="mt-6 border-t border-zinc-800" />
<dl className="mt-6 grid grid-cols-1 gap-x-8 gap-y-4 sm:grid-cols-2">
<div className="flex flex-col gap-y-2 border-b border-dotted border-gray-200 pb-4">
<dt className="text-sm/6 text-gray-600">Lines of code</dt>
<div className="flex flex-col gap-y-2 border-b border-dotted border-zinc-800 pb-4">
<dt className="text-sm/6 text-zinc-400">Lines of code</dt>
<dd className="order-first text-6xl font-medium tracking-tight">
<AnimatedNumber start={10} end={40} />k
</dd>
</div>
<div className="flex flex-col gap-y-2 border-b border-dotted border-gray-200 pb-4">
<dt className="text-sm/6 text-gray-600">
<div className="flex flex-col gap-y-2 border-b border-dotted border-zinc-800 pb-4">
<dt className="text-sm/6 text-zinc-400">
Individual open-source projects
</dt>
<dd className="order-first text-6xl font-medium tracking-tight">
@@ -59,13 +59,13 @@ function Header() {
</dd>
</div>
<div className="flex flex-col gap-y-2 max-sm:border-b max-sm:border-dotted max-sm:border-gray-200 max-sm:pb-4">
<dt className="text-sm/6 text-gray-600">Docker pulls</dt>
<dt className="text-sm/6 text-zinc-400">Docker pulls</dt>
<dd className="order-first text-6xl font-medium tracking-tight">
<AnimatedNumber start={0} end={48.8} decimals={1} />k
</dd>
</div>
<div className="flex flex-col gap-y-2">
<dt className="text-sm/6 text-gray-600">Contributors</dt>
<dt className="text-sm/6 text-zinc-400">Contributors</dt>
<dd className="order-first text-6xl font-medium tracking-tight">
&gt;
<AnimatedNumber start={0} end={15} />
@@ -91,7 +91,7 @@ function FrequentlyAskedQuestions() {
<dt className="text-sm font-semibold">
Do you intend to replace Steam?
</dt>
<dd className="mt-4 text-sm/6 text-gray-600">
<dd className="mt-4 text-sm/6 text-zinc-400">
No. Drop is not a replacement for Steam, in the sense that we will
ever offer a marketplace for developers to sell games. Drop can
replace Steam <i>for an individual</i>, if they only played
@@ -102,7 +102,7 @@ function FrequentlyAskedQuestions() {
<dt className="text-sm font-semibold">
Will Drop ever cost money or require a subscription?
</dt>
<dd className="mt-4 text-sm/6 text-gray-600">
<dd className="mt-4 text-sm/6 text-zinc-400">
We believe in <strong>paying for services, not code</strong>. All
our projects are and always will be open source (AGPLv3), and we
endeavour to allow users to self-host as much of it as possible.
@@ -114,7 +114,7 @@ function FrequentlyAskedQuestions() {
</dl>
<dl>
<dt className="text-sm font-semibold">Is Drop legal?</dt>
<dd className="mt-4 text-sm/6 text-gray-600">
<dd className="mt-4 text-sm/6 text-zinc-400">
Yes. Officially, Drop is <strong>only</strong> for DRM-free games,
like the ones you purchase from GOG or download from itch.io,{' '}
<strong>and</strong> that you have a license to redistribute. We
@@ -126,7 +126,7 @@ function FrequentlyAskedQuestions() {
</dl>
<dl>
<dt className="text-sm font-semibold">How can I support Drop or get involved?</dt>
<dd className="mt-4 text-sm/6 text-gray-600">
<dd className="mt-4 text-sm/6 text-zinc-400">
Thank you for helping us out! If you&apos;re looking to contribute
code, check out our{' '}
<Link
+1 -1
View File
@@ -15,7 +15,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className="text-gray-950 antialiased">
<body className="text-zinc-100 bg-zinc-950 antialiased">
{children}
</body>
</html>
+3 -3
View File
@@ -61,7 +61,7 @@ export default async function BlogPost({
className="aspect-square size-6 rounded-full object-cover"
/>
)}
<div className="text-sm/5 text-gray-700">{author.name}</div>
<div className="text-sm/5 text-zinc-300">{author.name}</div>
</div>
)}
{
@@ -69,7 +69,7 @@ export default async function BlogPost({
{tags.map((tag) => (
<div
key={tag}
className="rounded-full border border-dotted border-gray-300 bg-gray-50 px-2 text-sm/6 font-medium text-gray-500"
className="rounded-full border border-dotted border-zinc-800 bg-zinc-900 px-2 text-sm/6 font-medium text-zinc-400"
>
{tag}
</div>
@@ -77,7 +77,7 @@ export default async function BlogPost({
</div>
}
</div>
<div className="text-gray-700">
<div className="text-zinc-400">
<div className="max-w-2xl xl:mx-auto">
{post.image && (
<img
+1 -1
View File
@@ -17,7 +17,7 @@ export default function About() {
<Container>
<Navbar />
</Container>
<main className="grid min-h-full place-items-center bg-white px-6 py-24 sm:py-32 lg:px-8">
<main className="grid min-h-full place-items-center px-6 py-24 sm:py-32 lg:px-8">
<div className="text-center">
<Subheading>404</Subheading>
<Heading>
+51 -18
View File
@@ -5,8 +5,8 @@ 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 { ArrowDownCircleIcon } from '@heroicons/react/24/solid'
import type { Metadata } from 'next'
export const metadata: Metadata = {
@@ -21,16 +21,18 @@ function Hero() {
<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]">
<h1 className="font-display text-6xl/[0.9] font-medium tracking-tight text-balance text-zinc-100 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">
<p className="mt-8 max-w-lg text-xl/7 font-medium text-zinc-100/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">
<Button href="https://docs.droposs.org/docs/guides/quickstart">
Get started
</Button>
<Button variant="outline" href="/about">
About
</Button>
</div>
@@ -42,18 +44,49 @@ function Hero() {
function FeatureSection() {
return (
<div className="overflow-hidden">
<Container className="pb-24">
<Heading as="h2" className="max-w-3xl">
A better experience for DRM&#8209;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 className="py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-2xl sm:text-center">
<Subheading>Drop OSS</Subheading>
<Heading as="h3" className="mt-2 max-w-4xl">
The ultimate self-hosted game manager.
</Heading>
<p className="mt-6 text-lg/8 text-zinc-400">
Drop is built from the ground up to be flexible, fast, and
beautiful. It's designed to scale with your library, and handle
thousands of games.
</p>
</div>
</div>
<div className="relative overflow-hidden pt-16">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<img
alt="App screenshot"
src="/gallery/store.png"
width={1920}
height={1071}
className="mb-[-5%] rounded-xl shadow-2xl ring-1 ring-gray-900/10"
/>
<div aria-hidden="true" className="relative">
<div className="absolute -inset-x-20 bottom-0 bg-linear-to-t from-zinc-950 pt-[7%]" />
</div>
</div>
</div>
<div className="mx-auto mt-16 max-w-7xl px-6 sm:mt-20 md:mt-24 lg:px-8">
<dl className="mx-auto grid max-w-2xl grid-cols-1 gap-x-6 gap-y-10 text-base/7 text-zinc-400 sm:grid-cols-2 lg:mx-0 lg:max-w-none lg:grid-cols-3 lg:gap-x-8 lg:gap-y-16">
<div className="relative pl-9">
<dt className="inline font-semibold text-zinc-100">
<ArrowDownCircleIcon
aria-hidden="true"
className="absolute top-1 left-1 size-5 text-blue-600"
/>
ADASDASD
</dt>{' '}
<dd className="inline">ASDASDASDAS</dd>
</div>
</dl>
</div>
</div>
)
}
@@ -132,7 +165,7 @@ export default function Home() {
<div className="overflow-hidden">
<Hero />
<main>
<div className="bg-linear-to-b from-white from-50% to-gray-100 py-32">
<div className="bg-linear-to-b pb-16">
<FeatureSection />
<BentoSection />
</div>
+1 -2
View File
@@ -26,7 +26,7 @@ function CTA() {
<div className="mt-12 grid grid-cols-1 gap-12 lg:grid-cols-2">
<div className="max-w-lg">
<p className="text-sm/6 text-gray-600">
<p className="text-sm/6 text-zinc-400">
If you&apos;re looking to give back to the project financially -
first off, thank you. It really does help a lot. There are two
options for contributing: our OpenCollective, which funds
@@ -45,7 +45,6 @@ function CTA() {
<Button
className="w-full sm:w-auto"
href="/about#team"
variant="secondary"
>
Team &rarr;
</Button>
+4 -11
View File
@@ -29,28 +29,21 @@ export function BentoCard({
data-dark={dark ? 'true' : undefined}
className={clsx(
className,
'group relative flex flex-col overflow-hidden rounded-lg',
'bg-white shadow-xs ring-1 ring-black/5',
'data-dark:bg-gray-800 data-dark:ring-white/15',
'group relative flex flex-col overflow-hidden rounded-lg ring-1',
'bg-zinc-900 ring-white/15',
)}
>
<div className="relative h-80 shrink-0">
{graphic}
{fade.includes('top') && (
<div className="absolute inset-0 bg-linear-to-b from-white to-50% group-data-dark:from-gray-800 group-data-dark:from-[-25%]" />
)}
{fade.includes('bottom') && (
<div className="absolute inset-0 bg-linear-to-t from-white to-20% group-data-dark:from-gray-800 group-data-dark:from-[-25%]" />
)}
</div>
<div className="relative p-10">
<Subheading as="h3" dark={dark}>
{eyebrow}
</Subheading>
<p className="mt-1 text-2xl/8 font-medium tracking-tight text-gray-950 group-data-dark:text-white">
<p className="mt-1 text-2xl/8 font-medium tracking-tight text-white">
{title}
</p>
<p className="mt-2 max-w-[600px] text-sm/6 text-gray-600 group-data-dark:text-gray-400">
<p className="mt-2 max-w-[600px] text-sm/6 text-gray-400">
{description}
</p>
</div>
+7 -7
View File
@@ -5,22 +5,22 @@ import { Link } from './link'
const variants = {
primary: clsx(
'inline-flex items-center justify-center px-4 py-[calc(--spacing(2)-1px)]',
'rounded-full border border-transparent bg-gray-950 shadow-md',
'rounded-lg border border-transparent bg-gray-950 shadow-md',
'text-base font-medium whitespace-nowrap text-white',
'data-disabled:bg-gray-950 data-disabled:opacity-40 data-hover:bg-gray-800',
),
secondary: clsx(
'relative inline-flex items-center justify-center px-4 py-[calc(--spacing(2)-1px)]',
'rounded-full border border-transparent bg-white/15 shadow-md ring-1 ring-[#D15052]/15',
'rounded-lg border border-transparent bg-white/15 shadow-md ring-1 ring-[#D15052]/15',
'after:absolute after:inset-0 after:rounded-full after:shadow-[inset_0_0_2px_1px_#ffffff4d]',
'text-base font-medium whitespace-nowrap text-gray-950',
'text-base font-medium whitespace-nowrap text-zinc-100',
'data-disabled:bg-white/15 data-disabled:opacity-40 data-hover:bg-white/20',
),
outline: clsx(
'inline-flex items-center justify-center px-2 py-[calc(--spacing(1.5)-1px)]',
'rounded-lg border border-transparent shadow-sm ring-1 ring-black/10',
'text-sm font-medium whitespace-nowrap text-gray-950',
'data-disabled:bg-transparent data-disabled:opacity-40 data-hover:bg-gray-50',
'inline-flex items-center justify-center px-4 py-[calc(--spacing(1.5)-1px)]',
'rounded-lg border border-transparent shadow-sm ring-1 ring-white/5',
'text-sm font-medium whitespace-nowrap text-zinc-100',
'data-disabled:bg-transparent data-disabled:opacity-40 data-hover:bg-zinc-900',
),
}
+24 -23
View File
@@ -39,7 +39,7 @@ function GameVaultLogo() {
return (
<div className="inline-flex items-center gap-x-2 text-xl font-bold">
<img src="/icons/gamevault.png" alt="GameVault Logo" className="size-8" />
<span className="relative whitespace-nowrap text-purple-900">
<span className="relative whitespace-nowrap text-zinc-100">
<svg
aria-hidden="true"
viewBox="0 0 418 42"
@@ -56,7 +56,7 @@ function GameVaultLogo() {
function GameVaultPlus() {
return (
<div className="inline-flex items-center gap-x-1 rounded-full bg-gray-100 px-2 py-1 text-xs">
<div className="inline-flex items-center gap-x-1 rounded-full bg-zinc-800 px-2 py-1 text-xs">
<img
src="/icons/gamevault-plus.png"
alt="GameVault+ icon"
@@ -69,7 +69,7 @@ function GameVaultPlus() {
function ComingSoon() {
return (
<div className="inline-flex items-center gap-x-1 rounded-full bg-gray-100 px-2 py-1 text-xs">
<div className="inline-flex items-center gap-x-1 rounded-full bg-zinc-900 px-2 py-1 text-xs">
coming soon&trade;
</div>
)
@@ -121,6 +121,7 @@ const projects: Array<{
'Installer/setup games': true,
'Portable games': true,
'Archives support': 'All 7-zip formats',
'Automatic import': 'Bulk-import tool',
},
Metadata: {
'Additional with plugins': false,
@@ -230,7 +231,7 @@ const projects: Array<{
'Non-versioned layout': true,
'Automatic import': true,
'Archives support': 'All 7-zip formats',
'Portable games': true
'Portable games': true,
},
Metadata: {
IGDB: true,
@@ -279,14 +280,14 @@ function Header() {
)
}
function PricingCards() {
function ProjectCards() {
return (
<div className="relative py-24">
<Gradient className="absolute inset-x-2 top-48 bottom-0 rounded-4xl ring-1 ring-black/5 ring-inset" />
<Container className="relative">
<div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
{projects.map((tier, tierIndex) => (
<PricingCard key={tierIndex} tier={tier} />
<ProjectCard key={tierIndex} tier={tier} />
))}
</div>
</Container>
@@ -294,22 +295,22 @@ function PricingCards() {
)
}
function PricingCard({ tier }: { tier: (typeof projects)[number] }) {
function ProjectCard({ tier }: { tier: (typeof projects)[number] }) {
return (
<div className="-m-2 grid grid-cols-1 rounded-4xl shadow-[inset_0_0_2px_1px_#ffffff4d] ring-1 ring-black/5 max-lg:mx-auto max-lg:w-full max-lg:max-w-md">
<div className="-m-2 grid grid-cols-1 rounded-4xl shadow-[inset_0_0_2px_1px_#ffffff4d] ring-1 ring-white/5 max-lg:mx-auto max-lg:w-full max-lg:max-w-md">
<div className="grid grid-cols-1 rounded-4xl p-2 shadow-md shadow-black/5">
<div className="rounded-3xl bg-white p-10 pb-9 shadow-2xl ring-1 ring-black/5">
<div className="rounded-3xl bg-zinc-900 p-10 pb-9 shadow-2xl ring-1 ring-black/5">
<div className="flex w-full items-center justify-center pb-8">
{tier.logo()}
</div>
<Subheading>{tier.name}</Subheading>
<p className="mt-2 text-sm/6 text-gray-950/75">{tier.description}</p>
<p className="mt-2 text-sm/6 text-zinc-100/75">{tier.description}</p>
<div className="mt-8">
<Button href={tier.href}>Learn more &rarr;</Button>
</div>
<div className="mt-8">
<h3 className="text-sm/6 font-medium text-gray-950">
<h3 className="text-sm/6 font-medium text-zinc-100">
Key features:
</h3>
<ul className="mt-3 space-y-3">
@@ -324,10 +325,10 @@ function PricingCard({ tier }: { tier: (typeof projects)[number] }) {
)
}
function PricingTable({
selectedTier: selectedProject,
function ProjectTable({
selectedProject,
}: {
selectedTier: (typeof projects)[number]
selectedProject: (typeof projects)[number]
}) {
function onlyUnique<T>(value: T, index: number, array: Array<T>) {
return array.indexOf(value) === index
@@ -385,7 +386,7 @@ function PricingTable({
<Menu>
<MenuButton className="flex items-center justify-between gap-2 font-medium">
{selectedProject.name}
<ChevronUpDownIcon className="size-4 fill-gray-900" />
<ChevronUpDownIcon className="size-4 fill-zinc-100" />
</MenuButton>
<MenuItems
anchor="bottom start"
@@ -444,7 +445,7 @@ function PricingTable({
colSpan={4}
className="px-0 pt-10 pb-0 group-first-of-type:pt-5"
>
<div className="-mx-4 rounded-lg bg-gray-50 px-4 py-3 text-sm/6 font-semibold">
<div className="-mx-4 rounded-lg bg-zinc-900 px-4 py-3 text-sm/6 font-semibold">
{section}
</div>
</th>
@@ -452,11 +453,11 @@ function PricingTable({
{features[section].map((name) => (
<tr
key={name}
className="border-b border-gray-100 last:border-none"
className="border-b border-zinc-800 last:border-none"
>
<th
scope="row"
className="px-0 py-4 text-sm/6 font-normal text-gray-600"
className="px-0 py-4 text-sm/6 font-normal text-zinc-200"
>
{name}
</th>
@@ -488,7 +489,7 @@ function PricingTable({
</span>
</>
) : (
<div className="text-xs">{value}</div>
<div className="text-xs text-zinc-400">{value}</div>
)}
</td>
)
@@ -514,10 +515,10 @@ function FeatureItem({
return (
<li
data-disabled={disabled ? true : undefined}
className="flex items-center gap-4 text-sm/6 text-gray-950/75 data-disabled:text-gray-950/25"
className="flex items-center gap-4 text-sm/6 text-zinc-100/75 data-disabled:text-zinc-100/40"
>
<span className="inline-flex h-6 items-center self-start">
<PlusIcon className="size-3.75 shrink-0 fill-gray-950/25" />
<PlusIcon className="size-3.75 shrink-0 fill-zinc-100/25" />
</span>
{disabled && <span className="sr-only">Coming soon:</span>}
{description}
@@ -553,8 +554,8 @@ export default function Pricing() {
<Navbar />
</Container>
<Header />
<PricingCards />
<PricingTable selectedTier={tier} />
<ProjectCards />
<ProjectTable selectedProject={tier} />
<Footer />
</main>
)
+6 -6
View File
@@ -226,14 +226,14 @@ function DownloadCard({
return (
<div className="-m-2 grid grid-cols-1 rounded-4xl shadow-[inset_0_0_2px_1px_#ffffff4d] ring-1 ring-black/5 max-lg:mx-auto max-lg:w-full max-lg:max-w-md">
<div className="grid grid-cols-1 rounded-4xl p-2 shadow-md shadow-black/5">
<div className="flex h-full flex-col justify-between rounded-3xl bg-white p-10 pb-9 shadow-2xl ring-1 ring-black/5">
<div className="flex h-full flex-col justify-between rounded-3xl bg-zinc-900/50 p-10 pb-9 shadow-2xl ring-1 ring-white/5">
<div>
<div className="flex w-full items-center justify-center gap-x-4">
{data.icon()}
<Heading>{data.name}</Heading>
</div>
<p className="mt-3 text-sm/6 text-gray-950/75">
<p className="mt-3 text-sm/6 text-zinc-100/75">
{data.description}
</p>
</div>
@@ -266,11 +266,11 @@ export default function DownloadCards() {
<Container>
<div className="flex flex-col items-center">
<Listbox value={currentVersion} onChange={setCurrentVersion}>
<Label className="block text-sm/6 font-medium text-gray-900">
<Label className="block text-sm/6 font-medium text-zinc-100">
Version
</Label>
<div className="relative mt-2">
<ListboxButton className="grid w-full min-w-[10rem] cursor-default grid-cols-1 rounded-md bg-white py-1.5 pr-2 pl-3 text-left text-gray-900 outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-600 sm:text-sm/6">
<ListboxButton className="grid w-full min-w-[10rem] cursor-default grid-cols-1 rounded-md bg-zinc-900 py-1.5 pr-2 pl-3 text-left text-zinc-100 outline-1 -outline-offset-1 outline-zinc-800 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-600 sm:text-sm/6">
<span className="col-start-1 row-start-1 truncate pr-6">
{currentVersion}
</span>
@@ -282,13 +282,13 @@ export default function DownloadCards() {
<ListboxOptions
transition
className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg outline-1 outline-black/5 data-leave:transition data-leave:duration-100 data-leave:ease-in data-closed:data-leave:opacity-0 sm:text-sm"
className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-zinc-900 py-1 text-base shadow-lg outline-1 outline-white/5 data-leave:transition data-leave:duration-100 data-leave:ease-in data-closed:data-leave:opacity-0 sm:text-sm"
>
{Object.keys(releasePages).map((version) => (
<ListboxOption
key={version}
value={version}
className="group relative cursor-default py-2 pr-9 pl-3 text-gray-900 select-none data-focus:bg-blue-600 data-focus:text-white data-focus:outline-hidden"
className="group relative cursor-default py-2 pr-9 pl-3 text-zinc-100 select-none data-focus:bg-blue-600 data-focus:text-white data-focus:outline-hidden"
>
<span className="block truncate font-normal group-data-selected:font-semibold">
{version}
+34 -36
View File
@@ -12,11 +12,11 @@ function CallToAction() {
<div className="relative pt-20 pb-16 text-center sm:py-24">
<hgroup>
<Subheading>Get started</Subheading>
<p className="mt-6 text-3xl font-medium tracking-tight text-gray-950 sm:text-5xl">
<p className="mt-6 text-3xl font-medium tracking-tight text-white sm:text-5xl">
Ready to dive in?
</p>
</hgroup>
<p className="mx-auto mt-6 max-w-xs text-sm/6 text-gray-500">
<p className="mx-auto mt-6 max-w-xs text-sm/6 text-zinc-400">
Get started hosting Drop in 10 minutes or less, and explore all the
features it has to offer.
</p>
@@ -33,7 +33,7 @@ function CallToAction() {
}
function SitemapHeading({ children }: { children: React.ReactNode }) {
return <h3 className="text-sm/6 font-medium text-gray-950/50">{children}</h3>
return <h3 className="text-sm/6 font-medium text-zinc-100/50">{children}</h3>
}
function SitemapLinks({ children }: { children: React.ReactNode }) {
@@ -45,7 +45,7 @@ function SitemapLink(props: React.ComponentPropsWithoutRef<typeof Link>) {
<li>
<Link
{...props}
className="font-medium text-gray-950 data-hover:text-gray-950/75"
className="font-medium text-zinc-100 data-hover:text-zinc-100/75"
/>
</li>
)
@@ -164,8 +164,8 @@ function SocialLinks() {
function Copyright() {
return (
<div className="text-xs text-gray-500">
&copy; {new Date().getFullYear()} Drop OSS. Licensed under AGPLv3 and
<div className="text-xs text-zinc-400">
&copy; {new Date().getFullYear()} Drop OSS. Website licensed under AGPLv3 and
Tailwind UI Plus (where applicable).
</div>
)
@@ -173,39 +173,37 @@ function Copyright() {
export function Footer() {
return (
<footer>
<Gradient className="relative">
<div className="absolute inset-2 rounded-4xl bg-white/80" />
<Container>
<CallToAction />
<PlusGrid className="pb-16">
<PlusGridRow>
<div className="grid grid-cols-2 gap-y-10 pb-6 lg:grid-cols-6 lg:gap-8">
<div className="col-span-2 flex">
<PlusGridItem className="pt-6 lg:pb-6">
<Logo className="h-9" />
</PlusGridItem>
</div>
<div className="col-span-2 grid grid-cols-2 gap-x-8 gap-y-12 lg:col-span-4 lg:grid-cols-subgrid lg:pt-6">
<Sitemap />
</div>
</div>
</PlusGridRow>
<PlusGridRow className="flex justify-between">
<div>
<PlusGridItem className="py-3">
<Copyright />
<footer className="relative m-2 overflow-hidden rounded-4xl">
<Gradient className="absolute inset-0 rounded-4xl" />
<Container>
<CallToAction />
<PlusGrid className="pb-16">
<PlusGridRow>
<div className="grid grid-cols-2 gap-y-10 pb-6 lg:grid-cols-6 lg:gap-8">
<div className="col-span-2 flex">
<PlusGridItem className="pt-6 lg:pb-6">
<Logo className="h-9" />
</PlusGridItem>
</div>
<div className="flex">
<PlusGridItem className="flex items-center gap-8 py-3">
<SocialLinks />
</PlusGridItem>
<div className="col-span-2 grid grid-cols-2 gap-x-8 gap-y-12 lg:col-span-4 lg:grid-cols-subgrid lg:pt-6">
<Sitemap />
</div>
</PlusGridRow>
</PlusGrid>
</Container>
</Gradient>
</div>
</PlusGridRow>
<PlusGridRow className="flex justify-between">
<div>
<PlusGridItem className="py-3">
<Copyright />
</PlusGridItem>
</div>
<div className="flex">
<PlusGridItem className="flex items-center gap-8 py-3">
<SocialLinks />
</PlusGridItem>
</div>
</PlusGridRow>
</PlusGrid>
</Container>
</footer>
)
}
+2 -2
View File
@@ -9,7 +9,7 @@ export function Gradient({
{...props}
className={clsx(
className,
'bg-linear-115 from-blue-100 from-28% via-sky-200 via-70% to-cyan-100 sm:bg-linear-145',
'bg-linear-115 from-blue-900 from-28% via-sky-800 via-70% to-cyan-900 sm:bg-linear-145 opacity-30',
)}
/>
)
@@ -21,7 +21,7 @@ export function GradientBackground() {
<div
className={clsx(
'absolute -top-44 -right-60 h-60 w-xl transform-gpu md:right-0',
'bg-linear-115 from-blue-200 from-28% via-sky-100 via-70% to-blue-200',
'bg-linear-115 from-blue-700 from-28% via-sky-800 via-70% to-blue-600',
'rotate-[-10deg] rounded-full blur-3xl',
)}
/>
+7 -7
View File
@@ -6,22 +6,22 @@ const components: MDXComponents = {
<p className="my-8 text-base/8 first:mt-0 last:mb-0">{children}</p>
),
h1: ({ children }) => (
<h2 className="mt-12 mb-10 text-4xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0">
<h2 className="mt-12 mb-10 text-4xl/8 font-medium tracking-tight text-zinc-100 first:mt-0 last:mb-0">
{children}
</h2>
),
h2: ({ children }) => (
<h2 className="mt-12 mb-10 text-2xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0">
<h2 className="mt-12 mb-10 text-2xl/8 font-medium tracking-tight text-zinc-100 first:mt-0 last:mb-0">
{children}
</h2>
),
h3: ({ children }) => (
<h3 className="mt-12 mb-10 text-xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0">
<h3 className="mt-12 mb-10 text-xl/8 font-medium tracking-tight text-zinc-100 first:mt-0 last:mb-0">
{children}
</h3>
),
blockquote: ({ children }) => (
<blockquote className="my-10 border-l-2 border-l-gray-300 pl-6 text-base/8 text-gray-950 first:mt-0 last:mb-0">
<blockquote className="my-10 border-l-2 border-l-zinc-700 pl-6 text-base/8 text-zinc-100 first:mt-0 last:mb-0">
{children}
</blockquote>
),
@@ -32,9 +32,9 @@ const components: MDXComponents = {
className="w-full rounded-2xl"
/>
),
hr: () => <hr className="my-8 border-t border-gray-200" />,
hr: () => <hr className="my-8 border-t border-zinc-800" />,
strong: ({ children }) => (
<strong className="font-semibold text-gray-950">{children}</strong>
<strong className="font-semibold text-zinc-100">{children}</strong>
),
code: ({ children }) => (
<>
@@ -60,7 +60,7 @@ const components: MDXComponents = {
return (
<Link
{...props}
className="font-medium text-blue-600 underline decoration-blue-400 underline-offset-4 data-hover:decoration-blue-600"
className="font-medium text-blue-400 underline decoration-blue-400 underline-offset-4 data-hover:decoration-blue-600"
>
{props.children}
</Link>
+5 -5
View File
@@ -27,7 +27,7 @@ function DesktopNav() {
<PlusGridItem key={href} className="relative flex">
<Link
href={href}
className="flex items-center px-4 py-3 text-base font-medium text-gray-950 bg-blend-multiply data-hover:bg-black/2.5"
className="flex items-center px-4 py-3 text-base font-medium text-zinc-100 bg-blend-multiply data-hover:bg-white/2.5"
>
{label}
</Link>
@@ -40,7 +40,7 @@ function DesktopNav() {
function MobileNavButton() {
return (
<DisclosureButton
className="flex size-12 items-center justify-center self-center rounded-lg data-hover:bg-black/5 lg:hidden"
className="flex size-12 items-center justify-center self-center rounded-lg data-hover:bg-white/5 lg:hidden"
aria-label="Open main menu"
>
<Bars2Icon className="size-6" />
@@ -63,15 +63,15 @@ function MobileNav() {
}}
key={href}
>
<Link href={href} className="text-base font-medium text-gray-950">
<Link href={href} className="text-base font-medium text-zinc-100">
{label}
</Link>
</motion.div>
))}
</div>
<div className="absolute left-1/2 w-screen -translate-x-1/2">
<div className="absolute inset-x-0 top-0 border-t border-black/5" />
<div className="absolute inset-x-0 top-2 border-t border-black/5" />
<div className="absolute inset-x-0 top-0 border-t border-white/5" />
<div className="absolute inset-x-0 top-2 border-t border-white/5" />
</div>
</DisclosurePanel>
)
+11 -11
View File
@@ -26,14 +26,14 @@ function FeaturedPosts() {
const postAuthors = fetchPostAuthors()
return (
<div className="mt-16 bg-linear-to-t from-gray-100 pb-14">
<div className="mt-16 pb-14">
<Container>
<h2 className="text-2xl font-medium tracking-tight">Featured</h2>
<div className="mt-6 grid grid-cols-1 gap-8 lg:grid-cols-3">
{featuredPosts.map((post) => (
<div
key={post._meta.path}
className="relative flex flex-col rounded-3xl bg-white p-2 shadow-md ring-1 shadow-black/5 ring-black/5"
className="relative flex flex-col rounded-3xl bg-zinc-900 p-2 shadow-md ring-1 shadow-white/5 ring-white/5"
>
{post.image && (
<img
@@ -43,7 +43,7 @@ function FeaturedPosts() {
/>
)}
<div className="flex flex-1 flex-col p-8">
<div className="text-sm/5 text-gray-700">
<div className="text-sm/5 text-zinc-400">
{dayjs(post.date).format('dddd, MMMM D, YYYY')}
</div>
<div className="mt-2 text-base/7 font-medium">
@@ -52,7 +52,7 @@ function FeaturedPosts() {
{post.title}
</Link>
</div>
<div className="mt-2 flex-1 text-sm/6 text-gray-500">
<div className="mt-2 flex-1 text-sm/6 text-zinc-400">
{post.excerpt}
</div>
{postAuthors[post.author ?? ''] && (
@@ -62,7 +62,7 @@ function FeaturedPosts() {
src={postAuthors[post.author ?? ''].avatar}
className="aspect-square size-6 rounded-full object-cover"
/>
<div className="text-sm/5 text-gray-700">
<div className="text-sm/5 text-zinc-300">
{postAuthors[post.author ?? ''].name}
</div>
</div>
@@ -94,7 +94,7 @@ function Posts({ page, category }: { page: number; category?: string }) {
{posts.map((post) => (
<div
key={post._meta.path}
className="relative grid grid-cols-1 border-b border-b-gray-100 py-10 first:border-t first:border-t-gray-200 max-sm:gap-3 sm:grid-cols-3"
className="relative grid grid-cols-1 border-b border-b-zinc-900 py-10 first:border-t first:border-t-zinc-900 max-sm:gap-3 sm:grid-cols-3"
>
<div>
<div className="text-sm/5 max-sm:text-gray-700 sm:font-medium">
@@ -109,7 +109,7 @@ function Posts({ page, category }: { page: number; category?: string }) {
className="aspect-square size-6 rounded-full object-cover"
/>
}
<div className="text-sm/5 text-gray-700">
<div className="text-sm/5 text-zinc-300">
{postAuthors[post.author ?? ''].name}
</div>
</div>
@@ -117,7 +117,7 @@ function Posts({ page, category }: { page: number; category?: string }) {
</div>
<div className="sm:col-span-2 sm:max-w-2xl">
<h2 className="text-sm/5 font-medium">{post.title}</h2>
<p className="mt-3 text-sm/6 text-gray-500">{post.excerpt}</p>
<p className="mt-3 text-sm/6 text-zinc-300">{post.excerpt}</p>
<div className="mt-4">
<Link
href={post.url}
@@ -180,9 +180,9 @@ function Pagination({
data-active={i + 1 === page ? true : undefined}
className={clsx(
'size-7 rounded-lg text-center text-sm/7 font-medium',
'data-hover:bg-gray-100',
'data-active:shadow-sm data-active:ring-1 data-active:ring-black/10',
'data-active:data-hover:bg-gray-50',
'data-hover:bg-zinc-900',
'data-active:shadow-sm data-active:ring-1 data-active:ring-white/10',
'data-active:data-hover:bg-zinc-900',
)}
>
{i + 1}
+5 -5
View File
@@ -28,10 +28,10 @@ export function PlusGridRow({
aria-hidden="true"
className="absolute inset-y-0 left-1/2 -z-10 w-screen -translate-x-1/2"
>
<div className="absolute inset-x-0 top-0 border-t border-black/5"></div>
<div className="absolute inset-x-0 top-2 border-t border-black/5"></div>
<div className="absolute inset-x-0 bottom-0 hidden border-b border-black/5 group-last/row:block"></div>
<div className="absolute inset-x-0 bottom-2 hidden border-b border-black/5 group-last/row:block"></div>
<div className="absolute inset-x-0 top-0 border-t border-white/5"></div>
<div className="absolute inset-x-0 top-2 border-t border-white/5"></div>
<div className="absolute inset-x-0 bottom-0 hidden border-b border-white/5 group-last/row:block"></div>
<div className="absolute inset-x-0 bottom-2 hidden border-b border-white/5 group-last/row:block"></div>
</div>
{children}
</div>
@@ -83,7 +83,7 @@ export function PlusGridIcon({
aria-hidden="true"
className={clsx(
className,
'absolute size-[15px] fill-black/10',
'absolute size-[15px] fill-white/10',
yClass,
xClass,
)}
+4 -13
View File
@@ -76,19 +76,10 @@ function SponsorCard({
ref={ref}
style={{ opacity }}
{...props}
className="relative flex aspect-9/16 w-64 shrink-0 snap-start scroll-ml-(--scroll-padding) flex-col justify-end overflow-hidden rounded-3xl sm:aspect-3/4 sm:w-72"
className="relative flex w-64 rounded-3xl sm:w-72 bg-black"
>
<img
alt=""
src={img}
className="absolute inset-x-0 top-0 aspect-square w-full object-contain"
/>
<div
aria-hidden="true"
className="absolute inset-0 rounded-3xl bg-linear-to-t from-black from-[calc(7/16*100%)] ring-1 ring-gray-950/10 ring-inset sm:from-25%"
/>
<figure className="relative p-10">
<figcaption className="mt-6 border-t border-white/20 pt-6">
<figcaption className="pb-3 border-b border-white/20">
<p className="text-sm/6 font-medium text-white">{name}</p>
<p className="text-sm/6 font-medium">
<span className="bg-linear-to-r from-sky-300 from-28% via-blue-200 via-70% to-cyan-300 bg-clip-text text-transparent">
@@ -272,8 +263,8 @@ export function Sponsors() {
data-active={activeIndex === i ? true : undefined}
aria-label={`Scroll to sponsorship from ${name}`}
className={clsx(
'size-2.5 cursor-pointer rounded-full border border-transparent bg-gray-300 transition',
'data-active:bg-gray-400 data-hover:bg-gray-400',
'size-2.5 cursor-pointer rounded-full border border-transparent bg-zinc-600 transition',
'data-active:bg-blue-700 data-hover:bg-zinc-900',
'forced-colors:data-active:bg-[Highlight] forced-colors:data-focus:outline-offset-4',
)}
/>
+7 -6
View File
@@ -27,8 +27,8 @@ function Person({
<img alt="" src={img} className="size-12 rounded-full" />
<div className="text-sm/6">
<h3 className="font-medium group-hover:underline">{name}</h3>
<p className="text-gray-500">{description}</p>
<p className="text-xs font-semibold text-gray-400 uppercase">
<p className="text-zinc-400">{description}</p>
<p className="text-xs font-semibold text-blue-600/80">
{contributions} contribution(s)
</p>
</div>
@@ -105,14 +105,14 @@ export function Team() {
</Lead>
<div className="mt-12 grid grid-cols-1 gap-12 lg:grid-cols-2">
<div className="max-w-lg">
<p className="text-sm/6 text-gray-600">
<p className="text-sm/6 text-zinc-400">
Drop OSS was started, mostly on a whim, in response to frustrations
with the controlled nature of DRM games, and the missing comforts of
DRM-free games. Since then, we&apos;ve put together a small circle
of dedicated maintainers and contributors to develop Drop and all
its amazing features.
</p>
<p className="mt-8 text-sm/6 text-gray-600">
<p className="mt-8 text-sm/6 text-zinc-400">
If you know a little code, you can help out! We heavily encourage
contributions, especially if you&apos;re passionate about the
project and enjoy writing code. We use a variety of stacks across
@@ -124,8 +124,9 @@ export function Team() {
className="w-full sm:w-auto"
href="https://developer.droposs.org/contributing"
target="_blank"
variant="outline"
>
Contribute
Contribute &rarr;
</Button>
</div>
</div>
@@ -133,7 +134,7 @@ export function Team() {
<Subheading id="team" as="h3" className="mt-24">
The team
</Subheading>
<hr className="mt-6 border-t border-gray-200" />
<hr className="mt-6 border-t border-zinc-800" />
<ul
role="list"
className="mx-auto mt-16 grid grid-cols-1 gap-8 sm:grid-cols-3 lg:grid-cols-4"
+2 -2
View File
@@ -19,7 +19,7 @@ export function Heading({
data-dark={dark ? 'true' : undefined}
className={clsx(
className,
'text-4xl font-medium tracking-tighter text-pretty text-gray-950 data-dark:text-white sm:text-6xl',
'text-4xl font-medium tracking-tighter text-pretty text-white sm:text-6xl',
)}
/>
)
@@ -37,7 +37,7 @@ export function Subheading({
data-dark={dark ? 'true' : undefined}
className={clsx(
className,
'font-mono text-xs/5 font-semibold tracking-widest text-gray-500 uppercase data-dark:text-gray-400',
'font-mono text-xs/5 font-semibold tracking-widest uppercase text-zinc-500',
)}
/>
)
+35
View File
@@ -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
}
}
+52
View File
@@ -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"
}
+26
View File
@@ -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:
+324 -335
View File
File diff suppressed because it is too large Load Diff
+37 -5
View File
@@ -1,13 +1,45 @@
onlyBuiltDependencies:
- "@bufbuild/buf"
- "@parcel/watcher"
- "@prisma/client"
- "@prisma/engines"
- "@tailwindcss/oxide"
- '@bufbuild/buf'
- '@parcel/watcher'
- '@prisma/client'
- '@prisma/engines'
- '@tailwindcss/oxide'
- argon2
- esbuild
- prisma
- sharp
- unrs-resolver
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'
shamefullyHoist: true
+8
View File
@@ -590,6 +590,7 @@ version = "0.16.3"
dependencies = [
"anyhow",
"async-trait",
"droplet_types",
"dyn-clone",
"futures",
"getrandom 0.3.4",
@@ -609,6 +610,13 @@ dependencies = [
"x509-parser 0.17.0",
]
[[package]]
name = "droplet_types"
version = "0.1.0"
dependencies = [
"serde",
]
[[package]]
name = "dyn-clone"
version = "1.0.20"
+4 -2
View File
@@ -3,10 +3,11 @@ use std::{
sync::{Arc, LazyLock},
};
use droplet_rs::manifest::ManifestWriterFactory;
use log::info;
use protobuf::Message;
use serde_json::json;
use tokio::{spawn, sync::Semaphore};
use tokio::{io::{BufWriter, SimplexStream}, spawn, sync::Semaphore};
use crate::{
proto::{
@@ -62,7 +63,8 @@ pub async fn generate_manifest_rpc(
.await;
});
},
Some(READER_SEMAPHORE.clone()),
None::<&dyn ManifestWriterFactory<Writer = SimplexStream>>, // Dummy type signature, not actually used
Some(&READER_SEMAPHORE),
)
.await?;