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"
:class="[
'transition uppercase font-display font-semibold text-md',
navIdx === currentPageIndex
navIdx === currentNavigation
? 'text-zinc-100'
: '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> = [
{

View file

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

View file

@ -1,28 +1,17 @@
<template>
<div class="flex flex-row h-full">
<div
class="flex-none max-h-full overflow-y-auto w-64 bg-zinc-950 px-2 py-1"
>
<div 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">
<NuxtLink
v-for="(nav, navIdx) in navigation"
:key="nav.route"
:class="[
'transition-all duration-200 rounded-lg flex items-center py-1.5 px-3',
navIdx === currentNavigationIndex
? 'bg-zinc-800 text-zinc-100'
: nav.isInstalled.value
<NuxtLink v-for="(nav, navIdx) in navigation" :key="nav.route" :class="[
'transition-all duration-200 rounded-lg flex items-center py-1.5 px-3',
navIdx === currentNavigation
? 'bg-zinc-800 text-zinc-100'
: nav.isInstalled.value
? 'text-zinc-300 hover:bg-zinc-800/90 hover:text-zinc-200'
: '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">
<img
class="size-6 flex-none object-cover bg-zinc-900 rounded"
:src="icons[navIdx]"
alt=""
/>
<img class="size-6 flex-none object-cover bg-zinc-900 rounded" :src="icons[nav.id]" alt="" />
<p class="truncate text-sm font-display leading-6 flex-1">
{{ nav.label }}
</p>
@ -31,34 +20,47 @@
</ul>
</div>
<div class="grow overflow-y-auto">
<NuxtPage :libraryDownloadError = "libraryDownloadError" />
<NuxtPage :libraryDownloadError="libraryDownloadError" />
</div>
</div>
</template>
<script setup lang="ts">
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;
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 {
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;
return new Array();
}
}
const rawGames: Array<Game> = await calculateGames();
const games = await Promise.all(rawGames.map((e) => useGame(e.id)));
const icons = await Promise.all(
games.map(({ game, status }) => useObject(game.mIconId))
);
await calculateGames();
const navigation = computed(() => rawGames.value.map((game) => {
const status = games[game.id].status;
const navigation = games.map(({ game, status }) => {
const isInstalled = computed(
() =>
status.value.type == GameStatusEnum.Installed ||
@ -70,9 +72,18 @@ const navigation = games.map(({ game, status }) => {
route: `/library/${game.id}`,
prefix: `/library/${game.id}`,
isInstalled,
id: game.id,
};
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>

View file

@ -10,13 +10,13 @@
<ul role="list" class="-mx-2 space-y-1">
<li v-for="(item, itemIdx) in navigation" :key="item.prefix">
<NuxtLink :href="item.route" :class="[
itemIdx === currentPageIndex
itemIdx === currentNavigation
? 'bg-zinc-800/50 text-zinc-100'
: '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',
]">
<component :is="item.icon" :class="[
itemIdx === currentPageIndex
itemIdx === currentNavigation
? 'text-zinc-100'
: 'text-zinc-400 group-hover:text-zinc-200',
'transition h-6 w-6 shrink-0',
@ -112,10 +112,10 @@ const navigation = computed(() => [
const currentPlatform = platform();
// 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(navigation, (newNav) => {
currentPageIndex.value = useCurrentNavigationIndex(newNav).value;
currentNavigation.value = useCurrentNavigationIndex(newNav).currentNavigation.value;
});
</script>

View file

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

View file

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

View file

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

View file

@ -255,7 +255,7 @@ impl DownloadManagerBuilder {
}
Err(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();
}
}
@ -287,7 +287,7 @@ impl DownloadManagerBuilder {
fn manage_error_signal(&mut self, error: ApplicationDownloadError) {
debug!("got signal Error");
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.remove_and_cleanup_front_download(&current_agent.metadata());

View file

@ -16,7 +16,7 @@ pub trait Downloadable: Send + Sync {
fn status(&self) -> DownloadStatus;
fn metadata(&self) -> DownloadableMetadata;
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_incomplete(&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};
// TODO: Rename / separate from downloads
#[derive(Debug, Clone, SerializeDisplay)]
#[derive(Debug, SerializeDisplay)]
pub enum ApplicationDownloadError {
Communication(RemoteAccessError),
Checksum,

View file

@ -10,7 +10,7 @@ use url::ParseError;
use super::drop_server_error::DropServerError;
#[derive(Debug, Clone, SerializeDisplay)]
#[derive(Debug, SerializeDisplay)]
pub enum RemoteAccessError {
FetchError(Arc<reqwest::Error>),
ParsingError(ParseError),
@ -21,6 +21,7 @@ pub enum RemoteAccessError {
InvalidRedirect,
ManifestDownloadFailed(StatusCode, String),
OutOfSync,
Cache(cacache::Error),
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::Generic(message) => write!(f, "{}", message),
RemoteAccessError::Cache(error) => write!(f, "Cache Error: {}", error),
}
}
}

View file

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

View file

@ -135,7 +135,7 @@ impl GameDownloadAgent {
&[("id", &self.id), ("version", &self.version)],
|f| f.header("Authorization", header),
)
.map_err(|e| ApplicationDownloadError::Communication(e))?
.map_err(ApplicationDownloadError::Communication)?
.send()
.map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
@ -280,9 +280,13 @@ impl GameDownloadAgent {
) {
Ok(request) => request,
Err(e) => {
sender.send(DownloadManagerSignal::Error(ApplicationDownloadError::Communication(e))).unwrap();
sender
.send(DownloadManagerSignal::Error(
ApplicationDownloadError::Communication(e),
))
.unwrap();
continue;
},
}
};
scope.spawn(move |_| {
@ -362,7 +366,7 @@ impl Downloadable for GameDownloadAgent {
*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;
app_handle
.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::games::state::{GameStatusManager, GameStatusWithTransient};
use crate::remote::auth::generate_authorization_header;
use crate::remote::cache::{cache_object, get_cached_object};
use crate::remote::requests::make_request;
use crate::AppState;
#[derive(serde::Serialize)]
#[derive(Serialize, Deserialize)]
pub struct FetchGameStruct {
game: Game,
status: GameStatusWithTransient,
}
#[derive(Serialize, Deserialize, Clone)]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Game {
id: String,
@ -66,7 +67,9 @@ pub struct StatsUpdateEvent {
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 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 state = app.state::<Mutex<AppState>>();
let mut handle = state.lock().unwrap();
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 {});
}
}
drop(handle);
drop(db_handle);
cache_object("library", &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(
id: String,
app: tauri::AppHandle,
state: tauri::State<'_, Mutex<AppState>>,
) -> Result<FetchGameStruct, RemoteAccessError> {
let state = app.state::<Mutex<AppState>>();
let mut state_handle = state.lock().unwrap();
let game = state_handle.games.get(&id);
@ -119,6 +136,8 @@ pub fn fetch_game_logic(
status,
};
cache_object(id, &data)?;
return Ok(data);
}
let client = reqwest::blocking::Client::new();
@ -155,9 +174,18 @@ pub fn fetch_game_logic(
status,
};
cache_object(id, &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(
game_id: String,
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) {
println!("triggered uninstall for agent");
debug!("triggered uninstall for agent");
let mut db_handle = borrow_db_mut_checked();
db_handle
.applications
@ -213,6 +241,7 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
return;
}
let previous_state = previous_state.unwrap();
if let Some((_, install_dir)) = match previous_state {
GameDownloadStatus::Installed {
version_name,
@ -229,6 +258,7 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
.transient_statuses
.entry(meta.clone())
.and_modify(|v| *v = ApplicationTransientStatus::Uninstalling {});
drop(db_handle);
let app_handle = app_handle.clone();
@ -239,6 +269,10 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
Ok(_) => {
let mut db_handle = borrow_db_mut_checked();
db_handle.applications.transient_statuses.remove(&meta);
db_handle
.applications
.installed_game_version
.remove(&meta.id);
db_handle
.applications
.game_statuses
@ -248,6 +282,7 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle)
save_db();
debug!("uninstalled game id {}", &meta.id);
app_handle.emit("update_library", {}).unwrap();
push_game_update(
&app_handle,

View file

@ -40,11 +40,13 @@ use log4rs::encode::pattern::PatternEncoder;
use log4rs::Config;
use process::commands::{kill_game, launch_game};
use process::process_manager::ProcessManager;
use remote::auth::{self, generate_authorization_header, recieve_handshake};
use remote::auth::{self, recieve_handshake};
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 reqwest::blocking::Body;
use serde::{Deserialize, Serialize};
use std::env;
use std::path::Path;
@ -60,9 +62,10 @@ use tauri::{AppHandle, Manager, RunEvent, WindowEvent};
use tauri_plugin_deep_link::DeepLinkExt;
use tauri_plugin_dialog::DialogExt;
#[derive(Clone, Copy, Serialize)]
#[derive(Clone, Copy, Serialize, Eq, PartialEq)]
pub enum AppStatus {
NotConfigured,
Offline,
ServerError,
SignedOut,
SignedIn,
@ -236,6 +239,7 @@ pub fn run() {
// Remote
use_remote,
gen_drop_url,
fetch_drop_object,
// Library
fetch_library,
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)
@ -356,35 +360,9 @@ pub fn run() {
Ok(())
})
.register_asynchronous_uri_scheme_protocol("object", move |_ctx, request, responder| {
// 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() {
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);
.register_asynchronous_uri_scheme_protocol("object", move |ctx, request, responder| {
let state: tauri::State<'_, Mutex<AppState>> = ctx.app_handle().state();
offline!(state, fetch_object, fetch_object_offline, request, responder);
})
.on_window_event(|window, event| {
if let WindowEvent::CloseRequested { api, .. } = event {

View file

@ -372,7 +372,7 @@ impl ProcessHandler for UMULauncher {
_log_file: File,
_error_file: File,
) -> 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 {
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()

View file

@ -191,7 +191,7 @@ pub fn setup() -> (AppStatus, Option<User>) {
if auth.is_some() {
let user_result = match fetch_user() {
Ok(data) => data,
Err(RemoteAccessError::FetchError(_)) => return (AppStatus::ServerUnavailable, None),
Err(RemoteAccessError::FetchError(_)) => return (AppStatus::Offline, None),
Err(_) => return (AppStatus::SignedInNeedsReauth, None),
};
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 log::debug;
use reqwest::blocking::Client;
use tauri::{AppHandle, Emitter, Manager};
use url::Url;
use crate::{
database::db::{borrow_db_checked, borrow_db_mut_checked, save_db},
error::remote_access_error::RemoteAccessError,
AppState, AppStatus,
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
};
use super::{
auth::{auth_initiate_logic, recieve_handshake, setup},
remote::use_remote_logic,
auth::{auth_initiate_logic, recieve_handshake, setup}, cache::{cache_object, get_cached_object}, remote::use_remote_logic
};
#[tauri::command]
@ -35,6 +34,28 @@ pub fn gen_drop_url(path: String) -> Result<String, RemoteAccessError> {
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]
pub fn sign_out(app: AppHandle) {
// 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;
#[macro_use]
pub mod cache;
pub mod commands;
pub mod fetch_object;
pub mod remote;
pub mod requests;

View file

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