Compare commits

..

127 commits

Author SHA1 Message Date
quexeky
d9c4f7aa75
feat(library): Reactive library updating
Signed-off-by: quexeky <git@quexeky.dev>
2025-02-12 10:00:45 +11:00
quexeky
316a3742eb
fix(cache): Added proper error handling to fetch_object(_offline) 2025-02-12 10:00:45 +11:00
quexeky
b9df197534
feat(cache): Caching objects which use the useObject tauri command 2025-02-12 10:00:45 +11:00
quexeky
5c479cb283
chore(cache): Added fetch_drop_object command
Signed-off-by: quexeky <git@quexeky.dev>
2025-02-12 10:00:45 +11:00
quexeky
4c59c5d6c1
feat(cache): Implemented caching for game metadata 2025-02-12 10:00:45 +11:00
quexeky
9977107374
fix(cache): Bug where games would not remove themselves from the list of installed applications when being uninstalled 2025-02-12 10:00:45 +11:00
quexeky
2690c3019d
chore: Various formatting 2025-02-12 10:00:45 +11:00
quexeky
2a1a7326d0
feat(cache): Added forceOffline in settings and caching games & library 2025-02-12 10:00:45 +11:00
quexeky
f33ca95bdf
feat(cache): Added offline!() macro to manage online and offline function distinctions
See fetch_library command for example
2025-02-12 10:00:45 +11:00
quexeky
bb23e88ead
chore: Swapped over to using a macro with an offline mode
Signed-off-by: quexeky <git@quexeky.dev>
2025-02-12 10:00:45 +11:00
quexeky
810fbdfe49
chore: Progress on caching 2025-02-12 10:00:45 +11:00
quexeky
e204ff30b4
fix: Removed unnecessary nightly feature try_trait_v2 2025-02-12 10:00:45 +11:00
quexeky
501145c5d9
fix(downloads): Fix rearranging download queue throwing error 2025-02-12 10:00:45 +11:00
quexeky
dca5f65e89
chore: Version bump 2025-02-12 10:00:45 +11:00
quexeky
00f55ff3ae
Merge branch 'main' into develop 2025-01-25 18:46:33 +11:00
quexeky
52c70052a4
Update changelog.md 2025-01-25 14:35:17 +11:00
quexeky
7a0cf4fbb6
fix(logging): Restored RUST_LOG env functionality 2025-01-25 14:34:08 +11:00
quexeky
76bae3d926
fix(library): Added "LIbrary Failed to Update" content to recover from library load fail 2025-01-24 22:35:09 +11:00
Aden Lindsay
53234d283e
feat(settings): Made save button include user feedback & only allow numeric characters 2025-01-24 13:01:59 +11:00
quexeky
3e10f1749a
Update .gitlab-ci.yml with artifacts 2025-01-21 09:09:02 +11:00
quexeky
6d7630e7c0
Update .gitlab-ci.yml 2025-01-21 08:41:51 +11:00
quexeky
b6a54c0d09
chore: Update .gitlab-ci.yml 2025-01-21 08:04:29 +11:00
quexeky
9897698322
build: Version bump & appimage build 2025-01-20 23:13:49 +11:00
quexeky
4ef49cc832
fix: Games not launching due to string semantics 2025-01-20 23:12:57 +11:00
quexeky
6ad383799d
feat(games): Added multi-argument game launch and setup support 2025-01-20 20:03:44 +11:00
quexeky
e0ea8c9a57
chore: Apply stashed changes 2025-01-20 18:22:24 +11:00
quexeky
4fc0855ba1
fix(game downloads): Added error handling for chunk request errors 2025-01-20 18:13:25 +11:00
quexeky
f50818697f
fix: Adding usize to completed_contexts_lock instead of &usize 2025-01-20 17:39:05 +11:00
DecDuck
39f2ebd2d6
fix(download agent): fixed completed indexes 2025-01-20 17:29:33 +11:00
DecDuck
89ea34c94e
fix: assorted fixes 2025-01-20 16:46:57 +11:00
quexeky
92729701c3
chore: Ran cargo clippy & cargo fmt 2025-01-20 08:55:19 +11:00
quexeky
7d4651db69
feat(database): Ensure that any database issues are resolved by standalone functions
Functions are as follows:
- save_db()
- borrow_db_checked()
- borrow_db_mut_checked()
2025-01-20 08:24:06 +11:00
DecDuck
5db9ae5f98
fix(ui): modal stack doesn't cover whole app 2025-01-19 22:23:40 +11:00
DecDuck
4d8eadc491
fix(logs): add file & line to console logs 2025-01-19 22:23:09 +11:00
DecDuck
3ca87fc45b
feat(downloads): re-enable checksums 2025-01-19 22:22:55 +11:00
DecDuck
21204dee69
fix(download manager): don't crash download manager if multiple errors
come in
2025-01-19 22:22:04 +11:00
quexeky
cfc9d13cad
style(logging): Ensured that all logs start with lowercase capital and have no trailing punctuation 2025-01-19 20:36:38 +11:00
quexeky
5bb04dafdd
feat(settings): Allow settings to update UI using fetch_settings command 2025-01-19 19:14:52 +11:00
quexeky
23077040ce
Revert "chore: Update .gitlab-ci.yml"
This reverts commit fc6bab9381.
2025-01-19 18:37:51 +11:00
quexeky
b99ff67e69
chore(logging): Imported appropriate logging macros 2025-01-19 18:37:21 +11:00
quexeky
f183a9d1a2
refactor(logging): Using more appropriate logging statements
Still probably needs some work, but that's enough for now
2025-01-19 18:30:16 +11:00
quexeky
fc6bab9381
chore: Update .gitlab-ci.yml 2025-01-19 17:18:08 +11:00
quexeky
170fde5e23
feat(errors): Using SerializeDisplay for better error management with Result 2025-01-19 17:17:51 +11:00
DecDuck
c2f54c1dbc
fix: fix other metadata endpoints 2025-01-19 16:15:43 +11:00
DecDuck
d83aae6dc4
fix(install ui): stop loading on error 2025-01-19 15:44:00 +11:00
quexeky
9a184a8f35
refactor(remote): Created separate function to generate requests 2025-01-19 15:09:35 +11:00
quexeky
fd30b3e402
feat(progress): Added rolling progress window
Still needs tweaks on specific timings, as well as cleanup
2025-01-19 09:48:04 +11:00
quexeky
cf19477d4d
chore: Progress on rolling progress window 2025-01-19 08:41:20 +11:00
quexeky
5f5cbd07c6
chore(downloads): Progress on terminator 2025-01-16 18:57:40 +11:00
quexeky
0381b8b8cb
chore(exit): Progress on cleanup and exit 2025-01-16 18:57:40 +11:00
quexeky
9369ff14b8
chore(progress): Added rolling_progress_updates.rs
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-16 17:23:53 +11:00
quexeky
7c3140e424
feat(logging): Added line numbers to file logging and highlighting to console 2025-01-16 16:48:31 +11:00
quexeky
9e29aa7a76
fix(settings): Broken command invoke logic in settings/downloads.vue 2025-01-14 07:57:33 +11:00
quexeky
604d5b5884
Implement better error system and segregate errors and commands (#23)
* chore: Progress on amend_settings command

Signed-off-by: quexeky <git@quexeky.dev>

* chore(errors): Progress on better error handling with segragation of files

* chore: Progress on amend_settings command

Signed-off-by: quexeky <git@quexeky.dev>

* chore(commands): Separated commands under each subdirectory into respective commands.rs files

Signed-off-by: quexeky <git@quexeky.dev>

* chore(errors): Almost all errors and commands have been segregated

* chore(errors): Added drop server error

Signed-off-by: quexeky <git@quexeky.dev>

* feat(core): Update to using nightly compiler

Signed-off-by: quexeky <git@quexeky.dev>

* chore(errors): More progress on error handling

Signed-off-by: quexeky <git@quexeky.dev>

* chore(errors): Implementing Try and FromResidual for UserValue

Signed-off-by: quexeky <git@quexeky.dev>

* refactor(errors): Segregated errors and commands from code, and made commands return UserValue struct

Signed-off-by: quexeky <git@quexeky.dev>

* fix(errors): Added missing files

* chore(errors): Convert match statement to map_err

* feat(settings): Implemented settings editing from UI

* feat(errors): Clarified return values from retry_connect command

* chore(errors): Moved autostart commands to autostart.rs

* chore(process manager): Converted launch_process function for games to use game_id

---------

Signed-off-by: quexeky <git@quexeky.dev>
2025-01-13 21:44:57 +11:00
Aden Lindsay
245a84d20b
feat(debug): use shift or DEBUG RUST_LOG to show Debug Info
* Update settings.vue to have a conditional debug page

* Update debug.rs to add RUST_LOG status fetching
2025-01-09 19:44:04 +11:00
Aden Lindsay
f1c8bbf8dd
refactor(compat): remove unnecessary compat code (#20)
* Delete pages/settings/compatibility.vue

* Update settings.vue

* Update debug.vue

* Update lib.rs

* Update compat.rs
2025-01-09 13:44:27 +11:00
DecDuck
60d0a48a1a
fix(handle invalid database): use set_file_name instead of pushing to
strings
2025-01-09 12:27:32 +11:00
DecDuck
7ab53f3357
fix(metadata): update routes for new server 2025-01-09 12:11:39 +11:00
quexeky
4e93eb440c
feat(download manager): Added UI to change download threads
Co-authored-by: AdenMGB <140392385+AdenMGB@users.noreply.github.com>
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-06 20:56:19 +11:00
quexeky
94cf6788d8
chore: More refining info!() statements
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-06 20:46:08 +11:00
quexeky
3eda9799c5
chore: Removed tools/
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-06 20:06:01 +11:00
quexeky
f29e989aff
chore: Removed tests/
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-06 20:05:19 +11:00
quexeky
182361e598
chore: General cleanup
- Changed some info!() statements to debug!() and warn!()
- Removed most Turbofish syntax cases
- Removed InvalidCodeError and replaced it with InvalidResponse

Signed-off-by: quexeky <git@quexeky.dev>
2025-01-06 20:04:34 +11:00
quexeky
50f37fd022
chore: Moved generateGameMeta.ts to composables, using PathBuf instead of String for install_dirs
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-06 09:22:18 +11:00
quexeky
5ea47d733b
feat(settings): Added max_download_threads setting and separated settings from db
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-06 07:36:35 +11:00
quexeky
2822b7a593
chore: More cleanup after cargo clippy
Also added some type efficiency improvements (using references where possible and added SliceDeque crate)

Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 21:08:27 +11:00
quexeky
82804ebc67
refactor: Ran cargo clippy & fmt
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 20:29:15 +11:00
quexeky
8aad64ffa7
fix(db): Added Settings component
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:44:18 +11:00
quexeky
9e82a0b3c3
fix(game downloads): Accidentally was attempting to lock onto something that was already in scope
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:05:00 +11:00
quexeky
6ea4cf2797
perf(game downloads): Moved some variable declarations outside of the spawned download thread
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:05:00 +11:00
quexeky
165a9671fd
chore(README): Updated README.md
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:04:47 +11:00
quexeky
25ba200a5e
feat(database): Added database corruption dialog
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:04:47 +11:00
quexeky
32ae7d5385
feat(recovery): Added database recovery
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:04:47 +11:00
quexeky
005bab2fb8
fix(kill game): Re-enabled killing games
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:04:47 +11:00
quexeky
06d1e9ed95
refactor(downloads): Moved all files relevant to game downloads to their own directory
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:04:47 +11:00
quexeky
a56ee25581
fix(uninstalling): Re-enabled uninstalling apps
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:04:21 +11:00
quexeky
dceaa56ade
fix(game launcher): Renamed game_id to id
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:04:07 +11:00
quexeky
6159319172
feat(download manager): Added generic download manager
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:04:07 +11:00
quexeky
8be1dd435c
refactor(download manager): Removed Arc requirement for DownloadableMetadata
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:02:19 +11:00
quexeky
cac612b176
refactor(download manager): Fully separate & generic download manager
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:02:19 +11:00
quexeky
6568faaf4f
refactor(download manager): Removed all references to anything outside of the DownloadManager
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:00:47 +11:00
quexeky
ea70ec9453
chore(download manager): Added manage_go_signal command
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:00:47 +11:00
quexeky
f64782e5d4
chore(download manager): Added manage_queue_signal
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:00:47 +11:00
quexeky
a2e63aa2c8
chore(tool manager): Added ToolDownloadAgent
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:00:18 +11:00
quexeky
78149bbb3c
Revert "refactor(download manager): Moved manifest and stored_manifest to download_manager"
This reverts commit 8db2393346.
2025-01-05 19:00:18 +11:00
quexeky
a846eed306
refactor(download manager): Moved manifest and stored_manifest to download_manager
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:00:18 +11:00
quexeky
1a89135342
chore(download manager): Ensure that Downloadable is also send and sync
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:00:18 +11:00
quexeky
0a2ac25b1c
chore(download manager): Some easy cleanup of the download manager
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:00:18 +11:00
quexeky
aed58e49bc
refactor(download manager): Renamed GameDonwloadError to ApplicationDownloadError and moved
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 19:00:18 +11:00
quexeky
881fcc6abe
chore(download manager): Renamed most instances of "game" outside of actual game downloads
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 18:59:08 +11:00
quexeky
b4d70a35b3
refactor(download manager): Added Downloadable trait and replaced references to GameDownloadAgent
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 18:52:00 +11:00
quexeky
b6c64e56e5
refactor(download manager): Moved download manager to separate directory
Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 18:52:00 +11:00
quexeky
3299c71b3d
chore(tool manager): Progress on adding tools
Going to try changing around the download manager to take a generic trait rather than specifically for game downloads

Signed-off-by: quexeky <git@quexeky.dev>
2025-01-05 18:49:32 +11:00
DecDuck
2c8164e54f
feat(library ui): add installed ui in the library menu 2025-01-05 18:32:22 +11:00
Aden Lindsay
02f8591a60
feat(settings): add debug page
* Create debug.rs

* Update settings.vue to add tab for debug

* Update main.scss to add light theme

* Update interface.vue to add light mode

* Create debug.vue

* Update debug.vue too add open log button

* Update lib.rs

* Update debug.rs

* Update debug.rs

* Update lib.rs

* Update lib.rs

* Update debug.rs

* Update debug.vue

* fix(debug): refactor and cleanup

* revert(theme): revert light theming

---------

Co-authored-by: DecDuck <declanahofmeyr@gmail.com>
2025-01-05 17:56:33 +11:00
Aden Lindsay
0a0d9d6294
feat(account settings): Add signout functionality (#16)
* Create account.vue with logout button

* Update auth.rs to add signout command

* Update lib.rs to pass sign_out command to frontend
2025-01-01 13:23:18 +11:00
DecDuck
a17311a88d
fix(download manager): fix incorrect error assumptions & update types 2024-12-31 00:08:05 +11:00
DecDuck
472eb1d435
squash(autostart): added adenmgb's autostart feature
Squashed commit of the following:

commit 085cd9481dee748ee84c1a8f9dcd8ef0b01105ab
Author: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
Date:   Mon Dec 30 16:29:41 2024 +1030

    Update lib.rs for the DB sync of autostart

commit 86f2fb19bde3933ec6b5cd82701de0d306121659
Author: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
Date:   Mon Dec 30 16:29:13 2024 +1030

    Update db.rs to accomidate the settings sync

commit ece11e7581c9aef55588b2e829379b1224021a45
Author: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
Date:   Mon Dec 30 16:27:48 2024 +1030

    Update autostart.rs to include DB

commit 7ea8a24fdc2ff98379694ce8e347a40fcfd5fea3
Author: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
Date:   Mon Dec 30 15:17:38 2024 +1030

    Add files via upload

commit af2f232d94fa449d9e20df737ce81ebded87fd50
Author: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
Date:   Mon Dec 30 15:17:09 2024 +1030

    Delete src-tauri/Cargo.toml

commit 5d27b65612457de6eb30835d5423b9d96fd5a596
Author: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
Date:   Mon Dec 30 15:15:42 2024 +1030

    Add files via upload

commit 2eea7b97a876e23cc0d6daec37f1b75af9bf3ae2
Author: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
Date:   Mon Dec 30 15:15:31 2024 +1030

    Delete src-tauri/src/lib.rs

commit 9a635a10d1340f86c74812113284b115b34b9bbe
Author: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
Date:   Mon Dec 30 15:14:49 2024 +1030

    Add files via upload

commit 2fb049531a082fbdd217aba694819b7a3f954a55
Author: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
Date:   Mon Dec 30 15:13:37 2024 +1030

    Add files via upload

commit ea1be4d7505a9ab16bda338491c0ec313d0bc586
Author: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
Date:   Mon Dec 30 15:13:20 2024 +1030

    Delete pages/settings/index.vue
2024-12-30 17:31:03 +11:00
DecDuck
6b96e408b2
feat(process): shared child with stop command 2024-12-30 15:37:29 +11:00
quexeky
c3f62222fe
fix(deep links): Re-enabled deep links
Signed-off-by: quexeky <git@quexeky.dev>
2024-12-30 14:18:42 +11:00
quexeky
01e6162527
feat(process manager): Game kill tauri command
Signed-off-by: quexeky <git@quexeky.dev>
2024-12-30 13:50:26 +11:00
quexeky
88b2505e71
feat(Compat): Implemented spawning with umu (using umu-wrapper-lib)
Signed-off-by: quexeky <git@quexeky.dev>
2024-12-30 13:12:27 +11:00
DecDuck
95f2174f8d
fix(scrollbars): fix ugly scrollbars on edge webview 2024-12-29 20:45:45 +11:00
DecDuck
7c90d2b8fd
fix(process): fix poorly designed parsing for executables with spaces 2024-12-28 10:13:26 +11:00
DecDuck
d7b0302bdd
fix(library page): fix install button 2024-12-27 16:33:04 +11:00
DecDuck
3ccd44466f
merge(SeeThruHead): add nvm rc
Signed-off-by: DecDuck <declanahofmeyr@gmail.com>
2024-12-27 14:33:23 +11:00
Aden Lindsay
93b8b83c20
Add files via upload 2024-12-27 14:32:56 +11:00
Aden Lindsay
1861659daa
Delete pages/library.vue 2024-12-27 14:32:56 +11:00
DecDuck
327628b780
fix(install flow): clear stale data before requesting new 2024-12-27 14:32:56 +11:00
DecDuck
f4ac1c87cd
feat(install modal): add note about more install dirs 2024-12-27 14:32:56 +11:00
DecDuck
03fa3646fa
merge(adenmgb): library ui
Signed-off-by: DecDuck <declanahofmeyr@gmail.com>
2024-12-27 14:30:38 +11:00
seethruhead
a881d8e248 adds nvm rc! 2024-12-26 22:21:26 -05:00
Aden Lindsay
dcb2c0f004
Add files via upload 2024-12-27 13:40:24 +10:30
Aden Lindsay
c722a54132
Delete pages/library.vue 2024-12-27 13:39:50 +10:30
DecDuck
e72662c4a8
fix(install flow): clear stale data before requesting new 2024-12-27 14:04:55 +11:00
DecDuck
139bc0ca36
feat(install modal): add note about more install dirs 2024-12-27 14:03:06 +11:00
DecDuck
949acfc161
feat(auth): offer manual signin 2024-12-27 13:07:10 +11:00
DecDuck
9af0d08875
fix(auth initiate): add better error message 2024-12-27 12:15:30 +11:00
quexeky
dcb1564568
chore: Ran cargo clippy
Signed-off-by: quexeky <git@quexeky.dev>
2024-12-27 11:58:37 +11:00
DecDuck
1f899ec349
feat(download ui): add speed and time remaining information
closes #7

Co-authored-by: AdenMGB <140392385+AdenMGB@users.noreply.github.com>
2024-12-27 11:53:17 +11:00
DecDuck
6a8d0af87d
fix(sign on): add message about nonce expiration 2024-12-26 21:30:08 +11:00
DecDuck
21835858f1
feat(downloads): lockless tracking of downloaded chunks 2024-12-26 17:41:10 +11:00
DecDuck
a135b1321c
feat(process): better process management, including running state 2024-12-26 17:19:19 +11:00
DecDuck
ad92dbec08
feat(errors): better download manager errors + modal 2024-12-26 12:56:54 +11:00
DecDuck
85a08990c3
chore(metadata): update metadata 2024-12-26 12:56:26 +11:00
DecDuck
dd7f5675d8
feat(game): game uninstalling & partial compat 2024-12-26 11:59:26 +11:00
DecDuck
9ea2aa4997
chore(process manager): refactor for generic way to implement cross
platform launchers
2024-12-25 23:05:10 +11:00
26 changed files with 1232 additions and 284 deletions

View file

@ -11,7 +11,7 @@
v-for="(nav, navIdx) in navigation" v-for="(nav, navIdx) in navigation"
:class="[ :class="[
'transition uppercase font-display font-semibold text-md', 'transition uppercase font-display font-semibold text-md',
navIdx === currentPageIndex navIdx === currentNavigation
? 'text-zinc-100' ? 'text-zinc-100'
: 'text-zinc-400 hover:text-zinc-200', : 'text-zinc-400 hover:text-zinc-200',
]" ]"
@ -78,7 +78,7 @@ const navigation: Array<NavigationItem> = [
}, },
]; ];
const currentPageIndex = useCurrentNavigationIndex(navigation); const {currentNavigation} = useCurrentNavigationIndex(navigation);
const quickActions: Array<QuickActionNav> = [ const quickActions: Array<QuickActionNav> = [
{ {

View file

@ -26,5 +26,7 @@ export const useCurrentNavigationIndex = (
currentNavigation.value = calculateCurrentNavIndex(to); currentNavigation.value = calculateCurrentNavIndex(to);
}); });
return currentNavigation; return {currentNavigation, recalculateNavigation: () => {
currentNavigation.value = calculateCurrentNavIndex(route);
}};
}; };

