diff --git a/src/headless_engine/contracts.rs b/src/headless_engine/contracts.rs index 1825531..1682cbd 100644 --- a/src/headless_engine/contracts.rs +++ b/src/headless_engine/contracts.rs @@ -320,6 +320,11 @@ pub(super) enum AppAction { PluginRepositoryRemoveRequested { manifest_url: String }, #[serde(rename = "pluginScraperToggled")] PluginScraperToggled { scraper_id: String, enabled: bool }, + #[serde(rename = "pluginScraperSettingsUpdated")] + PluginScraperSettingsUpdated { + scraper_id: String, + settings: Value, + }, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)] diff --git a/src/headless_engine/mod.rs b/src/headless_engine/mod.rs index a6b8e8a..495ce68 100644 --- a/src/headless_engine/mod.rs +++ b/src/headless_engine/mod.rs @@ -550,6 +550,10 @@ impl HeadlessEngine { scraper_id, enabled, } => plugins::dispatch_toggle_scraper(self, scraper_id, enabled), + AppAction::PluginScraperSettingsUpdated { + scraper_id, + settings, + } => plugins::dispatch_update_scraper_settings(self, scraper_id, settings), } } @@ -1770,6 +1774,78 @@ mod tests { assert!(destroy_headless_engine(handle)); } + #[test] + fn plugin_repository_refetch_preserves_disabled_state_and_settings() { + let handle = create_headless_engine("{}"); + let manifest_value = json!({ + "manifestUrl": "https://example.com/manifest.json", + "manifest": { + "name": "Phisher's Repo", + "version": "1.0.0", + "scrapers": [ + {"id": "MoviesDrive", "name": "MoviesDrive", "version": "1.1.1", "filename": "src/providers/moviesdrive.js"} + ] + } + }); + + let requested: Value = serde_json::from_str( + &headless_engine_dispatch_json( + handle, + r#"{"type":"pluginRepositoryAddRequested","manifestUrl":"https://example.com/manifest.json"}"#, + ) + .unwrap(), + ) + .unwrap(); + headless_engine_complete_effect_json( + handle, + &json!({ + "effectId": requested["effects"][0]["id"].as_str().unwrap(), + "status": "ok", + "value": manifest_value + }) + .to_string(), + ) + .unwrap(); + + headless_engine_dispatch_json( + handle, + r#"{"type":"pluginScraperToggled","scraperId":"MoviesDrive","enabled":false}"#, + ) + .unwrap(); + headless_engine_dispatch_json( + handle, + r#"{"type":"pluginScraperSettingsUpdated","scraperId":"MoviesDrive","settings":{"quality":"1080p"}}"#, + ) + .unwrap(); + + let refetch_requested: Value = serde_json::from_str( + &headless_engine_dispatch_json( + handle, + r#"{"type":"pluginRepositoryAddRequested","manifestUrl":"https://example.com/manifest.json"}"#, + ) + .unwrap(), + ) + .unwrap(); + let refetched: Value = serde_json::from_str( + &headless_engine_complete_effect_json( + handle, + &json!({ + "effectId": refetch_requested["effects"][0]["id"].as_str().unwrap(), + "status": "ok", + "value": manifest_value + }) + .to_string(), + ) + .unwrap(), + ) + .unwrap(); + + let scraper = &refetched["state"]["plugins"]["scrapers"][0]; + assert_eq!(scraper["enabled"], false); + assert_eq!(scraper["settings"]["quality"], "1080p"); + assert!(destroy_headless_engine(handle)); + } + #[test] fn calendar_completion_plans_os_side_effects_in_core() { let handle = create_headless_engine("{}"); diff --git a/src/headless_engine/plugins.rs b/src/headless_engine/plugins.rs index 7b927f6..45b5440 100644 --- a/src/headless_engine/plugins.rs +++ b/src/headless_engine/plugins.rs @@ -77,6 +77,22 @@ pub(super) fn dispatch_toggle_scraper( vec![] } +pub(super) fn dispatch_update_scraper_settings( + engine: &mut HeadlessEngine, + scraper_id: String, + settings: Value, +) -> Vec { + if let Some(items) = engine.state.plugins.scrapers.as_array_mut() { + if let Some(scraper) = items + .iter_mut() + .find(|scraper| scraper["id"].as_str() == Some(scraper_id.as_str())) + { + scraper["settings"] = settings; + } + } + vec![] +} + pub(super) fn complete( engine: &mut HeadlessEngine, generation: u64, @@ -114,11 +130,43 @@ pub(super) fn complete( repository_entry, ); + let previous_by_id: std::collections::HashMap = engine + .state + .plugins + .scrapers + .as_array() + .into_iter() + .flatten() + .filter(|scraper| scraper["repositoryUrl"].as_str() == Some(manifest_url)) + .filter_map(|scraper| { + scraper["id"] + .as_str() + .map(|id| (id.to_string(), scraper.clone())) + }) + .collect(); + if let Some(items) = engine.state.plugins.scrapers.as_array_mut() { items.retain(|scraper| scraper["repositoryUrl"].as_str() != Some(manifest_url)); } for mut scraper in scrapers { scraper["repositoryUrl"] = Value::String(manifest_url.to_string()); + let manifest_enabled = scraper["enabled"].as_bool().unwrap_or(true); + if let Some(previous) = scraper["id"] + .as_str() + .and_then(|id| previous_by_id.get(id)) + { + if manifest_enabled { + if let Some(previous_enabled) = previous["enabled"].as_bool() { + scraper["enabled"] = Value::Bool(previous_enabled); + } + } + if let Some(previous_settings) = previous.get("settings") { + scraper["settings"] = previous_settings.clone(); + } + } + if scraper.get("settings").is_none() { + scraper["settings"] = json!({}); + } if let Some(items) = engine.state.plugins.scrapers.as_array_mut() { items.push(scraper); } diff --git a/src/plugins.rs b/src/plugins.rs index 4d87c1b..eda8ec7 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -166,6 +166,7 @@ fn plugin_result_to_stream(result: PluginStreamResult) -> Stream { title: Some(result.title), info_hash: result.info_hash, headers: result.headers, + subtitles: subtitle_tracks.clone(), subtitle_tracks, extra, ..Default::default() @@ -257,6 +258,8 @@ mod tests { let subs = stream.subtitle_tracks.as_ref().unwrap(); assert_eq!(subs[0].lang, "en"); assert_eq!(subs[0].url, "https://example.com/sub.srt"); + let subs_alias = stream.subtitles.as_ref().unwrap(); + assert_eq!(subs_alias[0].url, "https://example.com/sub.srt"); } #[test]