feat(sync): include plugin settings in snapshots
This commit is contained in:
@@ -34,6 +34,7 @@ mod sync_apply;
|
|||||||
mod sync_context;
|
mod sync_context;
|
||||||
mod sync_history;
|
mod sync_history;
|
||||||
mod sync_notes;
|
mod sync_notes;
|
||||||
|
mod sync_plugin_settings;
|
||||||
mod sync_profiles;
|
mod sync_profiles;
|
||||||
mod sync_reading_list;
|
mod sync_reading_list;
|
||||||
mod sync_site_permissions;
|
mod sync_site_permissions;
|
||||||
|
|||||||
@@ -51,6 +51,18 @@ impl InstalledPlugin {
|
|||||||
self.private_window_allowed = allowed;
|
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]
|
#[must_use]
|
||||||
pub fn manifest(&self) -> &PluginManifest {
|
pub fn manifest(&self) -> &PluginManifest {
|
||||||
&self.manifest
|
&self.manifest
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ impl BrowserCore {
|
|||||||
for record in body.history {
|
for record in body.history {
|
||||||
self.apply_history_sync_record(record, &mut summary, &context)?;
|
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)
|
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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::state::BrowserCore;
|
use crate::state::BrowserCore;
|
||||||
|
use crate::state::InstalledPlugin;
|
||||||
|
|
||||||
pub(crate) const SNAPSHOT_SCHEMA_REV: u32 = 1;
|
pub(crate) const SNAPSHOT_SCHEMA_REV: u32 = 1;
|
||||||
|
|
||||||
@@ -29,6 +30,8 @@ pub(crate) struct SyncSnapshotBody {
|
|||||||
pub(crate) site_permissions: Vec<SitePermissionSyncRecord>,
|
pub(crate) site_permissions: Vec<SitePermissionSyncRecord>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub(crate) history: Vec<HistorySyncRecord>,
|
pub(crate) history: Vec<HistorySyncRecord>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) plugin_settings: Vec<PluginSettingsSyncRecord>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyncSnapshotBody {
|
impl SyncSnapshotBody {
|
||||||
@@ -91,6 +94,11 @@ impl SyncSnapshotBody {
|
|||||||
HistorySyncRecord::from_entry(entry, core.sync_space_name_for(entry.space_id()))
|
HistorySyncRecord::from_entry(entry, core.sync_space_name_for(entry.space_id()))
|
||||||
})
|
})
|
||||||
.collect(),
|
.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 {
|
fn default_sync_enabled() -> bool {
|
||||||
true
|
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