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. # It is not intended for manual editing.
version = 4 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]] [[package]]
name = "android_system_properties" name = "android_system_properties"
version = "0.1.5" version = "0.1.5"
@@ -105,10 +120,10 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
"synstructure", "synstructure 0.13.2",
] ]
[[package]] [[package]]
@@ -117,10 +132,10 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
"synstructure", "synstructure 0.13.2",
] ]
[[package]] [[package]]
@@ -129,9 +144,9 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -140,9 +155,9 @@ version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -190,6 +205,21 @@ dependencies = [
"tokio", "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]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
@@ -295,9 +325,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -534,9 +564,9 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -577,7 +607,7 @@ dependencies = [
[[package]] [[package]]
name = "droplet-rs" name = "droplet-rs"
version = "0.14.1" version = "0.16.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -586,11 +616,14 @@ dependencies = [
"getrandom 0.3.4", "getrandom 0.3.4",
"hex", "hex",
"humansize", "humansize",
"libarchive-drop",
"rcgen", "rcgen",
"ring", "ring",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
"speedometer",
"test-generator",
"time", "time",
"tokio", "tokio",
"uuid", "uuid",
@@ -640,6 +673,28 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.3.0" version = "2.3.0"
@@ -737,9 +792,9 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -809,6 +864,18 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "gloo-timers" name = "gloo-timers"
version = "0.3.0" version = "0.3.0"
@@ -1193,9 +1260,9 @@ version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -1261,6 +1328,24 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 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]] [[package]]
name = "libc" name = "libc"
version = "0.2.178" version = "0.2.178"
@@ -1335,6 +1420,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 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]] [[package]]
name = "mio" name = "mio"
version = "1.1.1" version = "1.1.1"
@@ -1421,6 +1515,15 @@ dependencies = [
"objc2", "objc2",
] ]
[[package]]
name = "object"
version = "0.37.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "oid-registry" name = "oid-registry"
version = "0.7.1" version = "0.7.1"
@@ -1530,6 +1633,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "1.11.1" version = "1.11.1"
@@ -1569,6 +1678,15 @@ dependencies = [
"zerocopy", "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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.103" version = "1.0.103"
@@ -1654,13 +1772,22 @@ dependencies = [
"windows-sys 0.60.2", "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]] [[package]]
name = "quote" name = "quote"
version = "1.0.43" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
] ]
[[package]] [[package]]
@@ -1887,6 +2014,12 @@ dependencies = [
"ordered-multimap", "ordered-multimap",
] ]
[[package]]
name = "rustc-demangle"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "2.1.1" version = "2.1.1"
@@ -2084,9 +2217,9 @@ version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -2180,6 +2313,15 @@ dependencies = [
"windows-sys 0.60.2", "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]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.1" version = "1.2.1"
@@ -2198,14 +2340,36 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 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]] [[package]]
name = "syn" name = "syn"
version = "2.0.114" version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"unicode-ident", "unicode-ident",
] ]
@@ -2218,15 +2382,27 @@ dependencies = [
"futures-core", "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]] [[package]]
name = "synstructure" name = "synstructure"
version = "0.13.2" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -2263,6 +2439,18 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.69" version = "1.0.69"
@@ -2287,9 +2475,9 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -2298,9 +2486,9 @@ version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -2390,9 +2578,9 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -2507,6 +2695,18 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" 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]] [[package]]
name = "unit-prefix" name = "unit-prefix"
version = "0.5.2" version = "0.5.2"
@@ -2627,7 +2827,7 @@ version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
dependencies = [ dependencies = [
"quote", "quote 1.0.43",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
] ]
@@ -2638,9 +2838,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -2748,9 +2948,9 @@ version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -2759,9 +2959,9 @@ version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -3095,10 +3295,10 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
"synstructure", "synstructure 0.13.2",
] ]
[[package]] [[package]]
@@ -3116,9 +3316,9 @@ version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
@@ -3136,10 +3336,10 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
"synstructure", "synstructure 0.13.2",
] ]
[[package]] [[package]]
@@ -3176,9 +3376,9 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2 1.0.103",
"quote", "quote 1.0.43",
"syn", "syn 2.0.114",
] ]
[[package]] [[package]]
+1 -1
View File
@@ -11,7 +11,7 @@ clap = { version = "4.5.54", features = ["derive"] }
console = "0.16.2" console = "0.16.2"
dialoguer = "0.12.0" dialoguer = "0.12.0"
dirs = "6.0.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"] } fern = { version = "0.7.1", features = ["colored"] }
futures = "0.3.31" futures = "0.3.31"
indicatif = "0.18.3" indicatif = "0.18.3"
+23 -21
View File
@@ -3,7 +3,7 @@ use std::path::Path;
use crate::{ use crate::{
cli::UploadInfo, cli::UploadInfo,
commands::connect::{config::Config, config_option::ConfigOption}, commands::connect::{config::Config, config_option::ConfigOption},
manifest::{CompressionOption, DepotManifest, generate_v2_manifest}, manifest::{ClosureFactory, CompressionOption, DepotManifest, generate_v2_manifest},
operator_builder::OperatorBuilder, operator_builder::OperatorBuilder,
}; };
use futures::AsyncWriteExt; use futures::AsyncWriteExt;
@@ -12,13 +12,13 @@ use opendal::{FuturesAsyncWriter, Operator};
use tokio_util::compat::{Compat, FuturesAsyncWriteCompatExt}; use tokio_util::compat::{Compat, FuturesAsyncWriteCompatExt};
pub async fn upload( pub async fn upload(
info: &UploadInfo, upload_info: &UploadInfo,
config: Config, config: Config,
name: &Option<String>, name: &Option<String>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let game_id = &info.game_id; let game_id = upload_info.game_id.clone();
let path = &info.path; let path = upload_info.path.clone();
let version_id = &info.version_id; let version_id = upload_info.version_id.clone();
let operator = get_operator(config, name)?; let operator = get_operator(config, name)?;
@@ -27,28 +27,30 @@ pub async fn upload(
info!("Uploading chunks"); info!("Uploading chunks");
let v2_manifest = generate_v2_manifest( let v2_manifest = generate_v2_manifest(
Path::new(path), Path::new(&path),
async |id: String| { ClosureFactory::new(
info!("Uploading chunk id {id}"); async move |id: String| {
let writer = operator info!("Uploading chunk id {id}");
.writer(&format!("{game_id}/{version_id}/{id}")) let writer = operator
.await .writer(&format!("{game_id}/{version_id}/{id}"))
.unwrap() .await
.into_futures_async_write() .unwrap()
.compat_write(); .into_futures_async_write()
writer .compat_write();
}, writer
|writer: Compat<FuturesAsyncWriter>| async { },
writer.into_inner().close().await.unwrap(); |writer: Compat<FuturesAsyncWriter>| async {
}, writer.into_inner().close().await.unwrap();
},
),
) )
.await?; .await?;
info!("Finished uploading chunks"); info!("Finished uploading chunks");
existing_depot_manifest.append( existing_depot_manifest.append(
game_id.to_string(), upload_info.game_id.to_string(),
version_id.to_string(), upload_info.version_id.to_string(),
CompressionOption::None, CompressionOption::None,
); );
Ok(()) Ok(())
+2
View File
@@ -1,3 +1,5 @@
#![feature(async_fn_traits)]
use crate::commands::connect::config::manage_configuration; use crate::commands::connect::config::manage_configuration;
use crate::{ use crate::{
cli::{Cli, Commands}, cli::{Cli, Commands},
+58 -10
View File
@@ -1,8 +1,7 @@
use std::{collections::HashMap, path::Path}; use std::{collections::HashMap, path::Path};
use droplet_rs::manifest::{ use async_trait::async_trait;
Manifest, generate_manifest_rusty, generate_manifest_rusty_v2, use droplet_rs::manifest::{Manifest, ManifestWriterFactory, generate_manifest_rusty};
};
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
use log::info; use log::info;
use serde::{Deserialize, Serialize}; 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 where
W: AsyncWrite + Unpin, Writer: AsyncWrite + Unpin,
F: AsyncFn(String) -> W, Factory: AsyncFn(String) -> Writer,
CloseF: AsyncFn(W) 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( let progress_bar = ProgressBar::new(10_000).with_style(
ProgressStyle::default_bar() ProgressStyle::default_bar()
@@ -52,15 +100,15 @@ where
.unwrap(), .unwrap(),
); );
generate_manifest_rusty_v2( generate_manifest_rusty(
dir, dir,
|progress| { |progress| {
let progress_int = (progress * 100f32).round() as u64; let progress_int = (progress * 100f32).round() as u64;
progress_bar.set_position(progress_int); progress_bar.set_position(progress_int);
}, },
|log| progress_bar.suspend(|| info!("{}", log)), |log| progress_bar.suspend(|| info!("{}", log)),
factory, Some(&factory),
closer None,
) )
.await .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 views = fs.readdirSync(".").filter((view) => {
const expectedPath = `./${view}/package.json`; const expectedPath = `./${view}/package.json`;
return fs.existsSync(expectedPath); return fs.existsSync(expectedPath);
+1 -1
View File
@@ -36,7 +36,7 @@
as="div" as="div"
v-for="(nav, navIndex) in filteredNavigation" v-for="(nav, navIndex) in filteredNavigation"
:key="nav.id" :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 }" v-slot="{ open }"
:default-open="nav.deft" :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} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: [ content: [
@@ -7,7 +11,7 @@ export default {
"./plugins/**/*.{js,ts}", "./plugins/**/*.{js,ts}",
"./app.vue", "./app.vue",
"./error.vue", "./error.vue",
"../libs/drop-base/**/*.{js,vue,ts}", `${dropbase}/components/**/*.{js,vue,ts}`,
], ],
theme: { theme: {
extend: { 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" deranged = "=0.4.0"
dirs = "6.0.0" dirs = "6.0.0"
download_manager = { path = "./download_manager", version = "0.1.0" } # download manager download_manager = { path = "./download_manager", version = "0.1.0" } # download manager
droplet-rs = "0.7.3"
filetime = "0.2.25" filetime = "0.2.25"
futures-core = "0.3.31" futures-core = "0.3.31"
futures-lite = "2.6.0" futures-lite = "2.6.0"
@@ -1,7 +1,9 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
env,
sync::RwLock, sync::RwLock,
time::{Duration, Instant}, usize, time::{Duration, Instant},
usize,
}; };
use futures_util::StreamExt; use futures_util::StreamExt;
@@ -34,7 +36,7 @@ struct Depot {
manifest: Option<DepotManifest>, manifest: Option<DepotManifest>,
latest_speed: Option<usize>, // bytes per second latest_speed: Option<usize>, // bytes per second
current_downloads: SyncSemaphore, current_downloads: SyncSemaphore,
enabled: bool enabled: bool,
} }
pub struct DepotManager { pub struct DepotManager {
@@ -113,10 +115,16 @@ impl DepotManager {
for depot in &mut new_depots { for depot in &mut new_depots {
if let Err(sync_error) = self.sync_depot(depot).await { if let Err(sync_error) = self.sync_depot(depot).await {
warn!("failed to sync depot {}: {:?}", depot.endpoint, sync_error); 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(); let enabled = new_depots.iter().filter(|v| v.enabled).count();
if enabled == 0 { if enabled == 0 {
return Err(RemoteAccessError::NoDepots); return Err(RemoteAccessError::NoDepots);
+1 -1
View File
@@ -14,7 +14,7 @@ crossbeam-channel = "0.5.15"
ctr = "0.9.2" ctr = "0.9.2"
database = { path = "../database", version = "0.1.0" } database = { path = "../database", version = "0.1.0" }
download_manager = { path = "../download_manager", 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 = "*" futures-util = "*"
hex = "0.4.3" hex = "0.4.3"
log = "0.4.28" log = "0.4.28"
@@ -11,7 +11,7 @@ use download_manager::util::download_thread_control_flag::{
DownloadThreadControl, DownloadThreadControlFlag, DownloadThreadControl, DownloadThreadControlFlag,
}; };
use download_manager::util::progress_object::{ProgressHandle, ProgressObject, ProgressType}; 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::StreamExt;
use futures_util::stream::FuturesUnordered; use futures_util::stream::FuturesUnordered;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
@@ -13,7 +13,7 @@ use download_manager::util::download_thread_control_flag::{
DownloadThreadControl, DownloadThreadControlFlag, DownloadThreadControl, DownloadThreadControlFlag,
}; };
use download_manager::util::progress_object::ProgressHandle; use download_manager::util::progress_object::ProgressHandle;
use droplet_rs::manifest::ChunkData; use droplet_types::ChunkData;
use futures_util::StreamExt as _; use futures_util::StreamExt as _;
use log::{debug, info}; use log::{debug, info};
use remote::auth::generate_authorization_header; use remote::auth::generate_authorization_header;
-1
View File
@@ -10,7 +10,6 @@ bytes = "1.11.0"
chrono = "0.4.42" chrono = "0.4.42"
client = { path = "../client", version = "0.1.0" } client = { path = "../client", version = "0.1.0" }
database = { path = "../database", version = "0.1.0" } database = { path = "../database", version = "0.1.0" }
droplet-rs = "0.7.3"
gethostname = "1.0.2" gethostname = "1.0.2"
hex = "0.4.3" hex = "0.4.3"
http = "1.3.1" http = "1.3.1"
+1 -1
View File
@@ -27,7 +27,7 @@
}, },
"bundle": { "bundle": {
"active": true, "active": true,
"targets": ["nsis", "deb", "rpm", "dmg", "appimage"], "targets": ["nsis", "deb", "rpm", "dmg"],
"windows": { "windows": {
"nsis": { "nsis": {
"installMode": "both" "installMode": "both"
+1
View File
@@ -21,6 +21,7 @@ target/
/target /target
perf.data perf.data
perf.data.old
flamegraph.svg flamegraph.svg
*.json *.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" description = "Droplet is a `napi.rs` Rust/Node.js package full of high-performance and low-level utils for Drop"
[dependencies] [dependencies]
droplet_types = { path = "../droplet_types" }
hex = "0.4.3" hex = "0.4.3"
time = "0.3.41" time = "0.3.41"
ring = "0.17.14" ring = "0.17.14"
Submodule libraries/droplet/libarchive-rust deleted from fdb73ef2de
+3 -2
View File
@@ -1,13 +1,14 @@
#![deny(clippy::all)] #![deny(clippy::all)]
#![feature(impl_trait_in_bindings)] #![feature(impl_trait_in_bindings)]
#![feature(nonpoison_mutex)]
#![feature(sync_nonpoison)]
pub mod file_utils; pub mod file_utils;
pub mod manifest; pub mod manifest;
pub mod ssl; pub mod ssl;
pub mod versions; pub mod versions;
pub mod vm; pub mod vm;
extern crate libarchive_drop; pub use manifest::{CHUNK_SIZE, MAX_FILE_COUNT};
#[cfg(test)] #[cfg(test)]
pub mod tests; pub mod tests;
+15 -1
View File
@@ -1,8 +1,21 @@
use std::{env, path::PathBuf}; use std::{env, path::PathBuf};
use droplet_rs::manifest::generate_manifest_rusty; use droplet_rs::manifest::{ManifestWriterFactory, generate_manifest_rusty};
use tokio::runtime::Handle; 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] #[tokio::main]
pub async fn main() { pub async fn main() {
let mut args = env::args(); let mut args = env::args();
@@ -17,6 +30,7 @@ pub async fn main() {
|message| { |message| {
println!("{}", message); println!("{}", message);
}, },
Some(&SinkFactory {}),
None, None,
) )
.await .await
+217 -206
View File
@@ -1,162 +1,172 @@
use std::{ use std::{collections::HashMap, ops::Not, path::Path};
collections::HashMap,
mem,
path::Path,
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
};
use anyhow::anyhow; use anyhow::anyhow;
use async_trait::async_trait;
use futures::StreamExt;
use hex::ToHex as _; use hex::ToHex as _;
use humansize::{format_size, BINARY}; use humansize::{format_size, BINARY};
use serde::{Deserialize, Serialize};
use sha2::{Digest as _, Sha256}; use sha2::{Digest as _, Sha256};
use tokio::{ use tokio::io::AsyncWriteExt;
io::AsyncReadExt as _, use tokio::io::{AsyncReadExt as _, AsyncWrite};
join, use tokio::sync::Semaphore;
sync::{Mutex, Semaphore}, pub use droplet_types::{ChunkData, FileEntry, Manifest};
task::JoinSet,
};
#[derive(Serialize, Deserialize, Clone)]
pub struct FileEntry {
pub filename: String,
pub start: usize,
pub length: usize,
pub permissions: u32,
}
#[derive(Serialize, Deserialize, Clone)] pub const CHUNK_SIZE: u64 = 1024 * 1024 * 64;
pub struct ChunkData { pub const MAX_FILE_COUNT: usize = 512;
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;
use crate::versions::{ use crate::versions::{
create_backend_constructor, create_backend_constructor,
types::{VersionBackend, VersionFile}, types::{VersionBackend, VersionFile},
}; };
pub async fn generate_manifest_rusty<T: Fn(String), V: Fn(f32)>( #[async_trait]
dir: &Path, pub trait ManifestWriterFactory: Send + Sync {
progress_sfn: V, type Writer: AsyncWrite + Unpin;
log_sfn: T, async fn create(&self, id: String) -> anyhow::Result<Self::Writer>;
reader_semaphore: Option<Arc<Semaphore>>, async fn close(&self, writer: Self::Writer) -> anyhow::Result<()>;
) -> 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();
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?; let mut files = backend.list_files().await?;
files.sort_by_key(|b| std::cmp::Reverse(b.size)); files.sort_by(|a, b| b.size.cmp(&a.size));
// Filepath to chunk data
let mut chunks: Vec<Vec<(VersionFile, u64, u64)>> = Vec::new();
let mut current_chunk: Vec<(VersionFile, u64, u64)> = Vec::new();
log_sfn("organizing files into chunks...".to_string()); log_sfn("organising files into chunks...".to_string());
if required_single_file { let chunks = organise_files(files, backend.require_whole_files());
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);
}
log_sfn(format!( log_sfn(format!(
"organized into {} chunks, generating checksums...", "organized into {} chunks, generating checksums...",
chunks.len() 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 mut key = [0u8; 16];
let total_manifest_length = Arc::new(AtomicU64::new(0)); getrandom::fill(&mut key).map_err(|err| anyhow!("failed to generate key: {:?}", err))?;
// SAFETY: we .join_all() the futures using this let total_manifest_length = manifest
let backend: &'static (dyn VersionBackend + Send + Sync) = unsafe { mem::transmute(&*backend) }; .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(); Ok(Manifest {
let (send_log, mut recieve_log) = tokio::sync::mpsc::channel(16); version: "2".to_string(),
let chunks_length = chunks.len(); chunks: manifest,
for (index, chunk) in chunks.into_iter().enumerate() { size: total_manifest_length,
let send_log = send_log.clone(); key,
let total_manifest_length = total_manifest_length.clone(); })
let manifest = manifest.clone(); }
let reader_semaphore = reader_semaphore.clone();
futures.spawn(async move { fn organise_files(
let mut read_buf = vec![0u8; 1024 * 1024 * 8]; 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 uuid = uuid::Uuid::new_v4().to_string();
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
@@ -168,91 +178,92 @@ pub async fn generate_manifest_rusty<T: Fn(String), V: Fn(f32)>(
checksum: String::new(), checksum: String::new(),
iv, iv,
}; };
let mut chunk_length = 0u64;
let mut chunk_length = 0; let mut writer = match factory {
Some(factory) => Some(factory.create(uuid.clone()).await?),
None => None,
};
for (file, start, length) in chunk { for (file, start, length) in chunk {
let permit = if let Some(reader_semaphore) = &reader_semaphore { let permit = if let Some(semaphore) = &semaphore {
Some(reader_semaphore.acquire().await?) Some(semaphore.acquire().await?)
} else { } else {
None None
}; };
chunk_data.files.push(
let mut reader = backend.reader(&file, start, start + length).await?; read_and_generate_chunk_file_data(
backend,
let mut total = 0; &file,
start,
loop { length,
let amount = reader.read(&mut read_buf).await?; &mut hasher,
if amount == 0 { &mut read_buf,
break; &mut writer,
} )
total += amount; .await?,
hasher.update(&read_buf[0..amount]); );
}
if total as u64 > length {
panic!("read too much: target {}, got {}", length, total);
}
chunk_length += length; 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); drop(permit);
} }
if let Some(factory) = factory {
send_log factory.close(writer.expect("Failed to get writer")).await?;
.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);
let hash: String = hasher.finalize().encode_hex(); let hash: String = hasher.finalize().encode_hex();
chunk_data.checksum = hash; 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); Ok(results)
join!( }
async move { async fn read_and_generate_chunk_file_data<Writer>(
let mut current_progress = 0f32; backend: &(dyn VersionBackend + Sync + Send),
let total_progress = chunks_length as f32; file: &VersionFile,
while let Some(message) = recieve_log.recv().await { start: u64,
log_sfn(message); length: u64,
current_progress += 1.0f32; hasher: &mut Sha256,
progress_sfn((current_progress / total_progress) * 100.0f32) read_buf: &mut [u8],
} writer: &mut Option<Writer>,
}, ) -> anyhow::Result<FileEntry>
futures.join_all() where
); Writer: AsyncWrite + Unpin,
{
let mut reader = backend.reader(file, start, start + length).await?;
let manifest = manifest.lock().await; loop {
let manifest = manifest.clone(); let amount = reader.read(read_buf).await?;
let mut key = [0u8; 16]; if amount == 0 {
getrandom::fill(&mut key).map_err(|err| anyhow!("failed to generate key: {:?}", err))?; break;
}
if let Some(writer) = writer.as_mut() {
writer.write_all(&read_buf[0..amount]).await?;
}
hasher.update(&read_buf[0..amount]);
}
Ok(Manifest { Ok(FileEntry {
version: "2".to_string(), filename: file.relative_filename.clone(),
chunks: manifest, start: start.try_into().unwrap(),
size: total_manifest_length.fetch_add(0, Ordering::Relaxed), length: length.try_into().unwrap(),
key, permissions: file.permission,
}) })
} }
+3 -1
View File
@@ -4,8 +4,9 @@ extern crate test_generator;
use std::path::Path; use std::path::Path;
use test_generator::test_resources; 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")] #[test_resources("testfiles/**/*.7z")]
fn manifest_gen(resource: &str) { fn manifest_gen(resource: &str) {
@@ -22,6 +23,7 @@ fn manifest_gen(resource: &str) {
|message| { |message| {
println!("({}) {}", filepath.display(), message); println!("({}) {}", filepath.display(), message);
}, },
None::<&dyn ManifestWriterFactory<Writer = SimplexStream>>, // Dummy type signature, not actually used
None, None,
) )
.await .await
@@ -28,12 +28,12 @@ impl ZipVersionBackend {
} }
} }
struct ArchiveReader<'a> { struct ArchiveReader {
archive: FileReader, 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( fn poll_read(
mut self: std::pin::Pin<&mut Self>, mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>, _cx: &mut std::task::Context<'_>,
@@ -41,9 +41,8 @@ impl<'a> AsyncRead for ArchiveReader<'a> {
) -> std::task::Poll<std::io::Result<()>> { ) -> std::task::Poll<std::io::Result<()>> {
if let Some(block) = &mut self.prev_block { if let Some(block) = &mut self.prev_block {
let to_read = buf.remaining().min(block.len()); let to_read = buf.remaining().min(block.len());
let result = block.split_off(..to_read); let result = block.split_off(to_read);
let result = result.unwrap(); // SAFETY: above .min statement buf.put_slice(&result);
buf.put_slice(result);
// If the block is empty, we can read more // If the block is empty, we can read more
if block.is_empty() { if block.is_empty() {
@@ -52,27 +51,31 @@ impl<'a> AsyncRead for ArchiveReader<'a> {
return Poll::Ready(Ok(())); return Poll::Ready(Ok(()));
} }
} }
let block = match self.archive.read_block() { let prev_block_update = {
Ok(v) => v, let block = match self.archive.read_block() {
Err(err) => return Poll::Ready(Err(std::io::Error::other(err.to_string()))), Ok(v) => v,
}; Err(err) => return Poll::Ready(Err(std::io::Error::other(err.to_string()))),
};
let mut block = match block { let mut block = match block {
Some(v) => v, Some(v) => v,
None => return Poll::Ready(Ok(())), None => return Poll::Ready(Ok(())),
}; };
let write_amount = buf.remaining().min(block.len()); let write_amount = buf.remaining().min(block.len());
let to_write = block.split_off(..write_amount); let to_write = block.split_off(..write_amount);
let to_write = to_write.unwrap(); // SAFETY: above .min statement let to_write = to_write.unwrap(); // SAFETY: above .min statement
buf.put_slice(to_write); buf.put_slice(to_write);
if !block.is_empty() { if !block.is_empty() {
#[cfg(debug_assertions)] Some(block[buf.remaining()..].to_vec())
if self.prev_block.is_some() { } else {
panic!("replacing prev_block while it contains data") 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(())) Poll::Ready(Ok(()))
+12 -9
View File
@@ -5,10 +5,11 @@ use std::{
use anyhow::Result; use anyhow::Result;
use crate::versions::{ use crate::versions::{path_backend::PathVersionBackend, types::VersionBackend};
archive_backend::ZipVersionBackend, path_backend::PathVersionBackend, types::VersionBackend,
};
use crate::versions::archive_backend::ZipVersionBackend;
// libarchive backend is Linux-only for now
pub mod archive_backend; pub mod archive_backend;
pub mod path_backend; pub mod path_backend;
@@ -33,10 +34,13 @@ const SUPPORTED_FILE_EXTENSIONS: [&str; 11] = [
]; ];
pub mod types; pub mod types;
#[allow(clippy::type_complexity)] pub fn create_backend_constructor<'a, P>(
pub fn create_backend_constructor<'a>( path: P,
path: &Path, ) -> Option<Box<dyn FnOnce() -> Result<Box<dyn VersionBackend + Send + Sync + 'a>>>>
) -> Option<Box<dyn FnOnce() -> Result<Box<dyn VersionBackend + Send + Sync + 'a>>>> { where
P: AsRef<Path>,
{
let path = path.as_ref();
if !path.exists() { if !path.exists() {
return None; 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) { if SUPPORTED_FILE_EXTENSIONS.contains(&file_extension) {
let buf = path.to_path_buf(); let buf = path.to_path_buf();
return Some(Box::new(move || Ok(Box::new(ZipVersionBackend::new(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" version = "0.1.1"
authors = ["Jamie Winsor <reset@chef.io>", "Drop OSS"] authors = ["Jamie Winsor <reset@chef.io>", "Drop OSS"]
license = "Apache-2.0" 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" description = "A safe Rust API for authoring and extracting archives with libarchive"
keywords = ["libarchive", "archive", "tar", "zip"] keywords = ["libarchive", "archive", "tar", "zip"]
[dependencies] [dependencies]
libc = ">= 0.2.0" 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; pub mod util;
use libarchive::archive::{self, ReadFilter, ReadFormat}; use libarchive_drop::archive::{self, ReadFilter, ReadFormat};
use libarchive::reader::{self, Reader}; use libarchive_drop::reader::{self, Reader};
use libarchive::writer; use libarchive_drop::writer;
use std::fs::File; use std::fs::File;
#[test] #[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"> <section className="mt-16 grid grid-cols-1 lg:grid-cols-2 lg:gap-12">
<div className="max-w-lg"> <div className="max-w-lg">
<h2 className="text-2xl font-medium tracking-tight">Our mission</h2> <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 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 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 platforms like Steam have done so well, is that they provide awesome
features like worldwide play-together, social features, and useful features like worldwide play-together, social features, and useful
overlays and tools. We aim to replicate this for <i>any</i> game. overlays and tools. We aim to replicate this for <i>any</i> game.
</p> </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. 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 are unusually consumer-friendly, and provide a great service.
They will always have a place, providing a marketplace for DRM They will always have a place, providing a marketplace for DRM
@@ -41,16 +41,16 @@ function Header() {
</div> </div>
<div className="max-lg:mt-16 lg:col-span-1"> <div className="max-lg:mt-16 lg:col-span-1">
<Subheading>The Numbers</Subheading> <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"> <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"> <div className="flex flex-col gap-y-2 border-b border-dotted border-zinc-800 pb-4">
<dt className="text-sm/6 text-gray-600">Lines of code</dt> <dt className="text-sm/6 text-zinc-400">Lines of code</dt>
<dd className="order-first text-6xl font-medium tracking-tight"> <dd className="order-first text-6xl font-medium tracking-tight">
<AnimatedNumber start={10} end={40} />k <AnimatedNumber start={10} end={40} />k
</dd> </dd>
</div> </div>
<div className="flex flex-col gap-y-2 border-b border-dotted border-gray-200 pb-4"> <div className="flex flex-col gap-y-2 border-b border-dotted border-zinc-800 pb-4">
<dt className="text-sm/6 text-gray-600"> <dt className="text-sm/6 text-zinc-400">
Individual open-source projects Individual open-source projects
</dt> </dt>
<dd className="order-first text-6xl font-medium tracking-tight"> <dd className="order-first text-6xl font-medium tracking-tight">
@@ -59,13 +59,13 @@ function Header() {
</dd> </dd>
</div> </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"> <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"> <dd className="order-first text-6xl font-medium tracking-tight">
<AnimatedNumber start={0} end={48.8} decimals={1} />k <AnimatedNumber start={0} end={48.8} decimals={1} />k
</dd> </dd>
</div> </div>
<div className="flex flex-col gap-y-2"> <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"> <dd className="order-first text-6xl font-medium tracking-tight">
&gt; &gt;
<AnimatedNumber start={0} end={15} /> <AnimatedNumber start={0} end={15} />
@@ -91,7 +91,7 @@ function FrequentlyAskedQuestions() {
<dt className="text-sm font-semibold"> <dt className="text-sm font-semibold">
Do you intend to replace Steam? Do you intend to replace Steam?
</dt> </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 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 ever offer a marketplace for developers to sell games. Drop can
replace Steam <i>for an individual</i>, if they only played replace Steam <i>for an individual</i>, if they only played
@@ -102,7 +102,7 @@ function FrequentlyAskedQuestions() {
<dt className="text-sm font-semibold"> <dt className="text-sm font-semibold">
Will Drop ever cost money or require a subscription? Will Drop ever cost money or require a subscription?
</dt> </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 We believe in <strong>paying for services, not code</strong>. All
our projects are and always will be open source (AGPLv3), and we 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. endeavour to allow users to self-host as much of it as possible.
@@ -114,7 +114,7 @@ function FrequentlyAskedQuestions() {
</dl> </dl>
<dl> <dl>
<dt className="text-sm font-semibold">Is Drop legal?</dt> <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, Yes. Officially, Drop is <strong>only</strong> for DRM-free games,
like the ones you purchase from GOG or download from itch.io,{' '} like the ones you purchase from GOG or download from itch.io,{' '}
<strong>and</strong> that you have a license to redistribute. We <strong>and</strong> that you have a license to redistribute. We
@@ -126,7 +126,7 @@ function FrequentlyAskedQuestions() {
</dl> </dl>
<dl> <dl>
<dt className="text-sm font-semibold">How can I support Drop or get involved?</dt> <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 Thank you for helping us out! If you&apos;re looking to contribute
code, check out our{' '} code, check out our{' '}
<Link <Link
+1 -1
View File
@@ -15,7 +15,7 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en">
<body className="text-gray-950 antialiased"> <body className="text-zinc-100 bg-zinc-950 antialiased">
{children} {children}
</body> </body>
</html> </html>
+3 -3
View File
@@ -61,7 +61,7 @@ export default async function BlogPost({
className="aspect-square size-6 rounded-full object-cover" 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> </div>
)} )}
{ {
@@ -69,7 +69,7 @@ export default async function BlogPost({
{tags.map((tag) => ( {tags.map((tag) => (
<div <div
key={tag} 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} {tag}
</div> </div>
@@ -77,7 +77,7 @@ export default async function BlogPost({
</div> </div>
} }
</div> </div>
<div className="text-gray-700"> <div className="text-zinc-400">
<div className="max-w-2xl xl:mx-auto"> <div className="max-w-2xl xl:mx-auto">
{post.image && ( {post.image && (
<img <img
+1 -1
View File
@@ -17,7 +17,7 @@ export default function About() {
<Container> <Container>
<Navbar /> <Navbar />
</Container> </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"> <div className="text-center">
<Subheading>404</Subheading> <Subheading>404</Subheading>
<Heading> <Heading>
+51 -18
View File
@@ -5,8 +5,8 @@ import { Footer } from '@/components/footer'
import { Gradient } from '@/components/gradient' import { Gradient } from '@/components/gradient'
import { LogoCluster } from '@/components/logo-cluster' import { LogoCluster } from '@/components/logo-cluster'
import { Navbar } from '@/components/navbar' import { Navbar } from '@/components/navbar'
import { Screenshot } from '@/components/screenshot'
import { Heading, Subheading } from '@/components/text' import { Heading, Subheading } from '@/components/text'
import { ArrowDownCircleIcon } from '@heroicons/react/24/solid'
import type { Metadata } from 'next' import type { Metadata } from 'next'
export const metadata: Metadata = { export const metadata: Metadata = {
@@ -21,16 +21,18 @@ function Hero() {
<Container className="relative"> <Container className="relative">
<Navbar /> <Navbar />
<div className="pt-16 pb-24 sm:pt-24 sm:pb-32 md:pt-32 md:pb-48"> <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. An open Steam.
</h1> </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 Drop is an open-source, self-hosted alternative to platforms like
Steam and Epic. Steam and Epic.
</p> </p>
<div className="mt-12 flex flex-col gap-x-6 gap-y-4 sm:flex-row"> <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 href="https://docs.droposs.org/docs/guides/quickstart">
<Button variant="secondary" href="/about"> Get started
</Button>
<Button variant="outline" href="/about">
About About
</Button> </Button>
</div> </div>
@@ -42,18 +44,49 @@ function Hero() {
function FeatureSection() { function FeatureSection() {
return ( return (
<div className="overflow-hidden"> <div className="py-24 sm:py-32">
<Container className="pb-24"> <div className="mx-auto max-w-7xl px-6 lg:px-8">
<Heading as="h2" className="max-w-3xl"> <div className="mx-auto max-w-2xl sm:text-center">
A better experience for DRM&#8209;free games. <Subheading>Drop OSS</Subheading>
</Heading> <Heading as="h3" className="mt-2 max-w-4xl">
<Screenshot The ultimate self-hosted game manager.
width={3408} </Heading>
height={1846}
src="/screenshots/app.webp" <p className="mt-6 text-lg/8 text-zinc-400">
className="mt-16 h-144 sm:h-auto sm:w-304" Drop is built from the ground up to be flexible, fast, and
/> beautiful. It's designed to scale with your library, and handle
</Container> 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> </div>
) )
} }
@@ -132,7 +165,7 @@ export default function Home() {
<div className="overflow-hidden"> <div className="overflow-hidden">
<Hero /> <Hero />
<main> <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 /> <FeatureSection />
<BentoSection /> <BentoSection />
</div> </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="mt-12 grid grid-cols-1 gap-12 lg:grid-cols-2">
<div className="max-w-lg"> <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 - 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 first off, thank you. It really does help a lot. There are two
options for contributing: our OpenCollective, which funds options for contributing: our OpenCollective, which funds
@@ -45,7 +45,6 @@ function CTA() {
<Button <Button
className="w-full sm:w-auto" className="w-full sm:w-auto"
href="/about#team" href="/about#team"
variant="secondary"
> >
Team &rarr; Team &rarr;
</Button> </Button>
+4 -11
View File
@@ -29,28 +29,21 @@ export function BentoCard({
data-dark={dark ? 'true' : undefined} data-dark={dark ? 'true' : undefined}
className={clsx( className={clsx(
className, className,
'group relative flex flex-col overflow-hidden rounded-lg', 'group relative flex flex-col overflow-hidden rounded-lg ring-1',
'bg-white shadow-xs ring-1 ring-black/5', 'bg-zinc-900 ring-white/15',
'data-dark:bg-gray-800 data-dark:ring-white/15',
)} )}
> >
<div className="relative h-80 shrink-0"> <div className="relative h-80 shrink-0">
{graphic} {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>
<div className="relative p-10"> <div className="relative p-10">
<Subheading as="h3" dark={dark}> <Subheading as="h3" dark={dark}>
{eyebrow} {eyebrow}
</Subheading> </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} {title}
</p> </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} {description}
</p> </p>
</div> </div>
+7 -7
View File
@@ -5,22 +5,22 @@ import { Link } from './link'
const variants = { const variants = {
primary: clsx( primary: clsx(
'inline-flex items-center justify-center px-4 py-[calc(--spacing(2)-1px)]', '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', 'text-base font-medium whitespace-nowrap text-white',
'data-disabled:bg-gray-950 data-disabled:opacity-40 data-hover:bg-gray-800', 'data-disabled:bg-gray-950 data-disabled:opacity-40 data-hover:bg-gray-800',
), ),
secondary: clsx( secondary: clsx(
'relative inline-flex items-center justify-center px-4 py-[calc(--spacing(2)-1px)]', '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]', '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', 'data-disabled:bg-white/15 data-disabled:opacity-40 data-hover:bg-white/20',
), ),
outline: clsx( outline: clsx(
'inline-flex items-center justify-center px-2 py-[calc(--spacing(1.5)-1px)]', 'inline-flex items-center justify-center px-4 py-[calc(--spacing(1.5)-1px)]',
'rounded-lg border border-transparent shadow-sm ring-1 ring-black/10', 'rounded-lg border border-transparent shadow-sm ring-1 ring-white/5',
'text-sm font-medium whitespace-nowrap text-gray-950', 'text-sm font-medium whitespace-nowrap text-zinc-100',
'data-disabled:bg-transparent data-disabled:opacity-40 data-hover:bg-gray-50', 'data-disabled:bg-transparent data-disabled:opacity-40 data-hover:bg-zinc-900',
), ),
} }
+24 -23
View File
@@ -39,7 +39,7 @@ function GameVaultLogo() {
return ( return (
<div className="inline-flex items-center gap-x-2 text-xl font-bold"> <div className="inline-flex items-center gap-x-2 text-xl font-bold">
<img src="/icons/gamevault.png" alt="GameVault Logo" className="size-8" /> <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 <svg
aria-hidden="true" aria-hidden="true"
viewBox="0 0 418 42" viewBox="0 0 418 42"
@@ -56,7 +56,7 @@ function GameVaultLogo() {
function GameVaultPlus() { function GameVaultPlus() {
return ( 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 <img
src="/icons/gamevault-plus.png" src="/icons/gamevault-plus.png"
alt="GameVault+ icon" alt="GameVault+ icon"
@@ -69,7 +69,7 @@ function GameVaultPlus() {
function ComingSoon() { function ComingSoon() {
return ( 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; coming soon&trade;
</div> </div>
) )
@@ -121,6 +121,7 @@ const projects: Array<{
'Installer/setup games': true, 'Installer/setup games': true,
'Portable games': true, 'Portable games': true,
'Archives support': 'All 7-zip formats', 'Archives support': 'All 7-zip formats',
'Automatic import': 'Bulk-import tool',
}, },
Metadata: { Metadata: {
'Additional with plugins': false, 'Additional with plugins': false,
@@ -230,7 +231,7 @@ const projects: Array<{
'Non-versioned layout': true, 'Non-versioned layout': true,
'Automatic import': true, 'Automatic import': true,
'Archives support': 'All 7-zip formats', 'Archives support': 'All 7-zip formats',
'Portable games': true 'Portable games': true,
}, },
Metadata: { Metadata: {
IGDB: true, IGDB: true,
@@ -279,14 +280,14 @@ function Header() {
) )
} }
function PricingCards() { function ProjectCards() {
return ( return (
<div className="relative py-24"> <div className="relative py-24">
<Gradient className="absolute inset-x-2 top-48 bottom-0 rounded-4xl ring-1 ring-black/5 ring-inset" /> <Gradient className="absolute inset-x-2 top-48 bottom-0 rounded-4xl ring-1 ring-black/5 ring-inset" />
<Container className="relative"> <Container className="relative">
<div className="grid grid-cols-1 gap-8 lg:grid-cols-3"> <div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
{projects.map((tier, tierIndex) => ( {projects.map((tier, tierIndex) => (
<PricingCard key={tierIndex} tier={tier} /> <ProjectCard key={tierIndex} tier={tier} />
))} ))}
</div> </div>
</Container> </Container>
@@ -294,22 +295,22 @@ function PricingCards() {
) )
} }
function PricingCard({ tier }: { tier: (typeof projects)[number] }) { function ProjectCard({ tier }: { tier: (typeof projects)[number] }) {
return ( 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="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"> <div className="flex w-full items-center justify-center pb-8">
{tier.logo()} {tier.logo()}
</div> </div>
<Subheading>{tier.name}</Subheading> <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"> <div className="mt-8">
<Button href={tier.href}>Learn more &rarr;</Button> <Button href={tier.href}>Learn more &rarr;</Button>
</div> </div>
<div className="mt-8"> <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: Key features:
</h3> </h3>
<ul className="mt-3 space-y-3"> <ul className="mt-3 space-y-3">
@@ -324,10 +325,10 @@ function PricingCard({ tier }: { tier: (typeof projects)[number] }) {
) )
} }
function PricingTable({ function ProjectTable({
selectedTier: selectedProject, selectedProject,
}: { }: {
selectedTier: (typeof projects)[number] selectedProject: (typeof projects)[number]
}) { }) {
function onlyUnique<T>(value: T, index: number, array: Array<T>) { function onlyUnique<T>(value: T, index: number, array: Array<T>) {
return array.indexOf(value) === index return array.indexOf(value) === index
@@ -385,7 +386,7 @@ function PricingTable({
<Menu> <Menu>
<MenuButton className="flex items-center justify-between gap-2 font-medium"> <MenuButton className="flex items-center justify-between gap-2 font-medium">
{selectedProject.name} {selectedProject.name}
<ChevronUpDownIcon className="size-4 fill-gray-900" /> <ChevronUpDownIcon className="size-4 fill-zinc-100" />
</MenuButton> </MenuButton>
<MenuItems <MenuItems
anchor="bottom start" anchor="bottom start"
@@ -444,7 +445,7 @@ function PricingTable({
colSpan={4} colSpan={4}
className="px-0 pt-10 pb-0 group-first-of-type:pt-5" 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} {section}
</div> </div>
</th> </th>
@@ -452,11 +453,11 @@ function PricingTable({
{features[section].map((name) => ( {features[section].map((name) => (
<tr <tr
key={name} key={name}
className="border-b border-gray-100 last:border-none" className="border-b border-zinc-800 last:border-none"
> >
<th <th
scope="row" 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} {name}
</th> </th>
@@ -488,7 +489,7 @@ function PricingTable({
</span> </span>
</> </>
) : ( ) : (
<div className="text-xs">{value}</div> <div className="text-xs text-zinc-400">{value}</div>
)} )}
</td> </td>
) )
@@ -514,10 +515,10 @@ function FeatureItem({
return ( return (
<li <li
data-disabled={disabled ? true : undefined} 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"> <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> </span>
{disabled && <span className="sr-only">Coming soon:</span>} {disabled && <span className="sr-only">Coming soon:</span>}
{description} {description}
@@ -553,8 +554,8 @@ export default function Pricing() {
<Navbar /> <Navbar />
</Container> </Container>
<Header /> <Header />
<PricingCards /> <ProjectCards />
<PricingTable selectedTier={tier} /> <ProjectTable selectedProject={tier} />
<Footer /> <Footer />
</main> </main>
) )
+6 -6
View File
@@ -226,14 +226,14 @@ function DownloadCard({
return ( 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-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="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>
<div className="flex w-full items-center justify-center gap-x-4"> <div className="flex w-full items-center justify-center gap-x-4">
{data.icon()} {data.icon()}
<Heading>{data.name}</Heading> <Heading>{data.name}</Heading>
</div> </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} {data.description}
</p> </p>
</div> </div>
@@ -266,11 +266,11 @@ export default function DownloadCards() {
<Container> <Container>
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<Listbox value={currentVersion} onChange={setCurrentVersion}> <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 Version
</Label> </Label>
<div className="relative mt-2"> <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"> <span className="col-start-1 row-start-1 truncate pr-6">
{currentVersion} {currentVersion}
</span> </span>
@@ -282,13 +282,13 @@ export default function DownloadCards() {
<ListboxOptions <ListboxOptions
transition 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) => ( {Object.keys(releasePages).map((version) => (
<ListboxOption <ListboxOption
key={version} key={version}
value={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"> <span className="block truncate font-normal group-data-selected:font-semibold">
{version} {version}
+34 -36
View File
@@ -12,11 +12,11 @@ function CallToAction() {
<div className="relative pt-20 pb-16 text-center sm:py-24"> <div className="relative pt-20 pb-16 text-center sm:py-24">
<hgroup> <hgroup>
<Subheading>Get started</Subheading> <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? Ready to dive in?
</p> </p>
</hgroup> </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 Get started hosting Drop in 10 minutes or less, and explore all the
features it has to offer. features it has to offer.
</p> </p>
@@ -33,7 +33,7 @@ function CallToAction() {
} }
function SitemapHeading({ children }: { children: React.ReactNode }) { 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 }) { function SitemapLinks({ children }: { children: React.ReactNode }) {
@@ -45,7 +45,7 @@ function SitemapLink(props: React.ComponentPropsWithoutRef<typeof Link>) {
<li> <li>
<Link <Link
{...props} {...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> </li>
) )
@@ -164,8 +164,8 @@ function SocialLinks() {
function Copyright() { function Copyright() {
return ( return (
<div className="text-xs text-gray-500"> <div className="text-xs text-zinc-400">
&copy; {new Date().getFullYear()} Drop OSS. Licensed under AGPLv3 and &copy; {new Date().getFullYear()} Drop OSS. Website licensed under AGPLv3 and
Tailwind UI Plus (where applicable). Tailwind UI Plus (where applicable).
</div> </div>
) )
@@ -173,39 +173,37 @@ function Copyright() {
export function Footer() { export function Footer() {
return ( return (
<footer> <footer className="relative m-2 overflow-hidden rounded-4xl">
<Gradient className="relative"> <Gradient className="absolute inset-0 rounded-4xl" />
<div className="absolute inset-2 rounded-4xl bg-white/80" /> <Container>
<Container> <CallToAction />
<CallToAction /> <PlusGrid className="pb-16">
<PlusGrid className="pb-16"> <PlusGridRow>
<PlusGridRow> <div className="grid grid-cols-2 gap-y-10 pb-6 lg:grid-cols-6 lg:gap-8">
<div className="grid grid-cols-2 gap-y-10 pb-6 lg:grid-cols-6 lg:gap-8"> <div className="col-span-2 flex">
<div className="col-span-2 flex"> <PlusGridItem className="pt-6 lg:pb-6">
<PlusGridItem className="pt-6 lg:pb-6"> <Logo className="h-9" />
<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 />
</PlusGridItem> </PlusGridItem>
</div> </div>
<div className="flex"> <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">
<PlusGridItem className="flex items-center gap-8 py-3"> <Sitemap />
<SocialLinks />
</PlusGridItem>
</div> </div>
</PlusGridRow> </div>
</PlusGrid> </PlusGridRow>
</Container> <PlusGridRow className="flex justify-between">
</Gradient> <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> </footer>
) )
} }
+2 -2
View File
@@ -9,7 +9,7 @@ export function Gradient({
{...props} {...props}
className={clsx( className={clsx(
className, 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 <div
className={clsx( className={clsx(
'absolute -top-44 -right-60 h-60 w-xl transform-gpu md:right-0', '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', '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> <p className="my-8 text-base/8 first:mt-0 last:mb-0">{children}</p>
), ),
h1: ({ children }) => ( 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} {children}
</h2> </h2>
), ),
h2: ({ children }) => ( 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} {children}
</h2> </h2>
), ),
h3: ({ children }) => ( 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} {children}
</h3> </h3>
), ),
blockquote: ({ children }) => ( 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} {children}
</blockquote> </blockquote>
), ),
@@ -32,9 +32,9 @@ const components: MDXComponents = {
className="w-full rounded-2xl" 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: ({ children }) => (
<strong className="font-semibold text-gray-950">{children}</strong> <strong className="font-semibold text-zinc-100">{children}</strong>
), ),
code: ({ children }) => ( code: ({ children }) => (
<> <>
@@ -60,7 +60,7 @@ const components: MDXComponents = {
return ( return (
<Link <Link
{...props} {...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} {props.children}
</Link> </Link>
+5 -5
View File
@@ -27,7 +27,7 @@ function DesktopNav() {
<PlusGridItem key={href} className="relative flex"> <PlusGridItem key={href} className="relative flex">
<Link <Link
href={href} 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} {label}
</Link> </Link>
@@ -40,7 +40,7 @@ function DesktopNav() {
function MobileNavButton() { function MobileNavButton() {
return ( return (
<DisclosureButton <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" aria-label="Open main menu"
> >
<Bars2Icon className="size-6" /> <Bars2Icon className="size-6" />
@@ -63,15 +63,15 @@ function MobileNav() {
}} }}
key={href} 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} {label}
</Link> </Link>
</motion.div> </motion.div>
))} ))}
</div> </div>
<div className="absolute left-1/2 w-screen -translate-x-1/2"> <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-0 border-t border-white/5" />
<div className="absolute inset-x-0 top-2 border-t border-black/5" /> <div className="absolute inset-x-0 top-2 border-t border-white/5" />
</div> </div>
</DisclosurePanel> </DisclosurePanel>
) )
+11 -11
View File
@@ -26,14 +26,14 @@ function FeaturedPosts() {
const postAuthors = fetchPostAuthors() const postAuthors = fetchPostAuthors()
return ( return (
<div className="mt-16 bg-linear-to-t from-gray-100 pb-14"> <div className="mt-16 pb-14">
<Container> <Container>
<h2 className="text-2xl font-medium tracking-tight">Featured</h2> <h2 className="text-2xl font-medium tracking-tight">Featured</h2>
<div className="mt-6 grid grid-cols-1 gap-8 lg:grid-cols-3"> <div className="mt-6 grid grid-cols-1 gap-8 lg:grid-cols-3">
{featuredPosts.map((post) => ( {featuredPosts.map((post) => (
<div <div
key={post._meta.path} 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 && ( {post.image && (
<img <img
@@ -43,7 +43,7 @@ function FeaturedPosts() {
/> />
)} )}
<div className="flex flex-1 flex-col p-8"> <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')} {dayjs(post.date).format('dddd, MMMM D, YYYY')}
</div> </div>
<div className="mt-2 text-base/7 font-medium"> <div className="mt-2 text-base/7 font-medium">
@@ -52,7 +52,7 @@ function FeaturedPosts() {
{post.title} {post.title}
</Link> </Link>
</div> </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} {post.excerpt}
</div> </div>
{postAuthors[post.author ?? ''] && ( {postAuthors[post.author ?? ''] && (
@@ -62,7 +62,7 @@ function FeaturedPosts() {
src={postAuthors[post.author ?? ''].avatar} src={postAuthors[post.author ?? ''].avatar}
className="aspect-square size-6 rounded-full object-cover" 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} {postAuthors[post.author ?? ''].name}
</div> </div>
</div> </div>
@@ -94,7 +94,7 @@ function Posts({ page, category }: { page: number; category?: string }) {
{posts.map((post) => ( {posts.map((post) => (
<div <div
key={post._meta.path} 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>
<div className="text-sm/5 max-sm:text-gray-700 sm:font-medium"> <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" 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} {postAuthors[post.author ?? ''].name}
</div> </div>
</div> </div>
@@ -117,7 +117,7 @@ function Posts({ page, category }: { page: number; category?: string }) {
</div> </div>
<div className="sm:col-span-2 sm:max-w-2xl"> <div className="sm:col-span-2 sm:max-w-2xl">
<h2 className="text-sm/5 font-medium">{post.title}</h2> <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"> <div className="mt-4">
<Link <Link
href={post.url} href={post.url}
@@ -180,9 +180,9 @@ function Pagination({
data-active={i + 1 === page ? true : undefined} data-active={i + 1 === page ? true : undefined}
className={clsx( className={clsx(
'size-7 rounded-lg text-center text-sm/7 font-medium', 'size-7 rounded-lg text-center text-sm/7 font-medium',
'data-hover:bg-gray-100', 'data-hover:bg-zinc-900',
'data-active:shadow-sm data-active:ring-1 data-active:ring-black/10', 'data-active:shadow-sm data-active:ring-1 data-active:ring-white/10',
'data-active:data-hover:bg-gray-50', 'data-active:data-hover:bg-zinc-900',
)} )}
> >
{i + 1} {i + 1}
+5 -5
View File
@@ -28,10 +28,10 @@ export function PlusGridRow({
aria-hidden="true" aria-hidden="true"
className="absolute inset-y-0 left-1/2 -z-10 w-screen -translate-x-1/2" 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-0 border-t border-white/5"></div>
<div className="absolute inset-x-0 top-2 border-t border-black/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-black/5 group-last/row:block"></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-black/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> </div>
{children} {children}
</div> </div>
@@ -83,7 +83,7 @@ export function PlusGridIcon({
aria-hidden="true" aria-hidden="true"
className={clsx( className={clsx(
className, className,
'absolute size-[15px] fill-black/10', 'absolute size-[15px] fill-white/10',
yClass, yClass,
xClass, xClass,
)} )}
+4 -13
View File
@@ -76,19 +76,10 @@ function SponsorCard({
ref={ref} ref={ref}
style={{ opacity }} style={{ opacity }}
{...props} {...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"> <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 text-white">{name}</p>
<p className="text-sm/6 font-medium"> <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"> <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} data-active={activeIndex === i ? true : undefined}
aria-label={`Scroll to sponsorship from ${name}`} aria-label={`Scroll to sponsorship from ${name}`}
className={clsx( className={clsx(
'size-2.5 cursor-pointer rounded-full border border-transparent bg-gray-300 transition', 'size-2.5 cursor-pointer rounded-full border border-transparent bg-zinc-600 transition',
'data-active:bg-gray-400 data-hover:bg-gray-400', 'data-active:bg-blue-700 data-hover:bg-zinc-900',
'forced-colors:data-active:bg-[Highlight] forced-colors:data-focus:outline-offset-4', '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" /> <img alt="" src={img} className="size-12 rounded-full" />
<div className="text-sm/6"> <div className="text-sm/6">
<h3 className="font-medium group-hover:underline">{name}</h3> <h3 className="font-medium group-hover:underline">{name}</h3>
<p className="text-gray-500">{description}</p> <p className="text-zinc-400">{description}</p>
<p className="text-xs font-semibold text-gray-400 uppercase"> <p className="text-xs font-semibold text-blue-600/80">
{contributions} contribution(s) {contributions} contribution(s)
</p> </p>
</div> </div>
@@ -105,14 +105,14 @@ export function Team() {
</Lead> </Lead>
<div className="mt-12 grid grid-cols-1 gap-12 lg:grid-cols-2"> <div className="mt-12 grid grid-cols-1 gap-12 lg:grid-cols-2">
<div className="max-w-lg"> <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 Drop OSS was started, mostly on a whim, in response to frustrations
with the controlled nature of DRM games, and the missing comforts of 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 DRM-free games. Since then, we&apos;ve put together a small circle
of dedicated maintainers and contributors to develop Drop and all of dedicated maintainers and contributors to develop Drop and all
its amazing features. its amazing features.
</p> </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 If you know a little code, you can help out! We heavily encourage
contributions, especially if you&apos;re passionate about the contributions, especially if you&apos;re passionate about the
project and enjoy writing code. We use a variety of stacks across 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" className="w-full sm:w-auto"
href="https://developer.droposs.org/contributing" href="https://developer.droposs.org/contributing"
target="_blank" target="_blank"
variant="outline"
> >
Contribute Contribute &rarr;
</Button> </Button>
</div> </div>
</div> </div>
@@ -133,7 +134,7 @@ export function Team() {
<Subheading id="team" as="h3" className="mt-24"> <Subheading id="team" as="h3" className="mt-24">
The team The team
</Subheading> </Subheading>
<hr className="mt-6 border-t border-gray-200" /> <hr className="mt-6 border-t border-zinc-800" />
<ul <ul
role="list" role="list"
className="mx-auto mt-16 grid grid-cols-1 gap-8 sm:grid-cols-3 lg:grid-cols-4" 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} data-dark={dark ? 'true' : undefined}
className={clsx( className={clsx(
className, 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} data-dark={dark ? 'true' : undefined}
className={clsx( className={clsx(
className, 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: onlyBuiltDependencies:
- "@bufbuild/buf" - '@bufbuild/buf'
- "@parcel/watcher" - '@parcel/watcher'
- "@prisma/client" - '@prisma/client'
- "@prisma/engines" - '@prisma/engines'
- "@tailwindcss/oxide" - '@tailwindcss/oxide'
- argon2 - argon2
- esbuild - esbuild
- prisma - prisma
- sharp - sharp
- unrs-resolver - 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 shamefullyHoist: true
+8
View File
@@ -590,6 +590,7 @@ version = "0.16.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"droplet_types",
"dyn-clone", "dyn-clone",
"futures", "futures",
"getrandom 0.3.4", "getrandom 0.3.4",
@@ -609,6 +610,13 @@ dependencies = [
"x509-parser 0.17.0", "x509-parser 0.17.0",
] ]
[[package]]
name = "droplet_types"
version = "0.1.0"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.20" version = "1.0.20"
+4 -2
View File
@@ -3,10 +3,11 @@ use std::{
sync::{Arc, LazyLock}, sync::{Arc, LazyLock},
}; };
use droplet_rs::manifest::ManifestWriterFactory;
use log::info; use log::info;
use protobuf::Message; use protobuf::Message;
use serde_json::json; use serde_json::json;
use tokio::{spawn, sync::Semaphore}; use tokio::{io::{BufWriter, SimplexStream}, spawn, sync::Semaphore};
use crate::{ use crate::{
proto::{ proto::{
@@ -62,7 +63,8 @@ pub async fn generate_manifest_rpc(
.await; .await;
}); });
}, },
Some(READER_SEMAPHORE.clone()), None::<&dyn ManifestWriterFactory<Writer = SimplexStream>>, // Dummy type signature, not actually used
Some(&READER_SEMAPHORE),
) )
.await?; .await?;