feat(db): browser package update cache and per-profile update mode

This commit is contained in:
LynithDev 2026-08-01 12:06:22 +02:00
parent 207abaf64b
commit 1bd0b68e91
37 changed files with 1736 additions and 23 deletions

View file

@ -0,0 +1,26 @@
{
"db_name": "SQLite",
"query": "SELECT id FROM applied_migrations WHERE id = ?",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text",
"origin": {
"Table": {
"table": "applied_migrations",
"name": "id"
}
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "07450861d48b3bff106b7c06e958665f6be44e7937deffb921ddb6a5450805ab"
}

View file

@ -0,0 +1,158 @@
{
"db_name": "SQLite",
"query": "\n\t\tUPDATE clusters\n\t\tSET mc_version = ?,\n\t\t name = ?,\n\t\t folder_name = ?\n\t\tWHERE id = ?\n\t\tRETURNING\n\t\t\tid, name, folder_name, setting_profile_name, mc_version, mc_loader,\n\t\t\tstage, mc_loader_version, created_at, last_played, overall_played, linked_modpack_hash\n\t\t",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "id"
}
}
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "name"
}
}
},
{
"name": "folder_name",
"ordinal": 2,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "folder_name"
}
}
},
{
"name": "setting_profile_name",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "setting_profile_name"
}
}
},
{
"name": "mc_version",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_version"
}
}
},
{
"name": "mc_loader",
"ordinal": 5,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader"
}
}
},
{
"name": "stage",
"ordinal": 6,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "stage"
}
}
},
{
"name": "mc_loader_version",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "mc_loader_version"
}
}
},
{
"name": "created_at",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "created_at"
}
}
},
{
"name": "last_played",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "last_played"
}
}
},
{
"name": "overall_played",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "clusters",
"name": "overall_played"
}
}
},
{
"name": "linked_modpack_hash",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "clusters",
"name": "linked_modpack_hash"
}
}
}
],
"parameters": {
"Right": 4
},
"nullable": [
false,
false,
false,
true,
false,
false,
false,
true,
true,
true,
true,
true
]
},
"hash": "1a83654d854901219ca4be57d793ddef96835a67aa8affe3453845a646e5ba61"
}

View file

@ -0,0 +1,146 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT\n\t\t\tcluster_id, hash, provider as \"provider!: i64\", project_id,\n\t\t\tinstalled_version_id, installed_version_name,\n\t\t\tlatest_version_id, latest_version_name,\n\t\t\tdisplay_name, checked_at, skipped\n\t\tFROM browser_package_updates\n\t\t",
"describe": {
"columns": [
{
"name": "cluster_id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "cluster_id"
}
}
},
{
"name": "hash",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "hash"
}
}
},
{
"name": "provider!: i64",
"ordinal": 2,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "provider"
}
}
},
{
"name": "project_id",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "project_id"
}
}
},
{
"name": "installed_version_id",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "installed_version_id"
}
}
},
{
"name": "installed_version_name",
"ordinal": 5,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "installed_version_name"
}
}
},
{
"name": "latest_version_id",
"ordinal": 6,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "latest_version_id"
}
}
},
{
"name": "latest_version_name",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "latest_version_name"
}
}
},
{
"name": "display_name",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "display_name"
}
}
},
{
"name": "checked_at",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "checked_at"
}
}
},
{
"name": "skipped",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "skipped"
}
}
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "2005ac6f15d4c5fe36d5bfa87191e6be22264a2ca3e91155cb01f2debd39b319"
}

View file

@ -0,0 +1,146 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT\n\t\t\tcluster_id, hash, provider as \"provider!: i64\", project_id,\n\t\t\tinstalled_version_id, installed_version_name,\n\t\t\tlatest_version_id, latest_version_name,\n\t\t\tdisplay_name, checked_at, skipped\n\t\tFROM browser_package_updates\n\t\tWHERE cluster_id = ?\n\t\t",
"describe": {
"columns": [
{
"name": "cluster_id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "cluster_id"
}
}
},
{
"name": "hash",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "hash"
}
}
},
{
"name": "provider!: i64",
"ordinal": 2,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "provider"
}
}
},
{
"name": "project_id",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "project_id"
}
}
},
{
"name": "installed_version_id",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "installed_version_id"
}
}
},
{
"name": "installed_version_name",
"ordinal": 5,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "installed_version_name"
}
}
},
{
"name": "latest_version_id",
"ordinal": 6,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "latest_version_id"
}
}
},
{
"name": "latest_version_name",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "latest_version_name"
}
}
},
{
"name": "display_name",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "display_name"
}
}
},
{
"name": "checked_at",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "checked_at"
}
}
},
{
"name": "skipped",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "skipped"
}
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "271e5a3e03861f3699e3b32dc4a9bb0c0dcb880fcff67a096fa94b236fbe6107"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n\t\tUPDATE browser_package_updates\n\t\tSET skipped = ?\n\t\tWHERE cluster_id = ? AND hash = ?\n\t\t",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "472a614b1f1484e839f373fcfecf338462888518de3307bdab3db0b419a82be9"
}

View file