View file

@ -1,28 +1,17 @@
<template> <template>
<div class="flex flex-row h-full"> <div class="flex flex-row h-full">
<div <div class="flex-none max-h-full overflow-y-auto w-64 bg-zinc-950 px-2 py-1">
class="flex-none max-h-full overflow-y-auto w-64 bg-zinc-950 px-2 py-1"
>
<ul class="flex flex-col gap-y-1"> <ul class="flex flex-col gap-y-1">
<NuxtLink <NuxtLink v-for="(nav, navIdx) in navigation" :key="nav.route" :class="[
v-for="(nav, navIdx) in navigation" 'transition-all duration-200 rounded-lg flex items-center py-1.5 px-3',
:key="nav.route" navIdx === currentNavigation
:class="[ ? 'bg-zinc-800 text-zinc-100'
'transition-all duration-200 rounded-lg flex items-center py-1.5 px-3', : nav.isInstalled.value
navIdx === currentNavigationIndex
? 'bg-zinc-800 text-zinc-100'
: nav.isInstalled.value
? 'text-zinc-300 hover:bg-zinc-800/90 hover:text-zinc-200' ? 'text-zinc-300 hover:bg-zinc-800/90 hover:text-zinc-200'
: 'text-zinc-500 hover:bg-zinc-800/70 hover:text-zinc-300', : 'text-zinc-500 hover:bg-zinc-800/70 hover:text-zinc-300',
]" ]" :href="nav.route">
:href="nav.route"
>
<div class="flex items-center w-full gap-x-3"> <div class="flex items-center w-full gap-x-3">
<img <img class="size-6 flex-none object-cover bg-zinc-900 rounded" :src="icons[nav.id]" alt="" />
class="size-6 flex-none object-cover bg-zinc-900 rounded"
:src="icons[navIdx]"
alt=""
/>
<p class="truncate text-sm font-display leading-6 flex-1"> <p class="truncate text-sm font-display leading-6 flex-1">
{{ nav.label }} {{ nav.label }}
</p> </p>
@ -31,34 +20,47 @@
</ul> </ul>
</div> </div>
<div class="grow overflow-y-auto"> <div class="grow overflow-y-auto">
<NuxtPage :libraryDownloadError = "libraryDownloadError" /> <NuxtPage :libraryDownloadError="libraryDownloadError" />
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { GameStatusEnum, type Game, type NavigationItem } from "~/types"; import { listen } from "@tauri-apps/api/event";
import { GameStatusEnum, type Game, type GameStatus, type NavigationItem } from "~/types";
let libraryDownloadError = false; let libraryDownloadError = false;
async function calculateGames(): Promise<Game[]> { const games: { [key: string]: { game: Game, status: Ref<GameStatus, GameStatus> } } = {};
const icons: { [key: string]: string } = {};
const rawGames: Ref<Game[], Game[]> = ref([])
async function calculateGames() {
try { try {
return await invoke("fetch_library"); rawGames.value = await invoke("fetch_library");
for (const game of rawGames.value) {
if (games[game.id]) continue;
games[game.id] = await useGame(game.id);
}
for (const game of rawGames.value) {
if (icons[game.id]) continue;
icons[game.id] = await useObject(game.mIconId);
}
} }
catch(e) { catch (e) {
console.log(e)
libraryDownloadError = true; libraryDownloadError = true;
return new Array(); return new Array();
} }
} }
const rawGames: Array<Game> = await calculateGames(); await calculateGames();
const games = await Promise.all(rawGames.map((e) => useGame(e.id)));
const icons = await Promise.all( const navigation = computed(() => rawGames.value.map((game) => {
games.map(({ game, status }) => useObject(game.mIconId)) const status = games[game.id].status;
);
const navigation = games.map(({ game, status }) => {
const isInstalled = computed( const isInstalled = computed(
() => () =>
status.value.type == GameStatusEnum.Installed || status.value.type == GameStatusEnum.Installed ||
@ -70,9 +72,18 @@ const navigation = games.map(({ game, status }) => {
route: `/library/${game.id}`, route: `/library/${game.id}`,
prefix: `/library/${game.id}`, prefix: `/library/${game.id}`,
isInstalled, isInstalled,
id: game.id,
}; };
return item; return item;
}); })
);
const { currentNavigation, recalculateNavigation } = useCurrentNavigationIndex(navigation.value);
listen("update_library", async (event) => {
console.log("Updating library");
await calculateGames()
recalculateNavigation();
})
const currentNavigationIndex = useCurrentNavigationIndex(navigation);
</script> </script>

View file

@ -10,13 +10,13 @@
<ul role="list" class="-mx-2 space-y-1"> <ul role="list" class="-mx-2 space-y-1">
<li v-for="(item, itemIdx) in navigation" :key="item.prefix"> <li v-for="(item, itemIdx) in navigation" :key="item.prefix">
<NuxtLink :href="item.route" :class="[ <NuxtLink :href="item.route" :class="[
itemIdx === currentPageIndex itemIdx === currentNavigation
? 'bg-zinc-800/50 text-zinc-100' ? 'bg-zinc-800/50 text-zinc-100'
: 'text-zinc-400 hover:bg-zinc-800/30 hover:text-zinc-200', : 'text-zinc-400 hover:bg-zinc-800/30 hover:text-zinc-200',
'transition group flex gap-x-3 rounded-md p-2 pr-12 text-sm font-semibold leading-6', 'transition group flex gap-x-3 rounded-md p-2 pr-12 text-sm font-semibold leading-6',
]"> ]">
<component :is="item.icon" :class="[ <component :is="item.icon" :class="[
itemIdx === currentPageIndex itemIdx === currentNavigation
? 'text-zinc-100' ? 'text-zinc-100'
: 'text-zinc-400 group-hover:text-zinc-200', : 'text-zinc-400 group-hover:text-zinc-200',
'transition h-6 w-6 shrink-0', 'transition h-6 w-6 shrink-0',
@ -112,10 +112,10 @@ const navigation = computed(() => [
const currentPlatform = platform(); const currentPlatform = platform();
// Use .value to unwrap the computed ref // Use .value to unwrap the computed ref
const currentPageIndex = useCurrentNavigationIndex(navigation.value); const {currentNavigation} = useCurrentNavigationIndex(navigation.value);
// Watch for navigation changes and update currentPageIndex // Watch for navigation changes and update currentPageIndex
watch(navigation, (newNav) => { watch(navigation, (newNav) => {
currentPageIndex.value = useCurrentNavigationIndex(newNav).value; currentNavigation.value = useCurrentNavigationIndex(newNav).currentNavigation.value;
}); });
</script> </script>

View file

@ -1,9 +1,7 @@
<template> <template>
<div> <div>
<div class="border-b border-zinc-600 py-2 px-1"> <div class="border-b border-zinc-600 py-2 px-1">
<div <div class="-ml-4 -mt-2 flex flex-wrap items-center justify-between sm:flex-nowrap">
class="-ml-4 -mt-2 flex flex-wrap items-center justify-between sm:flex-nowrap"
>
<div class="ml-4 mt-2"> <div class="ml-4 mt-2">
<h3 class="text-base font-display font-semibold text-zinc-100"> <h3 class="text-base font-display font-semibold text-zinc-100">
Install directories Install directories
@ -15,27 +13,17 @@
</p> </p>
</div> </div>
<div class="ml-4 mt-2 shrink-0"> <div class="ml-4 mt-2 shrink-0">
<button <button @click="() => (open = true)" type="button"
@click="() => (open = true)" class="relative inline-flex items-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600">
type="button"
class="relative inline-flex items-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
Add new directory Add new directory
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<ul role="list" class="divide-y divide-gray-800"> <ul role="list" class="divide-y divide-gray-800">
<li <li v-for="(dir, dirIdx) in dirs" :key="dir" class="flex justify-between gap-x-6 py-5">
v-for="(dir, dirIdx) in dirs"
:key="dir"
class="flex justify-between gap-x-6 py-5"
>
<div class="flex min-w-0 gap-x-4"> <div class="flex min-w-0 gap-x-4">
<FolderIcon <FolderIcon class="h-6 w-6 text-blue-600 flex-none rounded-full" alt="" />
class="h-6 w-6 text-blue-600 flex-none rounded-full"
alt=""
/>
<div class="min-w-0 flex-auto"> <div class="min-w-0 flex-auto">
<p class="text-sm/6 text-zinc-100"> <p class="text-sm/6 text-zinc-100">
{{ dir }} {{ dir }}
@ -43,16 +31,12 @@
</div> </div>
</div> </div>
<div class="flex shrink-0 items-center gap-x-6"> <div class="flex shrink-0 items-center gap-x-6">
<button <button @click="() => deleteDirectory(dirIdx)" :disabled="dirs.length <= 1" :class="[
@click="() => deleteDirectory(dirIdx)" dirs.length <= 1
:disabled="dirs.length <= 1" ? 'text-zinc-700'
:class="[ : 'text-zinc-400 hover:text-zinc-100',
dirs.length <= 1 '-m-2.5 block p-2.5',
? 'text-zinc-700' ]">
: 'text-zinc-400 hover:text-zinc-100',
'-m-2.5 block p-2.5',
]"
>
<span class="sr-only">Open options</span> <span class="sr-only">Open options</span>
<TrashIcon class="size-5" aria-hidden="true" /> <TrashIcon class="size-5" aria-hidden="true" />
</button> </button>
@ -72,37 +56,44 @@
Maximum Download Threads Maximum Download Threads
</label> </label>
<div class="mt-2"> <div class="mt-2">
<input <input type="number" name="threads" id="threads" min="1" max="32" v-model="downloadThreads"
type="number" @keypress="validateNumberInput" @paste="validatePaste"
name="threads" class="block w-full rounded-md border-0 py-1.5 text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 bg-zinc-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6" />
id="threads"
min="1"
max="32"
v-model="downloadThreads"
@keypress="validateNumberInput"
@paste="validatePaste"
class="block w-full rounded-md border-0 py-1.5 text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 bg-zinc-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
/>
</div> </div>
<p class="mt-2 text-sm text-zinc-400"> <p class="mt-2 text-sm text-zinc-400">
The maximum number of concurrent download threads. Higher values may The maximum number of concurrent download threads. Higher values may
download faster but use more system resources. Default is 4. download faster but use more system resources. Default is 4.
</p> </p>
</div> </div>
<div class="mt-10 space-y-8">
<div class="flex flex-row items-center justify-between">
<div>
<h3 class="text-sm font-medium leading-6 text-zinc-100">Force Offline</h3>
<p class="mt-1 text-sm leading-6 text-zinc-400">
Drop will not make any external connections
</p>
</div>
<Switch v-model="forceOffline" :class="[
forceOffline ? 'bg-blue-600' : 'bg-zinc-700',
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out'
]">
<span :class="[
forceOffline ? 'translate-x-5' : 'translate-x-0',
'pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out'
]" />
</Switch>
</div>
</div>
<div class="mt-6"> <div class="mt-6">
<button <button type="button" @click="saveSettings" :disabled="saveState.loading" :class="[
type="button" 'inline-flex items-center rounded-md px-3 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 transition-colors duration-300',
@click="saveDownloadThreads" saveState.success
:disabled="saveState.loading" ? 'bg-green-600 hover:bg-green-500 focus-visible:outline-green-600'
:class="[ : 'bg-blue-600 hover:bg-blue-500 focus-visible:outline-blue-600',
'inline-flex items-center rounded-md px-3 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 transition-colors duration-300', 'disabled:bg-blue-600/50 disabled:cursor-not-allowed'
saveState.success ]">
? 'bg-green-600 hover:bg-green-500 focus-visible:outline-green-600'
: 'bg-blue-600 hover:bg-blue-500 focus-visible:outline-blue-600',
'disabled:bg-blue-600/50 disabled:cursor-not-allowed'
]"
>
{{ saveState.success ? 'Saved' : 'Save Changes' }} {{ saveState.success ? 'Saved' : 'Save Changes' }}
</button> </button>
</div> </div>
@ -110,49 +101,27 @@
</div> </div>
<TransitionRoot as="template" :show="open"> <TransitionRoot as="template" :show="open">
<Dialog class="relative z-50" @close="open = false"> <Dialog class="relative z-50" @close="open = false">
<TransitionChild <TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0" enter-to="opacity-100"
as="template" leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0">
enter="ease-out duration-300" <div class="fixed inset-0 bg-zinc-950 bg-opacity-75 transition-opacity" />
enter-from="opacity-0"
enter-to="opacity-100"
leave="ease-in duration-200"
leave-from="opacity-100"
leave-to="opacity-0"
>
<div
class="fixed inset-0 bg-zinc-950 bg-opacity-75 transition-opacity"
/>
</TransitionChild> </TransitionChild>
<div class="fixed inset-0 z-10 w-screen overflow-y-auto"> <div class="fixed inset-0 z-10 w-screen overflow-y-auto">
<div <div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0" <TransitionChild as="template" enter="ease-out duration-300"
>
<TransitionChild
as="template"
enter="ease-out duration-300"
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enter-to="opacity-100 translate-y-0 sm:scale-100" enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200"
leave="ease-in duration-200"
leave-from="opacity-100 translate-y-0 sm:scale-100" leave-from="opacity-100 translate-y-0 sm:scale-100"
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
>
<DialogPanel <DialogPanel
class="relative transform overflow-hidden rounded-lg bg-zinc-900 px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6" class="relative transform overflow-hidden rounded-lg bg-zinc-900 px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
>
<div class="sm:flex sm:items-start"> <div class="sm:flex sm:items-start">
<div class="mt-3 w-full sm:ml-4 sm:mt-0"> <div class="mt-3 w-full sm:ml-4 sm:mt-0">
<div> <div>
<label <label for="dir" class="block text-sm/6 font-medium text-zinc-100">Select game directory</label>
for="dir"
class="block text-sm/6 font-medium text-zinc-100"
>Select game directory</label
>
<div class="mt-2"> <div class="mt-2">
<button <button @click="() => selectDirectory()"
@click="() => selectDirectory()" class="block text-left w-full rounded-md border-0 px-3 py-1.5 text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 bg-zinc-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm/6">
class="block text-left w-full rounded-md border-0 px-3 py-1.5 text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 bg-zinc-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm/6"
>
{{ {{
currentDirectory ?? "Click to select a directory..." currentDirectory ?? "Click to select a directory..."
}} }}
@ -165,36 +134,25 @@
</div> </div>
</div> </div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse"> <div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<LoadingButton <LoadingButton :disabled="currentDirectory == undefined" type="button" :loading="createDirectoryLoading"
:disabled="currentDirectory == undefined" @click="() => submitDirectory()" :class="[
type="button"
:loading="createDirectoryLoading"
@click="() => submitDirectory()"
:class="[
'inline-flex w-full shadow-sm sm:ml-3 sm:w-auto', 'inline-flex w-full shadow-sm sm:ml-3 sm:w-auto',
currentDirectory === undefined currentDirectory === undefined
? 'text-zinc-400 bg-blue-600/10 hover:bg-blue-600/10' ? 'text-zinc-400 bg-blue-600/10 hover:bg-blue-600/10'
: 'text-white bg-blue-600 hover:bg-blue-500', : 'text-white bg-blue-600 hover:bg-blue-500',
]" ]">
>
Add Add
</LoadingButton> </LoadingButton>
<button <button type="button"
type="button"
class="mt-3 inline-flex w-full justify-center rounded-md bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-800 hover:bg-zinc-900 sm:mt-0 sm:w-auto" class="mt-3 inline-flex w-full justify-center rounded-md bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-800 hover:bg-zinc-900 sm:mt-0 sm:w-auto"
@click="() => cancelDirectory()" @click="() => cancelDirectory()" ref="cancelButtonRef">
ref="cancelButtonRef"
>
Cancel Cancel
</button> </button>
</div> </div>
<div v-if="error" class="mt-3 rounded-md bg-red-600/10 p-4"> <div v-if="error" class="mt-3 rounded-md bg-red-600/10 p-4">
<div class="flex"> <div class="flex">
<div class="flex-shrink-0"> <div class="flex-shrink-0">
<XCircleIcon <XCircleIcon class="h-5 w-5 text-red-600" aria-hidden="true" />
class="h-5 w-5 text-red-600"
aria-hidden="true"
/>
</div> </div>
<div class="ml-3"> <div class="ml-3">
<h3 class="text-sm font-medium text-red-600"> <h3 class="text-sm font-medium text-red-600">
@ -220,6 +178,7 @@ import {
} from "@headlessui/vue"; } from "@headlessui/vue";
import { FolderIcon, TrashIcon, XCircleIcon } from "@heroicons/vue/16/solid"; import { FolderIcon, TrashIcon, XCircleIcon } from "@heroicons/vue/16/solid";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { Switch } from '@headlessui/vue'
import { type Settings } from "~/types"; import { type Settings } from "~/types";
const open = ref(false); const open = ref(false);
@ -231,6 +190,7 @@ const dirs = ref<Array<string>>([]);
const settings = await invoke<Settings>("fetch_settings"); const settings = await invoke<Settings>("fetch_settings");
const downloadThreads = ref(settings?.maxDownloadThreads ?? 4); const downloadThreads = ref(settings?.maxDownloadThreads ?? 4);
const forceOffline = ref(settings?.forceOffline ?? false);
const saveState = reactive({ const saveState = reactive({
loading: false, loading: false,
@ -293,21 +253,21 @@ async function deleteDirectory(index: number) {
await updateDirs(); await updateDirs();
} }
async function saveDownloadThreads() { async function saveSettings() {
try { try {
saveState.loading = true; saveState.loading = true;
await invoke("update_settings", { await invoke("update_settings", {
newSettings: { maxDownloadThreads: downloadThreads.value }, newSettings: { maxDownloadThreads: downloadThreads.value, forceOffline: forceOffline.value },
}); });
// Show success state // Show success state
saveState.success = true; saveState.success = true;
// Reset back to normal state after 2 seconds // Reset back to normal state after 2 seconds
setTimeout(() => { setTimeout(() => {
saveState.success = false; saveState.success = false;
}, 2000); }, 2000);
} catch (error) { } catch (error) {
console.error('Failed to save settings:', error); console.error('Failed to save settings:', error);
} finally { } finally {
@ -317,8 +277,8 @@ async function saveDownloadThreads() {
function validateNumberInput(event: KeyboardEvent) { function validateNumberInput(event: KeyboardEvent) {
// Allow only numbers and basic control keys // Allow only numbers and basic control keys
if (!/^\d$/.test(event.key) && if (!/^\d$/.test(event.key) &&
!['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight'].includes(event.key)) { !['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
event.preventDefault(); event.preventDefault();
} }
} }

926
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -50,6 +50,11 @@ slice-deque = "0.3.0"
throttle_my_fn = "0.2.6" throttle_my_fn = "0.2.6"
parking_lot = "0.12.3" parking_lot = "0.12.3"
atomic-instant-full = "0.1.0" atomic-instant-full = "0.1.0"
cacache = "13.1.0"
bincode = "1.3.3"
http-serde = "2.1.1"
reqwest-middleware = "0.4.0"
reqwest-middleware-cache = "0.1.1"
[dependencies.tauri] [dependencies.tauri]
version = "2.1.1" version = "2.1.1"

View file

@ -74,7 +74,8 @@ pub fn update_settings(new_settings: Value) {
} }
let new_settings: Settings = serde_json::from_value(current_settings).unwrap(); let new_settings: Settings = serde_json::from_value(current_settings).unwrap();
db_lock.settings = new_settings; db_lock.settings = new_settings;
println!("new Settings: {:?}", db_lock.settings); drop(db_lock);
save_db();
} }
#[tauri::command] #[tauri::command]
pub fn fetch_settings() -> Settings { pub fn fetch_settings() -> Settings {

View file

@ -46,7 +46,7 @@ pub enum GameDownloadStatus {
} }
// Stuff that shouldn't be synced to disk // Stuff that shouldn't be synced to disk
#[derive(Clone, Serialize)] #[derive(Clone, Serialize, Deserialize)]
pub enum ApplicationTransientStatus { pub enum ApplicationTransientStatus {
Downloading { version_name: String }, Downloading { version_name: String },
Uninstalling {}, Uninstalling {},
@ -98,9 +98,10 @@ pub struct Database {
pub base_url: String, pub base_url: String,
pub applications: DatabaseApplications, pub applications: DatabaseApplications,
pub prev_database: Option<PathBuf>, pub prev_database: Option<PathBuf>,
pub cache_dir: PathBuf
} }
impl Database { impl Database {
fn new<T: Into<PathBuf>>(games_base_dir: T, prev_database: Option<PathBuf>) -> Self { fn new<T: Into<PathBuf>>(games_base_dir: T, prev_database: Option<PathBuf>, cache_dir: PathBuf) -> Self {
Self { Self {
applications: DatabaseApplications { applications: DatabaseApplications {
install_dirs: vec![games_base_dir.into()], install_dirs: vec![games_base_dir.into()],
@ -112,10 +113,8 @@ impl Database {
prev_database, prev_database,
base_url: "".to_owned(), base_url: "".to_owned(),
auth: None, auth: None,
settings: Settings { settings: Settings::default(),
autostart: false, cache_dir,
max_download_threads: 4,
},
} }
} }
} }
@ -150,21 +149,23 @@ impl DatabaseImpls for DatabaseInterface {
let db_path = data_root_dir.join("drop.db"); let db_path = data_root_dir.join("drop.db");
let games_base_dir = data_root_dir.join("games"); let games_base_dir = data_root_dir.join("games");
let logs_root_dir = data_root_dir.join("logs"); let logs_root_dir = data_root_dir.join("logs");
let cache_dir = data_root_dir.join("cache");
debug!("creating data directory at {:?}", data_root_dir); debug!("creating data directory at {:?}", data_root_dir);
create_dir_all(data_root_dir.clone()).unwrap(); create_dir_all(data_root_dir.clone()).unwrap();
create_dir_all(games_base_dir.clone()).unwrap(); create_dir_all(&games_base_dir).unwrap();
create_dir_all(logs_root_dir.clone()).unwrap(); create_dir_all(&logs_root_dir).unwrap();
create_dir_all(&cache_dir).unwrap();
let exists = fs::exists(db_path.clone()).unwrap(); let exists = fs::exists(db_path.clone()).unwrap();
match exists { match exists {
true => match PathDatabase::load_from_path(db_path.clone()) { true => match PathDatabase::load_from_path(db_path.clone()) {
Ok(db) => db, Ok(db) => db,
Err(e) => handle_invalid_database(e, db_path, games_base_dir), Err(e) => handle_invalid_database(e, db_path, games_base_dir, cache_dir),
}, },
false => { false => {
let default = Database::new(games_base_dir, None); let default = Database::new(games_base_dir, None, cache_dir);
debug!( debug!(
"Creating database at path {}", "Creating database at path {}",
db_path.as_os_str().to_str().unwrap() db_path.as_os_str().to_str().unwrap()
@ -204,6 +205,7 @@ fn handle_invalid_database(
_e: RustbreakError, _e: RustbreakError,
db_path: PathBuf, db_path: PathBuf,
games_base_dir: PathBuf, games_base_dir: PathBuf,
cache_dir: PathBuf
) -> rustbreak::Database<Database, rustbreak::backend::PathBackend, DropDatabaseSerializer> { ) -> rustbreak::Database<Database, rustbreak::backend::PathBackend, DropDatabaseSerializer> {
let new_path = { let new_path = {
let time = Utc::now().timestamp(); let time = Utc::now().timestamp();
@ -220,6 +222,7 @@ fn handle_invalid_database(
let db = Database::new( let db = Database::new(
games_base_dir.into_os_string().into_string().unwrap(), games_base_dir.into_os_string().into_string().unwrap(),
Some(new_path), Some(new_path),
cache_dir
); );
PathDatabase::create_at_path(db_path, db).expect("Database could not be created") PathDatabase::create_at_path(db_path, db).expect("Database could not be created")

View file

@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
pub struct Settings { pub struct Settings {
pub autostart: bool, pub autostart: bool,
pub max_download_threads: usize, pub max_download_threads: usize,
pub force_offline: bool
// ... other settings ... // ... other settings ...
} }
impl Default for Settings { impl Default for Settings {
@ -12,6 +13,7 @@ impl Default for Settings {
Self { Self {
autostart: false, autostart: false,
max_download_threads: 4, max_download_threads: 4,
force_offline: false
} }
} }
} }

View file

@ -48,7 +48,7 @@ pub enum DownloadManagerSignal {
Uninstall(DownloadableMetadata), Uninstall(DownloadableMetadata),
} }
#[derive(Debug, Clone)] #[derive(Debug)]
pub enum DownloadManagerStatus { pub enum DownloadManagerStatus {
Downloading, Downloading,
Paused, Paused,

View file

@ -255,7 +255,7 @@ impl DownloadManagerBuilder {
} }
Err(e) => { Err(e) => {
error!("download {:?} has error {}", download_agent.metadata(), &e); error!("download {:?} has error {}", download_agent.metadata(), &e);
download_agent.on_error(&app_handle, e.clone()); download_agent.on_error(&app_handle, &e);
sender.send(DownloadManagerSignal::Error(e)).unwrap(); sender.send(DownloadManagerSignal::Error(e)).unwrap();
} }
} }
@ -287,7 +287,7 @@ impl DownloadManagerBuilder {
fn manage_error_signal(&mut self, error: ApplicationDownloadError) { fn manage_error_signal(&mut self, error: ApplicationDownloadError) {
debug!("got signal Error"); debug!("got signal Error");
if let Some(current_agent) = self.current_download_agent.clone() { if let Some(current_agent) = self.current_download_agent.clone() {
current_agent.on_error(&self.app_handle, error.clone()); current_agent.on_error(&self.app_handle, &error);
self.stop_and_wait_current_download(); self.stop_and_wait_current_download();
self.remove_and_cleanup_front_download(&current_agent.metadata()); self.remove_and_cleanup_front_download(&current_agent.metadata());

View file

@ -16,7 +16,7 @@ pub trait Downloadable: Send + Sync {
fn status(&self) -> DownloadStatus; fn status(&self) -> DownloadStatus;
fn metadata(&self) -> DownloadableMetadata; fn metadata(&self) -> DownloadableMetadata;
fn on_initialised(&self, app_handle: &AppHandle); fn on_initialised(&self, app_handle: &AppHandle);
fn on_error(&self, app_handle: &AppHandle, error: ApplicationDownloadError); fn on_error(&self, app_handle: &AppHandle, error: &ApplicationDownloadError);
fn on_complete(&self, app_handle: &AppHandle); fn on_complete(&self, app_handle: &AppHandle);
fn on_incomplete(&self, app_handle: &AppHandle); fn on_incomplete(&self, app_handle: &AppHandle);
fn on_cancelled(&self, app_handle: &AppHandle); fn on_cancelled(&self, app_handle: &AppHandle);

View file

@ -8,7 +8,7 @@ use serde_with::SerializeDisplay;
use super::{remote_access_error::RemoteAccessError, setup_error::SetupError}; use super::{remote_access_error::RemoteAccessError, setup_error::SetupError};
// TODO: Rename / separate from downloads // TODO: Rename / separate from downloads
#[derive(Debug, Clone, SerializeDisplay)] #[derive(Debug, SerializeDisplay)]
pub enum ApplicationDownloadError { pub enum ApplicationDownloadError {
Communication(RemoteAccessError), Communication(RemoteAccessError),
Checksum, Checksum,

View file

@ -10,7 +10,7 @@ use url::ParseError;
use super::drop_server_error::DropServerError; use super::drop_server_error::DropServerError;
#[derive(Debug, Clone, SerializeDisplay)] #[derive(Debug, SerializeDisplay)]
pub enum RemoteAccessError { pub enum RemoteAccessError {
FetchError(Arc<reqwest::Error>), FetchError(Arc<reqwest::Error>),
ParsingError(ParseError), ParsingError(ParseError),
@ -21,6 +21,7 @@ pub enum RemoteAccessError {
InvalidRedirect, InvalidRedirect,
ManifestDownloadFailed(StatusCode, String), ManifestDownloadFailed(StatusCode, String),
OutOfSync, OutOfSync,
Cache(cacache::Error),
Generic(String), Generic(String),
} }
@ -52,6 +53,7 @@ impl Display for RemoteAccessError {
), ),
RemoteAccessError::OutOfSync => write!(f, "server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"), RemoteAccessError::OutOfSync => write!(f, "server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"),
RemoteAccessError::Generic(message) => write!(f, "{}", message), RemoteAccessError::Generic(message) => write!(f, "{}", message),
RemoteAccessError::Cache(error) => write!(f, "Cache Error: {}", error),
} }
} }
} }

View file

@ -1,9 +1,9 @@
use std::sync::Mutex; use std::sync::Mutex;
use tauri::AppHandle; use tauri::{AppHandle, Manager};
use crate::{ use crate::{
database::db::GameVersion, error::{library_error::LibraryError, remote_access_error::RemoteAccessError}, games::library::{get_current_meta, uninstall_game_logic}, AppState database::db::GameVersion, error::{library_error::LibraryError, remote_access_error::RemoteAccessError}, games::library::{fetch_game_logic_offline, fetch_library_logic_offline, get_current_meta, uninstall_game_logic}, offline, AppState
}; };
use super::{ use super::{
@ -15,16 +15,16 @@ use super::{
}; };
#[tauri::command] #[tauri::command]
pub fn fetch_library(app: AppHandle) -> Result<Vec<Game>, RemoteAccessError> { pub fn fetch_library(state: tauri::State<'_, Mutex<AppState>>) -> Result<Vec<Game>, RemoteAccessError> {
fetch_library_logic(app) offline!(state, fetch_library_logic, fetch_library_logic_offline, state)
} }
#[tauri::command] #[tauri::command]
pub fn fetch_game( pub fn fetch_game(
game_id: String, game_id: String,
app: tauri::AppHandle, state: tauri::State<'_, Mutex<AppState>>
) -> Result<FetchGameStruct, RemoteAccessError> { ) -> Result<FetchGameStruct, RemoteAccessError> {
fetch_game_logic(game_id, app) offline!(state, fetch_game_logic, fetch_game_logic_offline, game_id, state)
} }
#[tauri::command] #[tauri::command]
@ -38,7 +38,6 @@ pub fn uninstall_game(game_id: String, app_handle: AppHandle) -> Result<(), Libr
Some(data) => data, Some(data) => data,
None => return Err(LibraryError::MetaNotFound(game_id)), None => return Err(LibraryError::MetaNotFound(game_id)),
}; };
println!("{:?}", meta);
uninstall_game_logic(meta, &app_handle); uninstall_game_logic(meta, &app_handle);
Ok(()) Ok(())

View file

@ -135,7 +135,7 @@ impl GameDownloadAgent {
&[("id", &self.id), ("version", &self.version)], &[("id", &self.id), ("version", &self.version)],
|f| f.header("Authorization", header), |f| f.header("Authorization", header),
) )
.map_err(|e| ApplicationDownloadError::Communication(e))? .map_err(ApplicationDownloadError::Communication)?
.send() .send()
.map_err(|e| ApplicationDownloadError::Communication(e.into()))?; .map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
@ -280,9 +280,13 @@ impl GameDownloadAgent {
) { ) {
Ok(request) => request, Ok(request) => request,
Err(e) => { Err(e) => {
sender.send(DownloadManagerSignal::Error(ApplicationDownloadError::Communication(e))).unwrap(); sender
.send(DownloadManagerSignal::Error(
ApplicationDownloadError::Communication(e),
))
.unwrap();
continue; continue;
}, }
}; };
scope.spawn(move |_| { scope.spawn(move |_| {
@ -362,7 +366,7 @@ impl Downloadable for GameDownloadAgent {
*self.status.lock().unwrap() = DownloadStatus::Queued; *self.status.lock().unwrap() = DownloadStatus::Queued;
} }
fn on_error(&self, app_handle: &tauri::AppHandle, error: ApplicationDownloadError) { fn on_error(&self, app_handle: &tauri::AppHandle, error: &ApplicationDownloadError) {
*self.status.lock().unwrap() = DownloadStatus::Error; *self.status.lock().unwrap() = DownloadStatus::Error;
app_handle app_handle
.emit("download_error", error.to_string()) .emit("download_error", error.to_string())

View file

@ -14,16 +14,17 @@ use crate::download_manager::downloadable_metadata::DownloadableMetadata;
use crate::error::remote_access_error::RemoteAccessError; use crate::error::remote_access_error::RemoteAccessError;
use crate::games::state::{GameStatusManager, GameStatusWithTransient}; use crate::games::state::{GameStatusManager, GameStatusWithTransient};
use crate::remote::auth::generate_authorization_header; use crate::remote::auth::generate_authorization_header;
use crate::remote::cache::{cache_object, get_cached_object};
use crate::remote::requests::make_request; use crate::remote::requests::make_request;
use crate::AppState; use crate::AppState;
#[derive(serde::Serialize)] #[derive(Serialize, Deserialize)]
pub struct FetchGameStruct { pub struct FetchGameStruct {
game: Game, game: Game,
status: GameStatusWithTransient, status: GameStatusWithTransient,
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Game { pub struct Game {
id: String, id: String,
@ -66,7 +67,9 @@ pub struct StatsUpdateEvent {
pub time: usize, pub time: usize,
} }
pub fn fetch_library_logic(app: AppHandle) -> Result<Vec<Game>, RemoteAccessError> { pub fn fetch_library_logic(
state: tauri::State<'_, Mutex<AppState>>,
) -> Result<Vec<Game>, RemoteAccessError> {
let header = generate_authorization_header(); let header = generate_authorization_header();
let client = reqwest::blocking::Client::new(); let client = reqwest::blocking::Client::new();
@ -83,7 +86,6 @@ pub fn fetch_library_logic(app: AppHandle) -> Result<Vec<Game>, RemoteAccessErro
let games: Vec<Game> = response.json()?; let games: Vec<Game> = response.json()?;
let state = app.state::<Mutex<AppState>>();
let mut handle = state.lock().unwrap(); let mut handle = state.lock().unwrap();
let mut db_handle = borrow_db_mut_checked(); let mut db_handle = borrow_db_mut_checked();
@ -97,17 +99,32 @@ pub fn fetch_library_logic(app: AppHandle) -> Result<Vec<Game>, RemoteAccessErro
.insert(game.id.clone(), GameDownloadStatus::Remote {}); .insert(game.id.clone(), GameDownloadStatus::Remote {});
} }
} }
drop(handle); drop(handle);
drop(db_handle);
cache_object("library", &games)?;
Ok(games) Ok(games)
} }
pub fn fetch_library_logic_offline(
_state: tauri::State<'_, Mutex<AppState>>,
) -> Result<Vec<Game>, RemoteAccessError> {
let mut games: Vec<Game> = get_cached_object("library")?;
let db_handle = borrow_db_checked();
games.retain(|game| {
db_handle
.applications
.installed_game_version
.contains_key(&game.id)
});
Ok(games)
}
pub fn fetch_game_logic( pub fn fetch_game_logic(
id: String, id: String,
app: tauri::AppHandle, state: tauri::State<'_, Mutex<AppState>>,
) -> Result<FetchGameStruct, RemoteAccessError> { ) -> Result<FetchGameStruct, RemoteAccessError> {
let state = app.state::<Mutex<AppState>>();
let mut state_handle = state.lock().unwrap(); let mut state_handle = state.lock().unwrap();
let game = state_handle.games.get(&id); let game = state_handle.games.get(&id);
@ -119,6 +136,8 @@ pub fn fetch_game_logic(
status, status,
}; };
cache_object(id, &data)?;
return Ok(data); return Ok(data);
} }
let client = reqwest::blocking::Client::new(); let client = reqwest::blocking::Client::new();
@ -155,9 +174,18 @@ pub fn fetch_game_logic(
status, status,
}; };
cache_object(id, &data)?;
Ok(data) Ok(data)
} }
pub fn fetch_game_logic_offline(
id: String,
_state: tauri::State<'_, Mutex<AppState>>,
) -> Result<FetchGameStruct, RemoteAccessError> {
get_cached_object::<String, FetchGameStruct>(id)
}
pub fn fetch_game_verion_options_logic( pub fn fetch_game_verion_options_logic(
game_id: String, game_id: String,
state: tauri::State<'_, Mutex<AppState>>, state: tauri::State<'_, Mutex<AppState>>,
@ -193,7 +221,7 @@ pub fn fetch_game_verion_options_logic(
} }
pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle) { pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle) {
println!("triggered uninstall for agent"); debug!("triggered uninstall for agent");
let mut db_handle = borrow_db_mut_checked(); let mut db_handle = borrow_db_mut_checked();
db_handle db_handle
.applications .applications
@ -213,6 +241,7 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
return; return;
} }
let previous_state = previous_state.unwrap(); let previous_state = previous_state.unwrap();
if let Some((_, install_dir)) = match previous_state { if let Some((_, install_dir)) = match previous_state {
GameDownloadStatus::Installed { GameDownloadStatus::Installed {
version_name, version_name,
@ -229,6 +258,7 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
.transient_statuses .transient_statuses
.entry(meta.clone()) .entry(meta.clone())
.and_modify(|v| *v = ApplicationTransientStatus::Uninstalling {}); .and_modify(|v| *v = ApplicationTransientStatus::Uninstalling {});
drop(db_handle); drop(db_handle);
let app_handle = app_handle.clone(); let app_handle = app_handle.clone();
@ -239,6 +269,10 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
Ok(_) => { Ok(_) => {
let mut db_handle = borrow_db_mut_checked(); let mut db_handle = borrow_db_mut_checked();
db_handle.applications.transient_statuses.remove(&meta); db_handle.applications.transient_statuses.remove(&meta);
db_handle
.applications
.installed_game_version
.remove(&meta.id);
db_handle db_handle
.applications .applications
.game_statuses .game_statuses
@ -248,6 +282,7 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
save_db(); save_db();
debug!("uninstalled game id {}", &meta.id); debug!("uninstalled game id {}", &meta.id);
app_handle.emit("update_library", {}).unwrap();
push_game_update( push_game_update(
&app_handle, &app_handle,

View file

@ -40,11 +40,13 @@ use log4rs::encode::pattern::PatternEncoder;
use log4rs::Config; use log4rs::Config;
use process::commands::{kill_game, launch_game}; use process::commands::{kill_game, launch_game};
use process::process_manager::ProcessManager; use process::process_manager::ProcessManager;
use remote::auth::{self, generate_authorization_header, recieve_handshake}; use remote::auth::{self, recieve_handshake};
use remote::commands::{ use remote::commands::{
auth_initiate, gen_drop_url, manual_recieve_handshake, retry_connect, sign_out, use_remote, auth_initiate, fetch_drop_object, gen_drop_url, manual_recieve_handshake, retry_connect, sign_out, use_remote
}; };
use remote::fetch_object::{fetch_object, fetch_object_offline};
use remote::requests::make_request; use remote::requests::make_request;
use reqwest::blocking::Body;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::env; use std::env;
use std::path::Path; use std::path::Path;
@ -60,9 +62,10 @@ use tauri::{AppHandle, Manager, RunEvent, WindowEvent};
use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_deep_link::DeepLinkExt;
use tauri_plugin_dialog::DialogExt; use tauri_plugin_dialog::DialogExt;
#[derive(Clone, Copy, Serialize)] #[derive(Clone, Copy, Serialize, Eq, PartialEq)]
pub enum AppStatus { pub enum AppStatus {
NotConfigured, NotConfigured,
Offline,
ServerError, ServerError,
SignedOut, SignedOut,
SignedIn, SignedIn,
@ -236,6 +239,7 @@ pub fn run() {
// Remote // Remote
use_remote, use_remote,
gen_drop_url, gen_drop_url,
fetch_drop_object,
// Library // Library
fetch_library, fetch_library,
fetch_game, fetch_game,
@ -327,7 +331,7 @@ pub fn run() {
} }
_ => { _ => {
println!("menu event not handled: {:?}", event.id); warn!("menu event not handled: {:?}", event.id);
} }
}) })
.build(app) .build(app)
@ -356,35 +360,9 @@ pub fn run() {
Ok(()) Ok(())
}) })
.register_asynchronous_uri_scheme_protocol("object", move |_ctx, request, responder| { .register_asynchronous_uri_scheme_protocol("object", move |ctx, request, responder| {
// Drop leading / let state: tauri::State<'_, Mutex<AppState>> = ctx.app_handle().state();
let object_id = &request.uri().path()[1..]; offline!(state, fetch_object, fetch_object_offline, request, responder);
let header = generate_authorization_header();
let client: reqwest::blocking::Client = reqwest::blocking::Client::new();
let response = make_request(&client, &["/api/v1/client/object/", object_id], &[], |f| {
f.header("Authorization", header)
})
.unwrap()
.send();
if response.is_err() {
warn!(
"failed to fetch object with error: {}",
response.err().unwrap()
);
responder.respond(Response::builder().status(500).body(Vec::new()).unwrap());
return;
}
let response = response.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);
}) })
.on_window_event(|window, event| { .on_window_event(|window, event| {
if let WindowEvent::CloseRequested { api, .. } = event { if let WindowEvent::CloseRequested { api, .. } = event {

View file

@ -372,7 +372,7 @@ impl ProcessHandler for UMULauncher {
_log_file: File, _log_file: File,
_error_file: File, _error_file: File,
) -> Result<Child, Error> { ) -> Result<Child, Error> {
println!("Game override: .{:?}.", &game_version.umu_id_override); debug!("Game override: \"{:?}\"", &game_version.umu_id_override);
let game_id = match &game_version.umu_id_override { let game_id = match &game_version.umu_id_override {
Some(game_override) => game_override.is_empty().then_some(game_version.game_id.clone()).unwrap_or(game_override.clone()) , Some(game_override) => game_override.is_empty().then_some(game_version.game_id.clone()).unwrap_or(game_override.clone()) ,
None => game_version.game_id.clone() None => game_version.game_id.clone()

View file

@ -191,7 +191,7 @@ pub fn setup() -> (AppStatus, Option<User>) {
if auth.is_some() { if auth.is_some() {
let user_result = match fetch_user() { let user_result = match fetch_user() {
Ok(data) => data, Ok(data) => data,
Err(RemoteAccessError::FetchError(_)) => return (AppStatus::ServerUnavailable, None), Err(RemoteAccessError::FetchError(_)) => return (AppStatus::Offline, None),
Err(_) => return (AppStatus::SignedInNeedsReauth, None), Err(_) => return (AppStatus::SignedInNeedsReauth, None),
}; };
return (AppStatus::SignedIn, Some(user_result)); return (AppStatus::SignedIn, Some(user_result));

View file

@ -0,0 +1,63 @@
use crate::{database::db::borrow_db_checked, error::remote_access_error::RemoteAccessError};
use cacache::Integrity;
use http::{header::CONTENT_TYPE, response::Builder as ResponseBuilder, Response};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use tauri::{UriSchemeContext, UriSchemeResponder};
use super::{auth::generate_authorization_header, requests::make_request};
#[macro_export]
macro_rules! offline {
($var:expr, $func1:expr, $func2:expr, $( $arg:expr ),* ) => {
if crate::borrow_db_checked().settings.force_offline || $var.lock().unwrap().status == crate::AppStatus::Offline {
$func2( $( $arg ), *)
} else {
$func1( $( $arg ), *)
}
}
}
pub fn cache_object<'a, K: AsRef<str>, D: Serialize + DeserializeOwned>(
key: K,
data: &D,
) -> Result<Integrity, RemoteAccessError> {
let bytes = serde_json::to_vec(data).unwrap();
cacache::write_sync(&borrow_db_checked().cache_dir, key, bytes)
.map_err(|e| RemoteAccessError::Cache(e))
}
pub fn get_cached_object<'a, K: AsRef<str>, D: Serialize + DeserializeOwned>(
key: K,
) -> Result<D, RemoteAccessError> {
let bytes = cacache::read_sync(&borrow_db_checked().cache_dir, key)
.map_err(|e| RemoteAccessError::Cache(e))?;
let data = serde_json::from_slice::<D>(&bytes).unwrap();
Ok(data)
}
#[derive(Serialize, Deserialize)]
pub struct ObjectCache {
content_type: String,
body: Vec<u8>,
}
impl From<Response<Vec<u8>>> for ObjectCache {
fn from(value: Response<Vec<u8>>) -> Self {
ObjectCache {
content_type: value
.headers()
.get(CONTENT_TYPE)
.unwrap()
.to_str()
.unwrap()
.to_owned(),
body: value.body().clone(),
}
}
}
impl From<ObjectCache> for Response<Vec<u8>> {
fn from(value: ObjectCache) -> Self {
let resp_builder = ResponseBuilder::new().header(CONTENT_TYPE, value.content_type);
resp_builder.body(value.body).unwrap()
}
}

View file

@ -1,17 +1,16 @@
use std::sync::Mutex; use std::sync::Mutex;
use log::debug;
use reqwest::blocking::Client;
use tauri::{AppHandle, Emitter, Manager}; use tauri::{AppHandle, Emitter, Manager};
use url::Url; use url::Url;
use crate::{ use crate::{
database::db::{borrow_db_checked, borrow_db_mut_checked, save_db}, database::db::{borrow_db_checked, borrow_db_mut_checked, save_db}, error::remote_access_error::RemoteAccessError, remote::{auth::generate_authorization_header, requests::make_request}, AppState, AppStatus
error::remote_access_error::RemoteAccessError,
AppState, AppStatus,
}; };
use super::{ use super::{
auth::{auth_initiate_logic, recieve_handshake, setup}, auth::{auth_initiate_logic, recieve_handshake, setup}, cache::{cache_object, get_cached_object}, remote::use_remote_logic
remote::use_remote_logic,
}; };
#[tauri::command] #[tauri::command]
@ -35,6 +34,28 @@ pub fn gen_drop_url(path: String) -> Result<String, RemoteAccessError> {
Ok(url.to_string()) Ok(url.to_string())
} }
#[tauri::command]
pub fn fetch_drop_object(path: String) -> Result<Vec<u8>, RemoteAccessError> {
let drop_url = gen_drop_url(path.clone());
let req = make_request(
&Client::new(),
&[&path],
&[],
|r| { r.header("Authorization", generate_authorization_header()) }
)?.send();
match req {
Ok(data) => {
let data = data.bytes()?.to_vec();
cache_object(&path, &data)?;
Ok(data)
},
Err(e) => {
debug!("{}", e);
get_cached_object::<&str, Vec<u8>>(&path)
},
}
}
#[tauri::command] #[tauri::command]
pub fn sign_out(app: AppHandle) { pub fn sign_out(app: AppHandle) {
// Clear auth from database // Clear auth from database

View file

@ -0,0 +1,55 @@
use http::{header::CONTENT_TYPE, response::Builder as ResponseBuilder};
use log::warn;
use tauri::UriSchemeResponder;
use super::{auth::generate_authorization_header, cache::{cache_object, get_cached_object, ObjectCache}, requests::make_request};
pub fn fetch_object(
request: http::Request<Vec<u8>>,
responder: UriSchemeResponder,
) {
// Drop leading /
let object_id = &request.uri().path()[1..];
let header = generate_authorization_header();
let client: reqwest::blocking::Client = reqwest::blocking::Client::new();
let response = make_request(&client, &["/api/v1/client/object/", object_id], &[], |f| {
f.header("Authorization", header)
})
.unwrap()
.send();
if response.is_err() {
let data = get_cached_object::<&str, ObjectCache>(object_id);
match data {
Ok(data) => responder.respond(data.into()),
Err(e) => {
warn!("{}", e)
},
}
return;
}
let response = response.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();
cache_object::<&str, ObjectCache>(object_id, &resp.clone().into()).unwrap();
responder.respond(resp);
}
pub fn fetch_object_offline(
request: http::Request<Vec<u8>>,
responder: UriSchemeResponder,
) {
let object_id = &request.uri().path()[1..];
let data = get_cached_object::<&str, ObjectCache>(object_id);
match data {
Ok(data) => responder.respond(data.into()),
Err(e) => warn!("{}", e),
}
}

View file

@ -1,4 +1,7 @@
pub mod auth; pub mod auth;
#[macro_use]
pub mod cache;
pub mod commands; pub mod commands;
pub mod fetch_object;
pub mod remote; pub mod remote;
pub mod requests; pub mod requests;

View file

@ -38,6 +38,7 @@ export type Game = {
export enum AppStatus { export enum AppStatus {
NotConfigured = "NotConfigured", NotConfigured = "NotConfigured",
Offline = "Offline",
SignedOut = "SignedOut", SignedOut = "SignedOut",
SignedIn = "SignedIn", SignedIn = "SignedIn",
SignedInNeedsReauth = "SignedInNeedsReauth", SignedInNeedsReauth = "SignedInNeedsReauth",
@ -76,4 +77,5 @@ export type DownloadableMetadata = {
export type Settings = { export type Settings = {
autostart: boolean, autostart: boolean,
maxDownloadThreads: number, maxDownloadThreads: number,
forceOffline: boolean
} }