diff --git a/desktop/components/Header.vue b/desktop/components/Header.vue
index 5b257de3..eeb5d655 100644
--- a/desktop/components/Header.vue
+++ b/desktop/components/Header.vue
@@ -45,7 +45,7 @@
diff --git a/desktop/pages/library/[id]/index.vue b/desktop/pages/library/[id]/index.vue
new file mode 100644
index 00000000..f8109b8a
--- /dev/null
+++ b/desktop/pages/library/[id]/index.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
diff --git a/desktop/pages/library/index.vue b/desktop/pages/library/index.vue
new file mode 100644
index 00000000..61c7765c
--- /dev/null
+++ b/desktop/pages/library/index.vue
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/desktop/prisma/schema.prisma b/desktop/prisma/schema.prisma
new file mode 100644
index 00000000..1ca51a1f
--- /dev/null
+++ b/desktop/prisma/schema.prisma
@@ -0,0 +1,150 @@
+// This should be copied from the main Drop repo
+// TODO: do this automatically
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+model User {
+ id String @id @default(uuid())
+ username String @unique
+ admin Boolean @default(false)
+
+ email String
+ displayName String
+ profilePicture String // Object
+
+ authMecs LinkedAuthMec[]
+ clients Client[]
+}
+
+enum AuthMec {
+ Simple
+}
+
+model LinkedAuthMec {
+ userId String
+ mec AuthMec
+
+ credentials Json
+
+ user User @relation(fields: [userId], references: [id])
+
+ @@id([userId, mec])
+}
+
+enum ClientCapabilities {
+ DownloadAggregation
+}
+
+enum Platform {
+ Windows @map("windows")
+ Linux @map("linux")
+}
+
+// References a device
+model Client {
+ id String @id @default(uuid())
+ userId String
+ user User @relation(fields: [userId], references: [id])
+
+ endpoint String
+ capabilities ClientCapabilities[]
+
+ name String
+ platform Platform
+ lastConnected DateTime
+}
+
+enum MetadataSource {
+ Custom
+ GiantBomb
+}
+
+model Game {
+ id String @id @default(uuid())
+
+ metadataSource MetadataSource
+ metadataId String
+
+ // Any field prefixed with m is filled in from metadata
+ // Acts as a cache so we can search and filter it
+ mName String // Name of game
+ mShortDescription String // Short description
+ mDescription String // Supports markdown
+ mDevelopers Developer[]
+ mPublishers Publisher[]
+
+ mReviewCount Int
+ mReviewRating Float
+
+ mIconId String // linked to objects in s3
+ mBannerId String // linked to objects in s3
+ mCoverId String
+ mImageLibrary String[] // linked to objects in s3
+
+ versions GameVersion[]
+ libraryBasePath String @unique // Base dir for all the game versions
+
+ @@unique([metadataSource, metadataId], name: "metadataKey")
+}
+
+// A particular set of files that relate to the version
+model GameVersion {
+ gameId String
+ game Game @relation(fields: [gameId], references: [id])
+ versionName String // Sub directory for the game files
+
+ platform Platform
+ launchCommand String // Command to run to start. Platform-specific. Windows games on Linux will wrap this command in Proton/Wine
+ setupCommand String // Command to setup game (dependencies and such)
+ dropletManifest Json // Results from droplet
+
+ versionIndex Int
+ delta Boolean @default(false)
+
+ @@id([gameId, versionName])
+}
+
+model Developer {
+ id String @id @default(uuid())
+
+ metadataSource MetadataSource
+ metadataId String
+ metadataOriginalQuery String
+
+ mName String
+ mShortDescription String
+ mDescription String
+ mLogo String
+ mBanner String
+ mWebsite String
+
+ games Game[]
+
+ @@unique([metadataSource, metadataId, metadataOriginalQuery], name: "metadataKey")
+}
+
+model Publisher {
+ id String @id @default(uuid())
+
+ metadataSource MetadataSource
+ metadataId String
+ metadataOriginalQuery String
+
+ mName String
+ mShortDescription String
+ mDescription String
+ mLogo String
+ mBanner String
+ mWebsite String
+
+ games Game[]
+
+ @@unique([metadataSource, metadataId, metadataOriginalQuery], name: "metadataKey")
+}
diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock
index f9489fef..c28ec7ed 100644
--- a/desktop/src-tauri/Cargo.lock
+++ b/desktop/src-tauri/Cargo.lock
@@ -1022,6 +1022,7 @@ dependencies = [
"directories",
"env_logger",
"hex",
+ "http",
"log",
"openssl",
"rayon",
diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml
index b8145409..00f4a04b 100644
--- a/desktop/src-tauri/Cargo.toml
+++ b/desktop/src-tauri/Cargo.toml
@@ -36,6 +36,7 @@ structured-logger = "1.0.3"
hex = "0.4.3"
tauri-plugin-dialog = "2"
env_logger = "0.11.5"
+http = "1.1.0"
[dependencies.uuid]
version = "1.10.0"
diff --git a/desktop/src-tauri/src/auth.rs b/desktop/src-tauri/src/auth.rs
index 67c4368c..57c542f5 100644
--- a/desktop/src-tauri/src/auth.rs
+++ b/desktop/src-tauri/src/auth.rs
@@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize};
use tauri::{AppHandle, Emitter, Manager};
use url::Url;
-use crate::{db::DatabaseAuth, AppState, AppStatus, User, DB};
+use crate::{db::{fetch_base_url, DatabaseAuth}, AppState, AppStatus, User, DB};
#[derive(Serialize)]
struct InitiateRequestBody {
@@ -79,10 +79,7 @@ pub fn generate_authorization_header() -> String {
}
pub fn fetch_user() -> Result {
- let base_url = {
- let handle = DB.borrow_data().unwrap();
- Url::parse(&handle.base_url).unwrap()
- };
+ let base_url = fetch_base_url();
let endpoint = base_url.join("/api/v1/client/user").unwrap();
let header = generate_authorization_header();
diff --git a/desktop/src-tauri/src/db.rs b/desktop/src-tauri/src/db.rs
index f53e7653..29558e54 100644
--- a/desktop/src-tauri/src/db.rs
+++ b/desktop/src-tauri/src/db.rs
@@ -7,6 +7,7 @@ use std::{
use directories::BaseDirs;
use rustbreak::{deser::Bincode, PathDatabase};
use serde::Deserialize;
+use url::Url;
use crate::DB;
@@ -62,3 +63,8 @@ pub fn setup() -> DatabaseInterface {
pub fn is_set_up() -> bool {
return !DB.borrow_data().unwrap().base_url.is_empty();
}
+
+pub fn fetch_base_url() -> Url {
+ let handle = DB.borrow_data().unwrap();
+ Url::parse(&handle.base_url).unwrap()
+}
diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs
index 2b47428d..249b256f 100644
--- a/desktop/src-tauri/src/lib.rs
+++ b/desktop/src-tauri/src/lib.rs
@@ -1,15 +1,22 @@
mod auth;
mod db;
+mod library;
mod remote;
mod unpacker;
-use std::sync::{LazyLock, Mutex};
+use auth::{auth_initiate, generate_authorization_header, recieve_handshake};
+use db::{fetch_base_url, DatabaseInterface, DATA_ROOT_DIR};
+use env_logger;
use env_logger::Env;
-use auth::{auth_initiate, recieve_handshake};
-use db::{DatabaseInterface, DATA_ROOT_DIR};
+use http::{header::*, response::Builder as ResponseBuilder, status::StatusCode};
+use library::{fetch_game, fetch_library, Game};
use log::info;
use remote::{gen_drop_url, use_remote};
use serde::{Deserialize, Serialize};
+use std::{
+ collections::HashMap, io, sync::{LazyLock, Mutex}, task, thread
+};
+use structured_logger::{json::new_writer, Builder};
use tauri_plugin_deep_link::DeepLinkExt;
#[derive(Clone, Copy, Serialize)]
@@ -20,19 +27,19 @@ pub enum AppStatus {
SignedInNeedsReauth,
}
#[derive(Clone, Serialize, Deserialize)]
-#[serde(rename_all="camelCase")]
pub struct User {
id: String,
username: String,
admin: bool,
- display_name: String,
- profile_picture: String,
+ displayName: String,
+ profilePicture: String,
}
#[derive(Clone, Serialize)]
pub struct AppState {
status: AppStatus,
user: Option,
+ games: HashMap,
}
#[tauri::command]
@@ -43,7 +50,7 @@ fn fetch_state<'a>(state: tauri::State<'_, Mutex>) -> Result AppState {
+fn setup<'a>() -> AppState {
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
let is_set_up = db::is_set_up();
@@ -51,22 +58,24 @@ fn setup() -> AppState {
return AppState {
status: AppStatus::NotConfigured,
user: None,
+ games: HashMap::new(),
};
}
let auth_result = auth::setup().unwrap();
- AppState {
+ return AppState {
status: auth_result.0,
user: auth_result.1,
- }
+ games: HashMap::new(),
+ };
}
-pub static DB: LazyLock = LazyLock::new(db::setup);
+pub static DB: LazyLock = LazyLock::new(|| db::setup());
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let state = setup();
- info!("Initialized drop client");
+ info!("initialized drop client");
let mut builder = tauri::Builder::default().plugin(tauri_plugin_dialog::init());
@@ -81,10 +90,16 @@ pub fn run() {
.plugin(tauri_plugin_deep_link::init())
.manage(Mutex::new(state))
.invoke_handler(tauri::generate_handler![
+ // DB
fetch_state,
+ // Auth
auth_initiate,
+ // Remote
use_remote,
gen_drop_url,
+ // Library
+ fetch_library,
+ fetch_game,
])
.plugin(tauri_plugin_shell::init())
.setup(|app| {
@@ -113,14 +128,46 @@ pub fn run() {
app.deep_link().on_open_url(move |event| {
info!("handling drop:// url");
let binding = event.urls();
- let url = binding.first().unwrap();
- if url.host_str().unwrap() == "handshake" {
- recieve_handshake(handle.clone(), url.path().to_string())
+ let url = binding.get(0).unwrap();
+ match url.host_str().unwrap() {
+ "handshake" => recieve_handshake(handle.clone(), url.path().to_string()),
+ _ => (),
}
});
Ok(())
})
+ .register_asynchronous_uri_scheme_protocol("object", move |_ctx, request, responder| {
+ let base_url = fetch_base_url();
+
+ // Drop leading /
+ let object_id = &request.uri().path()[1..];
+
+ let object_url = base_url
+ .join("/api/v1/client/object/")
+ .unwrap()
+ .join(object_id)
+ .unwrap();
+
+ info!["{}", object_url.to_string()];
+
+ let header = generate_authorization_header();
+ let client: reqwest::blocking::Client = reqwest::blocking::Client::new();
+ let response = client
+ .get(object_url.to_string())
+ .header("Authorization", header)
+ .send()
+ .unwrap();
+
+ let resp_builder = ResponseBuilder::new().header(
+ CONTENT_TYPE,
+ response.headers().get("Content-Type").unwrap(),
+ );
+ let data = Vec::from(response.bytes().unwrap());
+ let resp = resp_builder.body(data).unwrap();
+
+ responder.respond(resp);
+ })
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
diff --git a/desktop/src-tauri/src/library.rs b/desktop/src-tauri/src/library.rs
new file mode 100644
index 00000000..534d810f
--- /dev/null
+++ b/desktop/src-tauri/src/library.rs
@@ -0,0 +1,70 @@
+use std::{borrow::BorrowMut, sync::Mutex};
+
+use serde::{Deserialize, Serialize};
+use serde_json::json;
+use tauri::{AppHandle, Manager};
+
+use crate::{auth::generate_authorization_header, db::fetch_base_url, AppState};
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct Game {
+ id: String,
+ mName: String,
+ mShortDescription: String,
+ mDescription: String,
+ // mDevelopers
+ // mPublishers
+
+ mIconId: String,
+ mBannerId: String,
+ mCoverId: String,
+ mImageLibrary: Vec,
+}
+
+#[tauri::command]
+pub fn fetch_library(app: AppHandle) -> Result {
+ let base_url = fetch_base_url();
+ let library_url = base_url.join("/api/v1/client/user/library").unwrap();
+
+ let header = generate_authorization_header();
+
+ let client = reqwest::blocking::Client::new();
+ let response = client
+ .get(library_url.to_string())
+ .header("Authorization", header)
+ .send()
+ .unwrap();
+
+ if response.status() != 200 {
+ return Err(format!(
+ "Library fetch request failed with {}",
+ response.status()
+ ));
+ }
+
+ // Keep as string
+ let games = response.json::>().unwrap();
+
+ let state = app.state::>();
+ let mut handle = state.lock().unwrap();
+
+ for game in games.iter() {
+ handle.games.insert(game.id.clone(), game.clone());
+ }
+
+ drop(handle);
+
+ return Ok(json!(games.clone()).to_string());
+}
+
+#[tauri::command]
+pub fn fetch_game(id: String, app: tauri::AppHandle) -> Result {
+ let state = app.state::>();
+ let handle = state.lock().unwrap();
+ let game = handle.games.get(&id);
+ if game.is_some() {
+ return Ok(json!(game.unwrap()).to_string());
+ }
+
+ return Ok("".to_string());
+}
diff --git a/desktop/types.d.ts b/desktop/types.d.ts
index 9b7405cc..566929f2 100644
--- a/desktop/types.d.ts
+++ b/desktop/types.d.ts
@@ -1,3 +1,17 @@
+import type { User } from "@prisma/client";
+import type { Component } from "vue"
+
+export type NavigationItem = {
+ prefix: string,
+ route: string,
+ label: string,
+}
+
+export type QuickActionNav = {
+ icon: Component,
+ notifications?: number,
+ action: () => Promise,
+}
export type AppState = {
status: AppStatus;
user?: User;
@@ -8,13 +22,4 @@ export enum AppStatus {
SignedOut = "SignedOut",
SignedIn = "SignedIn",
SignedInNeedsReauth = "SignedInNeedsReauth",
-}
-
-export type User = {
- id: string;
- username: string;
- admin: boolean;
- email: string;
- displayName: string;
- profilePicture: string;
-};
+}
\ No newline at end of file
diff --git a/desktop/yarn.lock b/desktop/yarn.lock
index b623e1b5..da122f89 100644
--- a/desktop/yarn.lock
+++ b/desktop/yarn.lock
@@ -1075,6 +1075,47 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73"
integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==
+"@prisma/client@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.20.0.tgz#4fc9f2b2341c9c997c139df4445688dd6b39663b"
+ integrity sha512-CLv55ZuMuUawMsxoqxGtLT3bEZoa2W8L3Qnp6rDIFWy+ZBrUcOFKdoeGPSnbBqxc3SkdxJrF+D1veN/WNynZYA==
+
+"@prisma/debug@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.20.0.tgz#c6d1cf6e3c6e9dba150347f13ca200b1d66cc9fc"
+ integrity sha512-oCx79MJ4HSujokA8S1g0xgZUGybD4SyIOydoHMngFYiwEwYDQ5tBQkK5XoEHuwOYDKUOKRn/J0MEymckc4IgsQ==
+
+"@prisma/engines-version@5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284":
+ version "5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284"
+ resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284.tgz#9a53b13cdcfd706ae54198111000f33c63655c39"
+ integrity sha512-Lg8AS5lpi0auZe2Mn4gjuCg081UZf88k3cn0RCwHgR+6cyHHpttPZBElJTHf83ZGsRNAmVCZCfUGA57WB4u4JA==
+
+"@prisma/engines@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.20.0.tgz#86fe407e55219d33d03ebc26dc829a422faed545"
+ integrity sha512-DtqkP+hcZvPEbj8t8dK5df2b7d3B8GNauKqaddRRqQBBlgkbdhJkxhoJTrOowlS3vaRt2iMCkU0+CSNn0KhqAQ==
+ dependencies:
+ "@prisma/debug" "5.20.0"
+ "@prisma/engines-version" "5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284"
+ "@prisma/fetch-engine" "5.20.0"
+ "@prisma/get-platform" "5.20.0"
+
+"@prisma/fetch-engine@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.20.0.tgz#b917880fb08f654981f14ca49923031b39683586"
+ integrity sha512-JVcaPXC940wOGpCOwuqQRTz6I9SaBK0c1BAyC1pcz9xBi+dzFgUu3G/p9GV1FhFs9OKpfSpIhQfUJE9y00zhqw==
+ dependencies:
+ "@prisma/debug" "5.20.0"
+ "@prisma/engines-version" "5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284"
+ "@prisma/get-platform" "5.20.0"
+
+"@prisma/get-platform@5.20.0":
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.20.0.tgz#c1a53a8d8af67f2b4a6b97dd4d25b1c603236804"
+ integrity sha512-8/+CehTZZNzJlvuryRgc77hZCWrUDYd/PmlZ7p2yNXtmf2Una4BWnTbak3us6WVdqoz5wmptk6IhsXdG2v5fmA==
+ dependencies:
+ "@prisma/debug" "5.20.0"
+
"@rollup/plugin-alias@^5.1.0":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz#53601d88cda8b1577aa130b4a6e452283605bf26"
@@ -2775,7 +2816,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
-fsevents@~2.3.2, fsevents@~2.3.3:
+fsevents@2.3.3, fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
@@ -4304,6 +4345,15 @@ pretty-bytes@^6.1.1:
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.1.1.tgz#38cd6bb46f47afbf667c202cfc754bffd2016a3b"
integrity sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==
+prisma@^5.20.0:
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.20.0.tgz#f2ab266a0d59383506886e7acbff0dbf322f4c7e"
+ integrity sha512-6obb3ucKgAnsGS9x9gLOe8qa51XxvJ3vLQtmyf52CTey1Qcez3A6W6ROH5HIz5Q5bW+0VpmZb8WBohieMFGpig==
+ dependencies:
+ "@prisma/engines" "5.20.0"
+ optionalDependencies:
+ fsevents "2.3.3"
+
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"