@ -0,0 +1,146 @@
{
"db_name": "SQLite",
"query": "\n\t\tINSERT INTO browser_package_updates (\n\t\t\tcluster_id, hash, provider, project_id,\n\t\t\tinstalled_version_id, installed_version_name,\n\t\t\tlatest_version_id, latest_version_name,\n\t\t\tdisplay_name, checked_at, skipped\n\t\t)\n\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)\n\t\tON CONFLICT(cluster_id, hash) DO UPDATE SET\n\t\t\tprovider = excluded.provider,\n\t\t\tproject_id = excluded.project_id,\n\t\t\tinstalled_version_id = excluded.installed_version_id,\n\t\t\tinstalled_version_name = excluded.installed_version_name,\n\t\t\tlatest_version_name = excluded.latest_version_name,\n\t\t\tdisplay_name = excluded.display_name,\n\t\t\tchecked_at = excluded.checked_at,\n\t\t\tskipped = CASE\n\t\t\t\tWHEN browser_package_updates.latest_version_id = excluded.latest_version_id\n\t\t\t\tTHEN browser_package_updates.skipped\n\t\t\t\tELSE 0\n\t\t\tEND,\n\t\t\tlatest_version_id = excluded.latest_version_id\n\t\tRETURNING\n\t\t\tcluster_id, hash, provider as \"provider!: i64\", project_id,\n\t\t\tinstalled_version_id, installed_version_name,\n\t\t\tlatest_version_id, latest_version_name,\n\t\t\tdisplay_name, checked_at, skipped\n\t\t",
"describe": {
"columns": [
{
"name": "cluster_id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "cluster_id"
}
}
},
{
"name": "hash",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "hash"
}
}
},
{
"name": "provider!: i64",
"ordinal": 2,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "provider"
}
}
},
{
"name": "project_id",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "project_id"
}
}
},
{
"name": "installed_version_id",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "installed_version_id"
}
}
},
{
"name": "installed_version_name",
"ordinal": 5,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "installed_version_name"
}
}
},
{
"name": "latest_version_id",
"ordinal": 6,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "latest_version_id"
}
}
},
{
"name": "latest_version_name",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "latest_version_name"
}
}
},
{
"name": "display_name",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "display_name"
}
}
},
{
"name": "checked_at",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "checked_at"
}
}
},
{
"name": "skipped",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "skipped"
}
}
}
],
"parameters": {
"Right": 10
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "5183d452802d8bbec7f2a52ceba7ffe26960fdc25855f6cc993d6ab5deb84c02"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n\t\tUPDATE game_sessions\n\t\tSET pid = ?, pid_started_at = ?\n\t\tWHERE started_at = ?\n\t\t",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "71fbe674b85a1fd6dc07a062e96a35888bb3b6a5a4fda5df3c662ca16b1d801e"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT OR IGNORE INTO applied_migrations (id, applied_at) VALUES (?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "836429d5d4054d1605d7de4cbcf3703ba78d72f39b3090734bb3e69c2b72d50c"
}

View file

@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n\t\t hook_pre, hook_wrapper, hook_post, os_extra\n\t\tFROM setting_profiles\n\t\tWHERE name = ?\n\t\t",
"query": "\n\t\tSELECT name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n\t\t hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode\n\t\tFROM setting_profiles\n\t\tWHERE name = ?\n\t\t",
"describe": {
"columns": [
{
@ -123,6 +123,17 @@
"name": "os_extra"
}
}
},
{
"name": "browser_update_mode",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "browser_update_mode"
}
}
}
],
"parameters": {
@ -139,8 +150,9 @@
true,
true,
true,
true,
true
]
},
"hash": "de1b40c927c9819f675b8e3e8e1f583c41450ae9deda20739d82d95e8ca70b27"
"hash": "98500a40f7e1ca7df79dd0ca90eaaa4f0b079ccbd757e5db1a8229ea58d2e700"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n\t\tDELETE FROM game_session_servers\n\t\tWHERE session_started_at = ?\n\t\t",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "b42a04c7b073b6cceff8fbd48a7b55f9beddb045bc43eaef851da89b3e547933"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "DELETE FROM browser_package_updates WHERE cluster_id = ? AND hash = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "c3f6c0105273939636dd0a7c7895a90e1e0902bde48eea1ced6d89ea6dbb4555"
}

View file

@ -0,0 +1,62 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT cluster_id, started_at, pid, pid_started_at\n\t\tFROM game_sessions\n\t\tWHERE ended_at IS NULL\n\t\tORDER BY started_at ASC\n\t\t",
"describe": {
"columns": [
{
"name": "cluster_id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "game_sessions",
"name": "cluster_id"
}
}
},
{
"name": "started_at",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "game_sessions",
"name": "started_at"
}
}
},
{
"name": "pid",
"ordinal": 2,
"type_info": "Integer",
"origin": {
"Table": {
"table": "game_sessions",
"name": "pid"
}
}
},
{
"name": "pid_started_at",
"ordinal": 3,
"type_info": "Integer",
"origin": {
"Table": {
"table": "game_sessions",
"name": "pid_started_at"
}
}
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
true,
true
]
},
"hash": "c49ab599ef0aa31813f2a84d86f79235e2470eeec5b10f1e768fcf54d37d57fe"
}

View file

