feat(sync): include plugin settings in snapshots
This commit is contained in:
@@ -34,6 +34,7 @@ mod sync_apply;
|
||||
mod sync_context;
|
||||
mod sync_history;
|
||||
mod sync_notes;
|
||||
mod sync_plugin_settings;
|
||||
mod sync_profiles;
|
||||
mod sync_reading_list;
|
||||
mod sync_site_permissions;
|
||||
|
||||
@@ -51,6 +51,18 @@ impl InstalledPlugin {
|
||||
self.private_window_allowed = allowed;
|
||||
}
|
||||
|
||||
pub(super) fn apply_synced_settings(
|
||||
&mut self,
|
||||
enabled: bool,
|
||||
private_window_allowed: bool,
|
||||
) -> bool {
|
||||
let changed =
|
||||
self.enabled != enabled || self.private_window_allowed != private_window_allowed;
|
||||
self.enabled = enabled;
|
||||
self.private_window_allowed = private_window_allowed;
|
||||
changed
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn manifest(&self) -> &PluginManifest {
|
||||
&self.manifest
|
||||
|
||||
@@ -34,6 +34,9 @@ impl BrowserCore {
|
||||
for record in body.history {
|
||||
self.apply_history_sync_record(record, &mut summary, &context)?;
|
||||
}
|
||||
for record in body.plugin_settings {
|
||||
self.apply_plugin_settings_sync_record(record, &mut summary)?;
|
||||
}
|
||||
Ok(summary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
use ely_domain::{PluginId, SyncObjectKind, SyncObjectPolicy};
|
||||
use ely_sync_client::SyncClientError;
|
||||
|
||||
use super::{BrowserCore, InstalledPlugin, sync::snapshot_schema_error};
|
||||
use crate::{sync_engine::SyncSnapshotApplySummary, sync_records::PluginSettingsSyncRecord};
|
||||
|
||||
impl BrowserCore {
|
||||
pub(crate) fn visible_plugin_settings_for_sync(&self) -> Vec<&InstalledPlugin> {
|
||||
if self.sync_object_policy(SyncObjectKind::PluginSettings) == SyncObjectPolicy::Paused {
|
||||
return Vec::new();
|
||||
}
|
||||
self.installed_plugins.iter().collect()
|
||||
}
|
||||
|
||||
pub(super) fn apply_plugin_settings_sync_record(
|
||||
&mut self,
|
||||
record: PluginSettingsSyncRecord,
|
||||
summary: &mut SyncSnapshotApplySummary,
|
||||
) -> Result<(), SyncClientError> {
|
||||
let plugin_id = PluginId::parse(&record.plugin_id).map_err(snapshot_schema_error)?;
|
||||
let Some(plugin) =
|
||||
self.installed_plugins.iter_mut().find(|plugin| plugin.id() == &plugin_id)
|
||||
else {
|
||||
summary.record_skipped();
|
||||
return Ok(());
|
||||
};
|
||||
if plugin.manifest().checksum() != record.checksum {
|
||||
summary.record_skipped();
|
||||
return Ok(());
|
||||
}
|
||||
if plugin.apply_synced_settings(record.enabled, record.private_window_allowed) {
|
||||
summary.record_updated();
|
||||
} else {
|
||||
summary.record_skipped();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use ely_domain::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::state::BrowserCore;
|
||||
use crate::state::InstalledPlugin;
|
||||
|
||||
pub(crate) const SNAPSHOT_SCHEMA_REV: u32 = 1;
|
||||
|
||||
@@ -29,6 +30,8 @@ pub(crate) struct SyncSnapshotBody {
|
||||
pub(crate) site_permissions: Vec<SitePermissionSyncRecord>,
|
||||
#[serde(default)]
|
||||
pub(crate) history: Vec<HistorySyncRecord>,
|
||||
#[serde(default)]
|
||||
pub(crate) plugin_settings: Vec<PluginSettingsSyncRecord>,
|
||||
}
|
||||
|
||||
impl SyncSnapshotBody {
|
||||
@@ -91,6 +94,11 @@ impl SyncSnapshotBody {
|
||||
HistorySyncRecord::from_entry(entry, core.sync_space_name_for(entry.space_id()))
|
||||
})
|
||||
.collect(),
|
||||
plugin_settings: core
|
||||
.visible_plugin_settings_for_sync()
|
||||
.into_iter()
|
||||
.map(PluginSettingsSyncRecord::from_plugin)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,6 +448,25 @@ impl HistorySyncRecord {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct PluginSettingsSyncRecord {
|
||||
pub(crate) plugin_id: String,
|
||||
pub(crate) checksum: String,
|
||||
pub(crate) enabled: bool,
|
||||
pub(crate) private_window_allowed: bool,
|
||||
}
|
||||
|
||||
impl PluginSettingsSyncRecord {
|
||||
fn from_plugin(plugin: &InstalledPlugin) -> Self {
|
||||
Self {
|
||||
plugin_id: plugin.id().as_str().to_string(),
|
||||
checksum: plugin.manifest().checksum().to_string(),
|
||||
enabled: plugin.enabled(),
|
||||
private_window_allowed: plugin.private_window_allowed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_sync_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
use std::error::Error;
|
||||
|
||||
use ely_browser_core::{BrowserCore, InitialBrowserConfig};
|
||||
use ely_domain::{PluginManifest, SyncObjectKind, SyncObjectPolicy};
|
||||
|
||||
#[test]
|
||||
fn sync_snapshot_updates_existing_plugin_settings() -> Result<(), Box<dyn Error>> {
|
||||
let mut source = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let plugin_id =
|
||||
source.install_plugin(plugin_manifest("com.elydora.reader", DEFAULT_CHECKSUM)?, false)?;
|
||||
source.set_plugin_private_window_allowed(&plugin_id, true)?;
|
||||
source.disable_plugin(&plugin_id)?;
|
||||
pause_tab_sync(&mut source);
|
||||
let bytes = source.build_sync_snapshot_bytes()?;
|
||||
|
||||
let mut target = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let target_plugin_id =
|
||||
target.install_plugin(plugin_manifest("com.elydora.reader", DEFAULT_CHECKSUM)?, false)?;
|
||||
let summary = target.apply_sync_snapshot_bytes(&bytes)?;
|
||||
let snapshot = target.snapshot()?;
|
||||
let [plugin] = snapshot.installed_plugins.as_slice() else {
|
||||
return Err(format!(
|
||||
"expected 1 installed plugin, got {}",
|
||||
snapshot.installed_plugins.len()
|
||||
)
|
||||
.into());
|
||||
};
|
||||
|
||||
assert_eq!(summary.imported(), 0);
|
||||
assert_eq!(summary.updated(), 1);
|
||||
assert_eq!(summary.skipped(), 0);
|
||||
assert_eq!(plugin.id(), &target_plugin_id);
|
||||
assert!(!plugin.enabled());
|
||||
assert!(plugin.private_window_allowed());
|
||||
assert_eq!(snapshot.plugin_audit_events.len(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_snapshot_omits_paused_plugin_settings() -> Result<(), Box<dyn Error>> {
|
||||
let mut source = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let plugin_id =
|
||||
source.install_plugin(plugin_manifest("com.elydora.reader", DEFAULT_CHECKSUM)?, false)?;
|
||||
source.disable_plugin(&plugin_id)?;
|
||||
source.set_sync_object_policy(SyncObjectKind::PluginSettings, SyncObjectPolicy::Paused);
|
||||
pause_tab_sync(&mut source);
|
||||
let bytes = source.build_sync_snapshot_bytes()?;
|
||||
|
||||
let mut target = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
target.install_plugin(plugin_manifest("com.elydora.reader", DEFAULT_CHECKSUM)?, false)?;
|
||||
let summary = target.apply_sync_snapshot_bytes(&bytes)?;
|
||||
let snapshot = target.snapshot()?;
|
||||
let [plugin] = snapshot.installed_plugins.as_slice() else {
|
||||
return Err(format!(
|
||||
"expected 1 installed plugin, got {}",
|
||||
snapshot.installed_plugins.len()
|
||||
)
|
||||
.into());
|
||||
};
|
||||
|
||||
assert_eq!(summary.imported(), 0);
|
||||
assert_eq!(summary.updated(), 0);
|
||||
assert_eq!(summary.skipped(), 0);
|
||||
assert!(plugin.enabled());
|
||||
assert!(!plugin.private_window_allowed());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_snapshot_skips_missing_plugin_package() -> Result<(), Box<dyn Error>> {
|
||||
let mut source = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let plugin_id =
|
||||
source.install_plugin(plugin_manifest("com.elydora.reader", DEFAULT_CHECKSUM)?, false)?;
|
||||
source.disable_plugin(&plugin_id)?;
|
||||
pause_tab_sync(&mut source);
|
||||
let bytes = source.build_sync_snapshot_bytes()?;
|
||||
|
||||
let mut target = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let summary = target.apply_sync_snapshot_bytes(&bytes)?;
|
||||
let snapshot = target.snapshot()?;
|
||||
|
||||
assert_eq!(summary.imported(), 0);
|
||||
assert_eq!(summary.updated(), 0);
|
||||
assert_eq!(summary.skipped(), 1);
|
||||
assert!(snapshot.installed_plugins.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_snapshot_skips_checksum_mismatched_plugin_package() -> Result<(), Box<dyn Error>> {
|
||||
let mut source = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let plugin_id =
|
||||
source.install_plugin(plugin_manifest("com.elydora.reader", DEFAULT_CHECKSUM)?, false)?;
|
||||
source.disable_plugin(&plugin_id)?;
|
||||
pause_tab_sync(&mut source);
|
||||
let bytes = source.build_sync_snapshot_bytes()?;
|
||||
|
||||
let mut target = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
target.install_plugin(plugin_manifest("com.elydora.reader", OTHER_CHECKSUM)?, false)?;
|
||||
let summary = target.apply_sync_snapshot_bytes(&bytes)?;
|
||||
let snapshot = target.snapshot()?;
|
||||
let [plugin] = snapshot.installed_plugins.as_slice() else {
|
||||
return Err(format!(
|
||||
"expected 1 installed plugin, got {}",
|
||||
snapshot.installed_plugins.len()
|
||||
)
|
||||
.into());
|
||||
};
|
||||
|
||||
assert_eq!(summary.imported(), 0);
|
||||
assert_eq!(summary.updated(), 0);
|
||||
assert_eq!(summary.skipped(), 1);
|
||||
assert!(plugin.enabled());
|
||||
assert_eq!(plugin.manifest().checksum(), OTHER_CHECKSUM.to_ascii_lowercase());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const DEFAULT_CHECKSUM: &str = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
const OTHER_CHECKSUM: &str = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
|
||||
|
||||
fn plugin_manifest(id: &str, checksum: &str) -> Result<PluginManifest, Box<dyn Error>> {
|
||||
PluginManifest::from_toml(plugin_manifest_toml(id, checksum).as_str()).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn pause_tab_sync(core: &mut BrowserCore) {
|
||||
core.set_sync_object_policy(SyncObjectKind::Tabs, SyncObjectPolicy::Paused);
|
||||
}
|
||||
|
||||
fn plugin_manifest_toml(id: &str, checksum: &str) -> String {
|
||||
format!(
|
||||
r#"
|
||||
id = "{id}"
|
||||
name = "Reader Exporter"
|
||||
description = "Exports the active reader view."
|
||||
author = "Elydora"
|
||||
homepage = "https://elydora.com/plugins/reader"
|
||||
permissions = ["page:metadata", "ui:command"]
|
||||
contributes = ["command-bar-command"]
|
||||
min_ely_build = "0.1.0"
|
||||
checksum = "{checksum}"
|
||||
|
||||
[signature]
|
||||
algorithm = "ed25519"
|
||||
key_id = "elydora-alpha-plugins"
|
||||
public_key = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
value = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
|
||||
"#
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user