@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n\t\tINSERT INTO setting_profiles (\n\t\t\tname, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n\t\t\thook_pre, hook_wrapper, hook_post, os_extra\n\t\t)\n\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n\t\tON CONFLICT(name) DO UPDATE SET\n\t\t\tjava_path = excluded.java_path,\n\t\t\tresolution = excluded.resolution,\n\t\t\tforce_fullscreen = excluded.force_fullscreen,\n\t\t\tmem_max = excluded.mem_max,\n\t\t\tlaunch_args = excluded.launch_args,\n\t\t\tlaunch_env = excluded.launch_env,\n\t\t\thook_pre = excluded.hook_pre,\n\t\t\thook_wrapper = excluded.hook_wrapper,\n\t\t\thook_post = excluded.hook_post,\n\t\t\tos_extra = excluded.os_extra\n\t\tRETURNING name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n hook_pre, hook_wrapper, hook_post, os_extra\n\t\t",
"query": "\n\t\tINSERT INTO setting_profiles (\n\t\t\tname, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n\t\t\thook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode\n\t\t)\n\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n\t\tON CONFLICT(name) DO UPDATE SET\n\t\t\tjava_path = excluded.java_path,\n\t\t\tresolution = excluded.resolution,\n\t\t\tforce_fullscreen = excluded.force_fullscreen,\n\t\t\tmem_max = excluded.mem_max,\n\t\t\tlaunch_args = excluded.launch_args,\n\t\t\tlaunch_env = excluded.launch_env,\n\t\t\thook_pre = excluded.hook_pre,\n\t\t\thook_wrapper = excluded.hook_wrapper,\n\t\t\thook_post = excluded.hook_post,\n\t\t\tos_extra = excluded.os_extra,\n\t\t\tbrowser_update_mode = excluded.browser_update_mode\n\t\tRETURNING name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode\n\t\t",
"describe": {
"columns": [
{
@ -123,10 +123,21 @@
"name": "os_extra"
}
}
},
{
"name": "browser_update_mode",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "browser_update_mode"
}
}
}
],
"parameters": {
"Right": 11
"Right": 12
},
"nullable": [
false,
@ -139,8 +150,9 @@
true,
true,
true,
true,
true
]
},
"hash": "b41126ba17cbc3005b446b23cebc29ffe6fc366294191e546808e9f591b723a9"
"hash": "c939cef4b51d506d5b73b4c2e0330ba45423c0116c911b9da16576290ed9f3c2"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "DELETE FROM browser_package_updates WHERE cluster_id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "f050a2d799f838b8fffca63c8ca0c758fc8787e982a2587b0a6f3c91abdcb3ec"
}

View file

@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n\t\t hook_pre, hook_wrapper, hook_post, os_extra\n\t\tFROM setting_profiles\n\t\tORDER BY name ASC\n\t\t",
"query": "\n\t\tSELECT name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n\t\t hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode\n\t\tFROM setting_profiles\n\t\tORDER BY name ASC\n\t\t",
"describe": {
"columns": [
{
@ -123,6 +123,17 @@
"name": "os_extra"
}
}
},
{
"name": "browser_update_mode",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "browser_update_mode"
}
}
}
],
"parameters": {
@ -139,8 +150,9 @@
true,
true,
true,
true,
true
]
},
"hash": "4ab22fa481720e3bae5befee821fe1c0689d666f8bbcde98890e2d4715e9bc80"
"hash": "f68b9ade92feb989828ca81582c5960bf2968c4b75705e1a0e6800e97c3f4628"
}

View file

@ -25,6 +25,6 @@ pub use cluster::{Cluster, ClusterLinkTarget};
pub use error::{ClusterError, ClusterResult};
pub use manager::ClusterManager;
pub use options::{ClusterUpdate, CreateClusterOptions};
pub use profile::{GameSettingsProfile, SettingsOsExtra};
pub use profile::{GameSettingsProfile, PackageUpdateMode, SettingsOsExtra};
pub use profiles::ProfileUpdate;
pub use stage::ClusterStage;

View file

@ -17,11 +17,15 @@ pub struct GameSettingsProfile {
pub hook_wrapper: Option<String>,
pub hook_post: Option<String>,
pub os_extra: Option<SettingsOsExtra>,
/// What to do when a package installed from the browser has a newer
/// version. Bundle content is never touched by this.
pub browser_update_mode: Option<PackageUpdateMode>,
}
// `Resolution` moved to oneclient_common: the launch-argument builder needs it
// too, and that crate cannot depend on the launcher settings.
pub use oneclient_common::Resolution;
pub use oneclient_common::domain::PackageUpdateMode;
cfg_select! {
target_os = "windows" => {
@ -62,6 +66,7 @@ impl GameSettingsProfile {
hook_wrapper: None,
hook_post: None,
os_extra: Some(SettingsOsExtra::default()),
browser_update_mode: Some(PackageUpdateMode::default()),
}
}
@ -100,6 +105,9 @@ impl GameSettingsProfile {
if self.os_extra.is_none() {
self.os_extra = global.os_extra.clone();
}
if self.browser_update_mode.is_none() {
self.browser_update_mode = global.browser_update_mode;
}
}
pub fn from_row(row: SettingProfileRow) -> crate::ClusterResult<Self> {
@ -121,6 +129,12 @@ impl GameSettingsProfile {
.os_extra
.map(|json| serde_json::from_str(&json))
.transpose()?,
// An unrecognised value inherits rather than errors: a profile
// written by a newer build must not make the cluster unloadable.
browser_update_mode: row
.browser_update_mode
.as_deref()
.and_then(PackageUpdateMode::parse),
})
}
@ -144,6 +158,9 @@ impl GameSettingsProfile {
.as_ref()
.map(serde_json::to_string)
.transpose()?,
browser_update_mode: self
.browser_update_mode
.map(|mode| mode.as_str().to_string()),
})
}
}

View file

@ -15,6 +15,7 @@ use oneclient_db::DbPool;
use crate::error::{ClusterError, ClusterResult};
use crate::profile::{GameSettingsProfile, SettingsOsExtra};
use oneclient_common::Resolution;
use oneclient_common::domain::PackageUpdateMode;
#[tracing::instrument(level = "debug", skip(pool, global))]
pub async fn get_profile_or_default(
@ -132,6 +133,7 @@ pub struct ProfileUpdate {
pub hook_wrapper: Patch<String>,
pub hook_post: Patch<String>,
pub os_extra: Patch<SettingsOsExtra>,
pub browser_update_mode: Patch<PackageUpdateMode>,
}
impl ProfileUpdate {
@ -151,6 +153,8 @@ impl ProfileUpdate {
self.hook_post
.apply_to_command_option(&mut profile.hook_post);
self.os_extra.apply_to_option(&mut profile.os_extra);
self.browser_update_mode
.apply_to_option(&mut profile.browser_update_mode);
}
}

View file

@ -125,6 +125,73 @@ pub enum HashAlgorithm {
Sha1,
}
/// What the launcher does when a package the user installed from the browser
/// has a newer version at launch.
///
/// Bundle-provided content is not covered by this: bundles carry their own
/// update flow, and the two must not fight over the same file.
///
/// The check itself always runs; this only decides what happens with the
/// result, so the "out of date" markers in the package manager are populated
/// no matter which variant is selected.
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
Default,
EnumIter,
StrumDisplay,
)]
#[serde(rename_all = "snake_case")]
pub enum PackageUpdateMode {
/// Apply every update as soon as it is found, with no modal.
Automatic,
/// Never apply and never ask.
Skip,
/// Ask, listing each package so the user decides one at a time.
#[default]
Prompt,
}
impl PackageUpdateMode {
/// Display order, defaulting variant first.
pub const ALL: &[Self] = &[Self::Prompt, Self::Automatic, Self::Skip];
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Automatic => "Automatically update",
Self::Skip => "Always skip auto updates",
Self::Prompt => "Show the modal",
}
}
/// The value stored in the `setting_profiles.browser_update_mode` column.
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Automatic => "automatic",
Self::Skip => "skip",
Self::Prompt => "prompt",
}
}
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
match value {
"automatic" => Some(Self::Automatic),
"skip" => Some(Self::Skip),
"prompt" => Some(Self::Prompt),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default, FromRepr)]
#[repr(u8)]
pub enum GameLoader {

View file

@ -15,7 +15,9 @@ pub mod version;
mod error;
pub use domain::{ContentType, GameLoader, HashAlgorithm, ProviderId, Resolution};
pub use domain::{
ContentType, GameLoader, HashAlgorithm, PackageUpdateMode, ProviderId, Resolution,
};
pub use error::{PathsError, PathsResult};
pub use os_ext::OsExt;
pub use patch::Patch;

View file

@ -2,7 +2,9 @@ mod launcher;
pub use launcher::{LauncherSettings, ViewLayout, ViewState};
// Profiles moved to `oneclient_cluster`; re-exported for existing paths.
pub use oneclient_cluster::{GameSettingsProfile, ProfileUpdate, SettingsOsExtra};
pub use oneclient_cluster::{
GameSettingsProfile, PackageUpdateMode, ProfileUpdate, SettingsOsExtra,
};
pub use oneclient_common::Resolution;
pub mod store;

View file

@ -0,0 +1,146 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT\n\t\t\tcluster_id, hash, provider as \"provider!: i64\", project_id,\n\t\t\tinstalled_version_id, installed_version_name,\n\t\t\tlatest_version_id, latest_version_name,\n\t\t\tdisplay_name, checked_at, skipped\n\t\tFROM browser_package_updates\n\t\t",
"describe": {
"columns": [
{
"name": "cluster_id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "cluster_id"
}
}
},
{
"name": "hash",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "hash"
}
}
},
{
"name": "provider!: i64",
"ordinal": 2,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "provider"
}
}
},
{
"name": "project_id",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "project_id"
}
}
},
{
"name": "installed_version_id",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "installed_version_id"
}
}
},
{
"name": "installed_version_name",
"ordinal": 5,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "installed_version_name"
}
}
},
{
"name": "latest_version_id",
"ordinal": 6,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "latest_version_id"
}
}
},
{
"name": "latest_version_name",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "latest_version_name"
}
}
},
{
"name": "display_name",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "display_name"
}
}
},
{
"name": "checked_at",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "checked_at"
}
}
},
{
"name": "skipped",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "skipped"
}
}
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "2005ac6f15d4c5fe36d5bfa87191e6be22264a2ca3e91155cb01f2debd39b319"
}

View file

@ -0,0 +1,146 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT\n\t\t\tcluster_id, hash, provider as \"provider!: i64\", project_id,\n\t\t\tinstalled_version_id, installed_version_name,\n\t\t\tlatest_version_id, latest_version_name,\n\t\t\tdisplay_name, checked_at, skipped\n\t\tFROM browser_package_updates\n\t\tWHERE cluster_id = ?\n\t\t",
"describe": {
"columns": [
{
"name": "cluster_id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "cluster_id"
}
}
},
{
"name": "hash",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "hash"
}
}
},
{
"name": "provider!: i64",
"ordinal": 2,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "provider"
}
}
},
{
"name": "project_id",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "project_id"
}
}
},
{
"name": "installed_version_id",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "installed_version_id"
}
}
},
{
"name": "installed_version_name",
"ordinal": 5,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "installed_version_name"
}
}
},
{
"name": "latest_version_id",
"ordinal": 6,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "latest_version_id"
}
}
},
{
"name": "latest_version_name",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "latest_version_name"
}
}
},
{
"name": "display_name",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "display_name"
}
}
},
{
"name": "checked_at",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "checked_at"
}
}
},
{
"name": "skipped",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "skipped"
}
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "271e5a3e03861f3699e3b32dc4a9bb0c0dcb880fcff67a096fa94b236fbe6107"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n\t\tUPDATE browser_package_updates\n\t\tSET skipped = ?\n\t\tWHERE cluster_id = ? AND hash = ?\n\t\t",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "472a614b1f1484e839f373fcfecf338462888518de3307bdab3db0b419a82be9"
}

View file

@ -0,0 +1,146 @@
{
"db_name": "SQLite",
"query": "\n\t\tINSERT INTO browser_package_updates (\n\t\t\tcluster_id, hash, provider, project_id,\n\t\t\tinstalled_version_id, installed_version_name,\n\t\t\tlatest_version_id, latest_version_name,\n\t\t\tdisplay_name, checked_at, skipped\n\t\t)\n\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)\n\t\tON CONFLICT(cluster_id, hash) DO UPDATE SET\n\t\t\tprovider = excluded.provider,\n\t\t\tproject_id = excluded.project_id,\n\t\t\tinstalled_version_id = excluded.installed_version_id,\n\t\t\tinstalled_version_name = excluded.installed_version_name,\n\t\t\tlatest_version_name = excluded.latest_version_name,\n\t\t\tdisplay_name = excluded.display_name,\n\t\t\tchecked_at = excluded.checked_at,\n\t\t\tskipped = CASE\n\t\t\t\tWHEN browser_package_updates.latest_version_id = excluded.latest_version_id\n\t\t\t\tTHEN browser_package_updates.skipped\n\t\t\t\tELSE 0\n\t\t\tEND,\n\t\t\tlatest_version_id = excluded.latest_version_id\n\t\tRETURNING\n\t\t\tcluster_id, hash, provider as \"provider!: i64\", project_id,\n\t\t\tinstalled_version_id, installed_version_name,\n\t\t\tlatest_version_id, latest_version_name,\n\t\t\tdisplay_name, checked_at, skipped\n\t\t",
"describe": {
"columns": [
{
"name": "cluster_id",
"ordinal": 0,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "cluster_id"
}
}
},
{
"name": "hash",
"ordinal": 1,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "hash"
}
}
},
{
"name": "provider!: i64",
"ordinal": 2,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "provider"
}
}
},
{
"name": "project_id",
"ordinal": 3,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "project_id"
}
}
},
{
"name": "installed_version_id",
"ordinal": 4,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "installed_version_id"
}
}
},
{
"name": "installed_version_name",
"ordinal": 5,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "installed_version_name"
}
}
},
{
"name": "latest_version_id",
"ordinal": 6,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "latest_version_id"
}
}
},
{
"name": "latest_version_name",
"ordinal": 7,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "latest_version_name"
}
}
},
{
"name": "display_name",
"ordinal": 8,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "display_name"
}
}
},
{
"name": "checked_at",
"ordinal": 9,
"type_info": "Text",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "checked_at"
}
}
},
{
"name": "skipped",
"ordinal": 10,
"type_info": "Integer",
"origin": {
"Table": {
"table": "browser_package_updates",
"name": "skipped"
}
}
}
],
"parameters": {
"Right": 10
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "5183d452802d8bbec7f2a52ceba7ffe26960fdc25855f6cc993d6ab5deb84c02"
}

View file

@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n\t\t hook_pre, hook_wrapper, hook_post, os_extra\n\t\tFROM setting_profiles\n\t\tWHERE name = ?\n\t\t",
"query": "\n\t\tSELECT name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n\t\t hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode\n\t\tFROM setting_profiles\n\t\tWHERE name = ?\n\t\t",
"describe": {
"columns": [
{
@ -123,6 +123,17 @@
"name": "os_extra"
}
}
},
{
"name": "browser_update_mode",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "browser_update_mode"
}
}
}
],
"parameters": {
@ -139,8 +150,9 @@
true,
true,
true,
true,
true
]
},
"hash": "de1b40c927c9819f675b8e3e8e1f583c41450ae9deda20739d82d95e8ca70b27"
"hash": "98500a40f7e1ca7df79dd0ca90eaaa4f0b079ccbd757e5db1a8229ea58d2e700"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "DELETE FROM browser_package_updates WHERE cluster_id = ? AND hash = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "c3f6c0105273939636dd0a7c7895a90e1e0902bde48eea1ced6d89ea6dbb4555"
}

View file

@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n\t\tINSERT INTO setting_profiles (\n\t\t\tname, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n\t\t\thook_pre, hook_wrapper, hook_post, os_extra\n\t\t)\n\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n\t\tON CONFLICT(name) DO UPDATE SET\n\t\t\tjava_path = excluded.java_path,\n\t\t\tresolution = excluded.resolution,\n\t\t\tforce_fullscreen = excluded.force_fullscreen,\n\t\t\tmem_max = excluded.mem_max,\n\t\t\tlaunch_args = excluded.launch_args,\n\t\t\tlaunch_env = excluded.launch_env,\n\t\t\thook_pre = excluded.hook_pre,\n\t\t\thook_wrapper = excluded.hook_wrapper,\n\t\t\thook_post = excluded.hook_post,\n\t\t\tos_extra = excluded.os_extra\n\t\tRETURNING name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n hook_pre, hook_wrapper, hook_post, os_extra\n\t\t",
"query": "\n\t\tINSERT INTO setting_profiles (\n\t\t\tname, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n\t\t\thook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode\n\t\t)\n\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n\t\tON CONFLICT(name) DO UPDATE SET\n\t\t\tjava_path = excluded.java_path,\n\t\t\tresolution = excluded.resolution,\n\t\t\tforce_fullscreen = excluded.force_fullscreen,\n\t\t\tmem_max = excluded.mem_max,\n\t\t\tlaunch_args = excluded.launch_args,\n\t\t\tlaunch_env = excluded.launch_env,\n\t\t\thook_pre = excluded.hook_pre,\n\t\t\thook_wrapper = excluded.hook_wrapper,\n\t\t\thook_post = excluded.hook_post,\n\t\t\tos_extra = excluded.os_extra,\n\t\t\tbrowser_update_mode = excluded.browser_update_mode\n\t\tRETURNING name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode\n\t\t",
"describe": {
"columns": [
{
@ -123,10 +123,21 @@
"name": "os_extra"
}
}
},
{
"name": "browser_update_mode",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "browser_update_mode"
}
}
}
],
"parameters": {
"Right": 11
"Right": 12
},
"nullable": [
false,
@ -139,8 +150,9 @@
true,
true,
true,
true,
true
]
},
"hash": "b41126ba17cbc3005b446b23cebc29ffe6fc366294191e546808e9f591b723a9"
"hash": "c939cef4b51d506d5b73b4c2e0330ba45423c0116c911b9da16576290ed9f3c2"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "DELETE FROM browser_package_updates WHERE cluster_id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "f050a2d799f838b8fffca63c8ca0c758fc8787e982a2587b0a6f3c91abdcb3ec"
}

View file

@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n\t\tSELECT name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n\t\t hook_pre, hook_wrapper, hook_post, os_extra\n\t\tFROM setting_profiles\n\t\tORDER BY name ASC\n\t\t",
"query": "\n\t\tSELECT name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,\n\t\t hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode\n\t\tFROM setting_profiles\n\t\tORDER BY name ASC\n\t\t",
"describe": {
"columns": [
{
@ -123,6 +123,17 @@
"name": "os_extra"
}
}
},
{
"name": "browser_update_mode",
"ordinal": 11,
"type_info": "Text",
"origin": {
"Table": {
"table": "setting_profiles",
"name": "browser_update_mode"
}
}
}
],
"parameters": {
@ -139,8 +150,9 @@
true,
true,
true,
true,
true
]
},
"hash": "4ab22fa481720e3bae5befee821fe1c0689d666f8bbcde98890e2d4715e9bc80"
"hash": "f68b9ade92feb989828ca81582c5960bf2968c4b75705e1a0e6800e97c3f4628"
}

View file

@ -0,0 +1,37 @@
-- Auto-updates for packages the user installed from the browser, i.e. remote
-- provider content that no bundle provides. Bundles keep their own update flow
-- and are deliberately out of scope here.
--
-- `browser_update_mode` is the per-profile override of the launcher-wide
-- setting; NULL means "inherit the global value", like every other column on
-- this table. Values are the strings in `PackageUpdateMode::as_str`.
ALTER TABLE setting_profiles ADD COLUMN browser_update_mode TEXT;
-- Cache of the launch-time update check, so the package manager can mark a
-- package out of date without going back to the provider on every render, and
-- still knows about it after a restart.
--
-- Keyed by the artifact hash actually installed in the cluster: that is what
-- the package rows are keyed by, and it changes when the update is applied,
-- which is what makes a stale row impossible to mistake for a live one.
--
-- `skipped` records that the user was asked about *this* newer version and said
-- no. The row stays either way, because a skipped package is still out of date
-- and should still say so in the package manager; it just stops re-opening the
-- modal for the same version.
CREATE TABLE browser_package_updates (
cluster_id INTEGER NOT NULL,
hash TEXT NOT NULL,
provider INTEGER NOT NULL,
project_id TEXT NOT NULL,
installed_version_id TEXT NOT NULL,
installed_version_name TEXT NOT NULL DEFAULT '',
latest_version_id TEXT NOT NULL,
latest_version_name TEXT NOT NULL DEFAULT '',
display_name TEXT NOT NULL DEFAULT '',
checked_at TEXT NOT NULL,
skipped INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (cluster_id, hash),
FOREIGN KEY (cluster_id) REFERENCES clusters (id) ON DELETE CASCADE
);
CREATE INDEX browser_package_updates_cluster_id_idx ON browser_package_updates (cluster_id);

View file

@ -0,0 +1,254 @@
use chrono::Utc;
use sqlx::{QueryBuilder, Sqlite, SqlitePool};
use crate::models::BrowserPackageUpdateRow;
pub async fn list_for_cluster(
pool: &SqlitePool,
cluster_id: i64,
) -> Result<Vec<BrowserPackageUpdateRow>, sqlx::Error> {
sqlx::query_as!(
BrowserPackageUpdateRow,
r#"
SELECT
cluster_id, hash, provider as "provider!: i64", project_id,
installed_version_id, installed_version_name,
latest_version_id, latest_version_name,
display_name, checked_at, skipped
FROM browser_package_updates
WHERE cluster_id = ?
"#,
cluster_id
)
.fetch_all(pool)
.await
}
pub async fn list_all(
pool: &SqlitePool,
) -> Result<Vec<BrowserPackageUpdateRow>, sqlx::Error> {
sqlx::query_as!(
BrowserPackageUpdateRow,
r#"
SELECT
cluster_id, hash, provider as "provider!: i64", project_id,
installed_version_id, installed_version_name,
latest_version_id, latest_version_name,
display_name, checked_at, skipped
FROM browser_package_updates
"#,
)
.fetch_all(pool)
.await
}
/// Records a pending update.
///
/// A row that already names the same `latest_version_id` keeps its `skipped`
/// flag: the user has answered for that version and re-running the check is not
/// a reason to ask again. A different `latest_version_id` is a new question, so
/// the flag resets.
#[allow(clippy::too_many_arguments)]
pub async fn upsert(
pool: &SqlitePool,
cluster_id: i64,
hash: &str,
provider: i64,
project_id: &str,
installed_version_id: &str,
installed_version_name: &str,
latest_version_id: &str,
latest_version_name: &str,
display_name: &str,
) -> Result<BrowserPackageUpdateRow, sqlx::Error> {
let checked_at = Utc::now().to_rfc3339();
sqlx::query_as!(
BrowserPackageUpdateRow,
r#"
INSERT INTO browser_package_updates (
cluster_id, hash, provider, project_id,
installed_version_id, installed_version_name,
latest_version_id, latest_version_name,
display_name, checked_at, skipped
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
ON CONFLICT(cluster_id, hash) DO UPDATE SET
provider = excluded.provider,
project_id = excluded.project_id,
installed_version_id = excluded.installed_version_id,
installed_version_name = excluded.installed_version_name,
latest_version_name = excluded.latest_version_name,
display_name = excluded.display_name,
checked_at = excluded.checked_at,
skipped = CASE
WHEN browser_package_updates.latest_version_id = excluded.latest_version_id
THEN browser_package_updates.skipped
ELSE 0
END,
latest_version_id = excluded.latest_version_id
RETURNING
cluster_id, hash, provider as "provider!: i64", project_id,
installed_version_id, installed_version_name,
latest_version_id, latest_version_name,
display_name, checked_at, skipped
"#,
cluster_id,
hash,
provider,
project_id,
installed_version_id,
installed_version_name,
latest_version_id,
latest_version_name,
display_name,
checked_at
)
.fetch_one(pool)
.await
}
pub async fn set_skipped(
pool: &SqlitePool,
cluster_id: i64,
hash: &str,
skipped: bool,
) -> Result<(), sqlx::Error> {
let skipped = i64::from(skipped);
sqlx::query!(
r#"
UPDATE browser_package_updates
SET skipped = ?
WHERE cluster_id = ? AND hash = ?
"#,
skipped,
cluster_id,
hash
)
.execute(pool)
.await?;
Ok(())
}
pub async fn delete(pool: &SqlitePool, cluster_id: i64, hash: &str) -> Result<(), sqlx::Error> {
sqlx::query!(
"DELETE FROM browser_package_updates WHERE cluster_id = ? AND hash = ?",
cluster_id,
hash
)
.execute(pool)
.await?;
Ok(())
}
/// Drops rows for hashes the latest check did not report.
///
/// A package that was updated, removed, or turned out to be current again must
/// stop being marked out of date, and the check only tells us what *is* stale.
/// Passing an empty list clears the cluster.
pub async fn retain_hashes(
pool: &SqlitePool,
cluster_id: i64,
hashes: &[String],
) -> Result<(), sqlx::Error> {
if hashes.is_empty() {
sqlx::query!(
"DELETE FROM browser_package_updates WHERE cluster_id = ?",
cluster_id
)
.execute(pool)
.await?;
return Ok(());
}
let mut builder: QueryBuilder<Sqlite> =
QueryBuilder::new("DELETE FROM browser_package_updates WHERE cluster_id = ");
builder.push_bind(cluster_id);
builder.push(" AND hash NOT IN (");
let mut separated = builder.separated(", ");
for hash in hashes {
separated.push_bind(hash);
}
separated.push_unseparated(")");
builder.build().execute(pool).await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
async fn pool() -> SqlitePool {
let pool = SqlitePool::connect("sqlite::memory:")
.await
.expect("in-memory sqlite");
sqlx::migrate!().run(&pool).await.expect("migrations run");
// Runtime query, not the macro: the offline cache is generated for the
// library only, and a fixture is not worth a `--all-targets` prepare.
sqlx::query(
r#"
INSERT INTO clusters (id, name, folder_name, mc_version)
VALUES (1, 'test', 'test', '1.21.4')
"#,
)
.execute(&pool)
.await
.expect("cluster row");
pool
}
async fn record(pool: &SqlitePool, hash: &str, latest: &str) -> BrowserPackageUpdateRow {
upsert(pool, 1, hash, 0, "proj", "v1", "1.0.0", latest, "2.0.0", "Mod")
.await
.expect("upsert")
}
#[tokio::test]
async fn skip_survives_a_recheck_of_the_same_version() {
let pool = pool().await;
record(&pool, "abc", "v2").await;
set_skipped(&pool, 1, "abc", true).await.expect("skip");
let row = record(&pool, "abc", "v2").await;
assert_eq!(row.skipped, 1, "re-running the check must not re-ask");
}
#[tokio::test]
async fn a_newer_version_asks_again() {
let pool = pool().await;
record(&pool, "abc", "v2").await;
set_skipped(&pool, 1, "abc", true).await.expect("skip");
let row = record(&pool, "abc", "v3").await;
assert_eq!(row.skipped, 0, "a different version is a new question");
}
#[tokio::test]
async fn retain_drops_hashes_the_check_no_longer_reports() {
let pool = pool().await;
record(&pool, "abc", "v2").await;
record(&pool, "def", "v2").await;
retain_hashes(&pool, 1, &["abc".to_string()])
.await
.expect("retain");
let rows = list_for_cluster(&pool, 1).await.expect("list");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].hash, "abc");
}
#[tokio::test]
async fn retain_with_nothing_clears_the_cluster() {
let pool = pool().await;
record(&pool, "abc", "v2").await;
retain_hashes(&pool, 1, &[]).await.expect("retain");
assert!(list_for_cluster(&pool, 1).await.expect("list").is_empty());
}
}

View file

@ -1,5 +1,6 @@
pub mod applied_migration;
pub mod artifact;
pub mod browser_package_update;
pub mod bundle;
pub mod cluster;
pub mod cluster_bundle;

View file

@ -15,7 +15,7 @@ pub async fn list_all(pool: &SqlitePool) -> DbResult<Vec<SettingProfileRow>> {
SettingProfileRow,
r#"
SELECT name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,
hook_pre, hook_wrapper, hook_post, os_extra
hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode
FROM setting_profiles
ORDER BY name ASC
"#,
@ -31,7 +31,7 @@ pub async fn get_by_name(pool: &SqlitePool, name: &str) -> DbResult<Option<Setti
SettingProfileRow,
r#"
SELECT name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,
hook_pre, hook_wrapper, hook_post, os_extra
hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode
FROM setting_profiles
WHERE name = ?
"#,
@ -59,9 +59,9 @@ pub async fn upsert(
r#"
INSERT INTO setting_profiles (
name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,
hook_pre, hook_wrapper, hook_post, os_extra
hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(name) DO UPDATE SET
java_path = excluded.java_path,
resolution = excluded.resolution,
@ -72,9 +72,10 @@ pub async fn upsert(
hook_pre = excluded.hook_pre,
hook_wrapper = excluded.hook_wrapper,
hook_post = excluded.hook_post,
os_extra = excluded.os_extra
os_extra = excluded.os_extra,
browser_update_mode = excluded.browser_update_mode
RETURNING name, java_path, resolution, force_fullscreen, mem_max, launch_args, launch_env,
hook_pre, hook_wrapper, hook_post, os_extra
hook_pre, hook_wrapper, hook_post, os_extra, browser_update_mode
"#,
&row.name,
&row.java_path,
@ -87,6 +88,7 @@ pub async fn upsert(
&row.hook_wrapper,
&row.hook_post,
&row.os_extra,
&row.browser_update_mode,
)
.fetch_one(pool)
.await

View file

@ -0,0 +1,21 @@
use sqlx::FromRow;
/// A browser-installed package the last update check found a newer version for.
///
/// `hash` is the artifact currently linked into the cluster, which is also the
/// primary key the package manager renders by, so a row can never be attached
/// to a file the cluster no longer has.
#[derive(Debug, Clone, FromRow)]
pub struct BrowserPackageUpdateRow {
pub cluster_id: i64,
pub hash: String,
pub provider: i64,
pub project_id: String,
pub installed_version_id: String,
pub installed_version_name: String,
pub latest_version_id: String,
pub latest_version_name: String,
pub display_name: String,
pub checked_at: String,
pub skipped: i64,
}

View file

@ -1,4 +1,5 @@
mod artifact;
mod browser_package_update;
mod bundle;
mod cluster;
mod cluster_bundle;
@ -8,6 +9,7 @@ mod package_metadata;
mod setting_profile;
pub use artifact::{ArtifactRow, ClusterArtifactRow, ProviderReleaseRow};
pub use browser_package_update::BrowserPackageUpdateRow;
pub use package_metadata::PackageMetadataRow;
pub use bundle::{BundleRow, NewBundle};
pub use cluster::{ClusterId, ClusterPatch, ClusterRow, NewCluster};

View file

@ -13,4 +13,6 @@ pub struct SettingProfileRow {
pub hook_wrapper: Option<String>,
pub hook_post: Option<String>,
pub os_extra: Option<String>,
/// `PackageUpdateMode::as_str`; NULL inherits the global setting.
pub browser_update_mode: Option<String>,
}