feat(sync): include profiles in snapshots
This commit is contained in:
@@ -30,7 +30,9 @@ mod space_exports;
|
||||
mod spaces;
|
||||
mod splits;
|
||||
mod sync;
|
||||
mod sync_context;
|
||||
mod sync_notes;
|
||||
mod sync_profiles;
|
||||
mod sync_reading_list;
|
||||
mod tab_group_order;
|
||||
mod tab_groups;
|
||||
|
||||
@@ -249,7 +249,10 @@ impl BrowserCore {
|
||||
if self.sync_object_policy(SyncObjectKind::Bookmarks) == SyncObjectPolicy::Paused {
|
||||
return Vec::new();
|
||||
}
|
||||
self.bookmarks.iter().collect()
|
||||
self.bookmarks
|
||||
.iter()
|
||||
.filter(|bookmark| self.profile_allows_cloud_sync(bookmark.profile_id()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn bookmark_mut(&mut self, bookmark_id: &BookmarkId) -> Result<&mut BookmarkEntry, CoreError> {
|
||||
|
||||
@@ -7,7 +7,7 @@ use ely_domain::{
|
||||
};
|
||||
use ely_sync_client::SyncClientError;
|
||||
|
||||
use super::BrowserCore;
|
||||
use super::{BrowserCore, sync_context::SyncSnapshotApplyContext};
|
||||
use crate::sync_engine::SyncSnapshotApplySummary;
|
||||
use crate::sync_records::{BookmarkSyncRecord, SpaceSyncRecord, SyncSnapshotBody, TabSyncRecord};
|
||||
|
||||
@@ -111,20 +111,24 @@ impl BrowserCore {
|
||||
body: SyncSnapshotBody,
|
||||
) -> Result<SyncSnapshotApplySummary, SyncClientError> {
|
||||
let mut summary = SyncSnapshotApplySummary::default();
|
||||
let mut context = SyncSnapshotApplyContext::default();
|
||||
for record in body.profiles {
|
||||
self.apply_profile_sync_record(record, &mut summary, &mut context)?;
|
||||
}
|
||||
for record in body.spaces {
|
||||
self.apply_space_sync_record(record, &mut summary)?;
|
||||
self.apply_space_sync_record(record, &mut summary, &context)?;
|
||||
}
|
||||
for record in body.tabs {
|
||||
self.apply_tab_sync_record(record, &mut summary)?;
|
||||
self.apply_tab_sync_record(record, &mut summary, &context)?;
|
||||
}
|
||||
for record in body.bookmarks {
|
||||
self.apply_bookmark_sync_record(record, &mut summary)?;
|
||||
self.apply_bookmark_sync_record(record, &mut summary, &context)?;
|
||||
}
|
||||
for record in body.notes {
|
||||
self.apply_note_sync_record(record, &mut summary)?;
|
||||
self.apply_note_sync_record(record, &mut summary, &context)?;
|
||||
}
|
||||
for record in body.reading_list {
|
||||
self.apply_reading_list_sync_record(record, &mut summary)?;
|
||||
self.apply_reading_list_sync_record(record, &mut summary, &context)?;
|
||||
}
|
||||
Ok(summary)
|
||||
}
|
||||
@@ -203,13 +207,7 @@ impl BrowserCore {
|
||||
}
|
||||
self.tabs
|
||||
.iter()
|
||||
.filter(|tab| {
|
||||
tab.sync_enabled()
|
||||
&& self
|
||||
.profiles
|
||||
.iter()
|
||||
.any(|profile| profile.id() == tab.profile_id() && profile.allows_sync())
|
||||
})
|
||||
.filter(|tab| tab.sync_enabled() && self.profile_allows_cloud_sync(tab.profile_id()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -224,9 +222,10 @@ impl BrowserCore {
|
||||
&mut self,
|
||||
record: SpaceSyncRecord,
|
||||
summary: &mut SyncSnapshotApplySummary,
|
||||
context: &SyncSnapshotApplyContext,
|
||||
) -> Result<(), SyncClientError> {
|
||||
let space_id = parse_space_id(&record.id)?;
|
||||
let default_profile_id = self.sync_profile_id(&record.default_profile_id)?;
|
||||
let default_profile_id = self.sync_profile_id(&record.default_profile_id, context)?;
|
||||
let archive_policy = ArchivePolicy::from(record.archive_policy.clone());
|
||||
let existing_index =
|
||||
self.spaces.iter().position(|space| space.id() == &space_id).or_else(|| {
|
||||
@@ -302,9 +301,10 @@ impl BrowserCore {
|
||||
&mut self,
|
||||
record: TabSyncRecord,
|
||||
summary: &mut SyncSnapshotApplySummary,
|
||||
context: &SyncSnapshotApplyContext,
|
||||
) -> Result<(), SyncClientError> {
|
||||
let tab_id = parse_tab_id(&record.id)?;
|
||||
let profile_id = self.sync_profile_id(&record.profile_id)?;
|
||||
let profile_id = self.sync_profile_id(&record.profile_id, context)?;
|
||||
let space_id = self.sync_space_id(&record.space_id, record.space_name.as_deref())?;
|
||||
let url = UrlText::parse(&record.url).map_err(snapshot_schema_error)?;
|
||||
let created_at = UNIX_EPOCH + Duration::from_secs(record.created_at_secs);
|
||||
@@ -350,9 +350,10 @@ impl BrowserCore {
|
||||
&mut self,
|
||||
record: BookmarkSyncRecord,
|
||||
summary: &mut SyncSnapshotApplySummary,
|
||||
context: &SyncSnapshotApplyContext,
|
||||
) -> Result<(), SyncClientError> {
|
||||
let bookmark_id = parse_bookmark_id(&record.id)?;
|
||||
let profile_id = self.sync_profile_id(&record.profile_id)?;
|
||||
let profile_id = self.sync_profile_id(&record.profile_id, context)?;
|
||||
let space_id = self.sync_space_id(&record.space_id, record.space_name.as_deref())?;
|
||||
let url = UrlText::parse(&record.url).map_err(snapshot_schema_error)?;
|
||||
let added_at = UNIX_EPOCH + Duration::from_secs(record.added_at_secs);
|
||||
@@ -402,14 +403,29 @@ impl BrowserCore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn sync_profile_id(&self, raw: &str) -> Result<ProfileId, SyncClientError> {
|
||||
pub(super) fn sync_profile_id(
|
||||
&self,
|
||||
raw: &str,
|
||||
context: &SyncSnapshotApplyContext,
|
||||
) -> Result<ProfileId, SyncClientError> {
|
||||
let profile_id = ProfileId::parse(raw).map_err(snapshot_schema_error)?;
|
||||
if let Some(local_profile_id) = context.profile_alias(&profile_id) {
|
||||
return Ok(local_profile_id);
|
||||
}
|
||||
if self.profiles.iter().any(|profile| profile.id() == &profile_id) {
|
||||
return Ok(profile_id);
|
||||
}
|
||||
Ok(self.active_profile_id.clone())
|
||||
}
|
||||
|
||||
pub(super) fn profile_allows_cloud_sync(&self, profile_id: &ProfileId) -> bool {
|
||||
self.profiles.iter().any(|profile| {
|
||||
profile.id() == profile_id
|
||||
&& profile.allows_sync()
|
||||
&& profile.sync_policy() == ely_domain::ProfileSyncPolicy::Enabled
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn sync_space_id(
|
||||
&self,
|
||||
raw: &str,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ely_domain::ProfileId;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct SyncSnapshotApplyContext {
|
||||
profile_aliases: BTreeMap<ProfileId, ProfileId>,
|
||||
}
|
||||
|
||||
impl SyncSnapshotApplyContext {
|
||||
pub(super) fn register_profile_alias(&mut self, remote_id: ProfileId, local_id: ProfileId) {
|
||||
self.profile_aliases.insert(remote_id, local_id);
|
||||
}
|
||||
|
||||
pub(super) fn profile_alias(&self, remote_id: &ProfileId) -> Option<ProfileId> {
|
||||
self.profile_aliases.get(remote_id).cloned()
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use ely_domain::{
|
||||
};
|
||||
use ely_sync_client::SyncClientError;
|
||||
|
||||
use super::{BrowserCore, sync::snapshot_schema_error};
|
||||
use super::{BrowserCore, sync::snapshot_schema_error, sync_context::SyncSnapshotApplyContext};
|
||||
use crate::{
|
||||
sync_engine::SyncSnapshotApplySummary,
|
||||
sync_records::{NoteSyncRecord, NoteTargetSyncRecord},
|
||||
@@ -17,23 +17,17 @@ impl BrowserCore {
|
||||
if self.sync_object_policy(SyncObjectKind::Notes) == SyncObjectPolicy::Paused {
|
||||
return Vec::new();
|
||||
}
|
||||
self.notes
|
||||
.iter()
|
||||
.filter(|note| {
|
||||
self.profiles
|
||||
.iter()
|
||||
.any(|profile| profile.id() == note.profile_id() && profile.allows_sync())
|
||||
})
|
||||
.collect()
|
||||
self.notes.iter().filter(|note| self.profile_allows_cloud_sync(note.profile_id())).collect()
|
||||
}
|
||||
|
||||
pub(super) fn apply_note_sync_record(
|
||||
&mut self,
|
||||
record: NoteSyncRecord,
|
||||
summary: &mut SyncSnapshotApplySummary,
|
||||
context: &SyncSnapshotApplyContext,
|
||||
) -> Result<(), SyncClientError> {
|
||||
let note_id = NoteId::parse(&record.id).map_err(snapshot_schema_error)?;
|
||||
let profile_id = self.sync_profile_id(&record.profile_id)?;
|
||||
let profile_id = self.sync_profile_id(&record.profile_id, context)?;
|
||||
let space_id = self.sync_space_id(&record.space_id, record.space_name.as_deref())?;
|
||||
let source_url = UrlText::parse(&record.source_url).map_err(snapshot_schema_error)?;
|
||||
let target =
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
use ely_domain::{Profile, ProfileId, ProfileKind, SyncObjectKind, SyncObjectPolicy};
|
||||
use ely_sync_client::SyncClientError;
|
||||
|
||||
use super::{BrowserCore, sync::snapshot_schema_error, sync_context::SyncSnapshotApplyContext};
|
||||
use crate::{sync_engine::SyncSnapshotApplySummary, sync_records::ProfileSyncRecord};
|
||||
|
||||
impl BrowserCore {
|
||||
pub(crate) fn visible_profiles_for_sync(&self) -> Vec<&Profile> {
|
||||
if self.sync_object_policy(SyncObjectKind::Profiles) == SyncObjectPolicy::Paused {
|
||||
return Vec::new();
|
||||
}
|
||||
self.profiles.iter().filter(|profile| profile.allows_sync()).collect()
|
||||
}
|
||||
|
||||
pub(super) fn apply_profile_sync_record(
|
||||
&mut self,
|
||||
record: ProfileSyncRecord,
|
||||
summary: &mut SyncSnapshotApplySummary,
|
||||
context: &mut SyncSnapshotApplyContext,
|
||||
) -> Result<(), SyncClientError> {
|
||||
let profile_id = ProfileId::parse(&record.id).map_err(snapshot_schema_error)?;
|
||||
let kind = ProfileKind::from(record.kind);
|
||||
if kind == ProfileKind::Private {
|
||||
summary.record_skipped();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let existing_index =
|
||||
self.profiles.iter().position(|profile| profile.id() == &profile_id).or_else(|| {
|
||||
self.profiles
|
||||
.iter()
|
||||
.position(|profile| profile.name().eq_ignore_ascii_case(record.name.trim()))
|
||||
});
|
||||
let id = existing_index
|
||||
.and_then(|index| self.profiles.get(index).map(|profile| profile.id().clone()))
|
||||
.unwrap_or_else(|| profile_id.clone());
|
||||
context.register_profile_alias(profile_id, id.clone());
|
||||
let mut profile = Profile::new(record.name, record.color_hex, kind);
|
||||
profile.set_sync_policy(record.sync_policy.into());
|
||||
let profile = Profile::restore(id, profile);
|
||||
|
||||
match existing_index {
|
||||
Some(index) if self.profiles[index] == profile => {}
|
||||
Some(index) => {
|
||||
self.profiles[index] = profile;
|
||||
summary.record_updated();
|
||||
}
|
||||
None => {
|
||||
self.profiles.push(profile);
|
||||
summary.record_imported();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use ely_domain::{
|
||||
};
|
||||
use ely_sync_client::SyncClientError;
|
||||
|
||||
use super::{BrowserCore, sync::snapshot_schema_error};
|
||||
use super::{BrowserCore, sync::snapshot_schema_error, sync_context::SyncSnapshotApplyContext};
|
||||
use crate::{
|
||||
sync_engine::SyncSnapshotApplySummary,
|
||||
sync_records::{ReadingListSyncRecord, ReadingProgressSyncRecord},
|
||||
@@ -19,11 +19,7 @@ impl BrowserCore {
|
||||
}
|
||||
self.reading_list
|
||||
.iter()
|
||||
.filter(|entry| {
|
||||
self.profiles
|
||||
.iter()
|
||||
.any(|profile| profile.id() == entry.profile_id() && profile.allows_sync())
|
||||
})
|
||||
.filter(|entry| self.profile_allows_cloud_sync(entry.profile_id()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -31,9 +27,10 @@ impl BrowserCore {
|
||||
&mut self,
|
||||
record: ReadingListSyncRecord,
|
||||
summary: &mut SyncSnapshotApplySummary,
|
||||
context: &SyncSnapshotApplyContext,
|
||||
) -> Result<(), SyncClientError> {
|
||||
let entry_id = ReadingListId::parse(&record.id).map_err(snapshot_schema_error)?;
|
||||
let profile_id = self.sync_profile_id(&record.profile_id)?;
|
||||
let profile_id = self.sync_profile_id(&record.profile_id, context)?;
|
||||
let space_id = self.sync_space_id(&record.space_id, record.space_name.as_deref())?;
|
||||
let source_url = UrlText::parse(&record.source_url).map_err(snapshot_schema_error)?;
|
||||
let progress = reading_progress_from_sync_record(record.progress)?;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use ely_domain::{
|
||||
ArchivePolicy, BookmarkEntry, BrowserTab, NoteEntry, NoteTarget, ReadingListEntry,
|
||||
ReadingProgress, Space, TabFlags,
|
||||
ArchivePolicy, BookmarkEntry, BrowserTab, NoteEntry, NoteTarget, Profile, ProfileKind,
|
||||
ProfileSyncPolicy, ReadingListEntry, ReadingProgress, Space, TabFlags,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -14,6 +14,8 @@ pub(crate) const SNAPSHOT_SCHEMA_REV: u32 = 1;
|
||||
pub(crate) struct SyncSnapshotBody {
|
||||
pub(crate) schema_rev: u32,
|
||||
#[serde(default)]
|
||||
pub(crate) profiles: Vec<ProfileSyncRecord>,
|
||||
#[serde(default)]
|
||||
pub(crate) spaces: Vec<SpaceSyncRecord>,
|
||||
pub(crate) bookmarks: Vec<BookmarkSyncRecord>,
|
||||
#[serde(default)]
|
||||
@@ -28,6 +30,11 @@ impl SyncSnapshotBody {
|
||||
pub(crate) fn from_core(core: &BrowserCore) -> Self {
|
||||
Self {
|
||||
schema_rev: SNAPSHOT_SCHEMA_REV,
|
||||
profiles: core
|
||||
.visible_profiles_for_sync()
|
||||
.into_iter()
|
||||
.map(ProfileSyncRecord::from_profile)
|
||||
.collect(),
|
||||
spaces: core
|
||||
.visible_spaces_for_sync()
|
||||
.into_iter()
|
||||
@@ -71,6 +78,79 @@ impl SyncSnapshotBody {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct ProfileSyncRecord {
|
||||
pub(crate) id: String,
|
||||
pub(crate) name: String,
|
||||
pub(crate) color_hex: u32,
|
||||
pub(crate) kind: ProfileKindSyncRecord,
|
||||
pub(crate) sync_policy: ProfileSyncPolicySyncRecord,
|
||||
}
|
||||
|
||||
impl ProfileSyncRecord {
|
||||
fn from_profile(profile: &Profile) -> Self {
|
||||
Self {
|
||||
id: profile.id().as_str().to_string(),
|
||||
name: profile.name().to_string(),
|
||||
color_hex: profile.color_hex(),
|
||||
kind: ProfileKindSyncRecord::from_profile_kind(profile.kind()),
|
||||
sync_policy: ProfileSyncPolicySyncRecord::from_profile_sync_policy(
|
||||
profile.sync_policy(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum ProfileKindSyncRecord {
|
||||
Standard,
|
||||
Private,
|
||||
}
|
||||
|
||||
impl ProfileKindSyncRecord {
|
||||
fn from_profile_kind(kind: &ProfileKind) -> Self {
|
||||
match kind {
|
||||
ProfileKind::Standard => Self::Standard,
|
||||
ProfileKind::Private => Self::Private,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProfileKindSyncRecord> for ProfileKind {
|
||||
fn from(kind: ProfileKindSyncRecord) -> Self {
|
||||
match kind {
|
||||
ProfileKindSyncRecord::Standard => Self::Standard,
|
||||
ProfileKindSyncRecord::Private => Self::Private,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum ProfileSyncPolicySyncRecord {
|
||||
Enabled,
|
||||
Paused,
|
||||
}
|
||||
|
||||
impl ProfileSyncPolicySyncRecord {
|
||||
fn from_profile_sync_policy(policy: ProfileSyncPolicy) -> Self {
|
||||
match policy {
|
||||
ProfileSyncPolicy::Enabled => Self::Enabled,
|
||||
ProfileSyncPolicy::Paused => Self::Paused,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProfileSyncPolicySyncRecord> for ProfileSyncPolicy {
|
||||
fn from(policy: ProfileSyncPolicySyncRecord) -> Self {
|
||||
match policy {
|
||||
ProfileSyncPolicySyncRecord::Enabled => Self::Enabled,
|
||||
ProfileSyncPolicySyncRecord::Paused => Self::Paused,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct SpaceSyncRecord {
|
||||
pub(crate) id: String,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
use std::error::Error;
|
||||
|
||||
use ely_browser_core::{BrowserCore, InitialBrowserConfig};
|
||||
use ely_domain::{ProfileKind, ProfileSyncPolicy, UrlText};
|
||||
|
||||
#[test]
|
||||
fn sync_snapshot_imports_remote_profiles_before_spaces() -> Result<(), Box<dyn Error>> {
|
||||
let mut source = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let source_home_tab_id = source.snapshot()?.active_tab_id;
|
||||
source.set_tab_sync_enabled(&source_home_tab_id, false)?;
|
||||
let profile_id = source.create_profile("Research", 0x9fc9a2, ProfileKind::Standard)?;
|
||||
source.set_profile_sync_policy(&profile_id, ProfileSyncPolicy::Paused)?;
|
||||
source.create_space("Research Space", "R", 0x4477aa)?;
|
||||
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()?;
|
||||
let imported_profile = snapshot
|
||||
.profiles
|
||||
.iter()
|
||||
.find(|profile| profile.name() == "Research")
|
||||
.ok_or("missing imported profile")?;
|
||||
let imported_space = snapshot
|
||||
.spaces
|
||||
.iter()
|
||||
.find(|space| space.name() == "Research Space")
|
||||
.ok_or("missing imported space")?;
|
||||
|
||||
assert_eq!(summary.imported(), 2);
|
||||
assert_eq!(summary.updated(), 0);
|
||||
assert_eq!(summary.skipped(), 0);
|
||||
assert_eq!(imported_profile.color_hex(), 0x9fc9a2);
|
||||
assert_eq!(imported_profile.kind(), &ProfileKind::Standard);
|
||||
assert_eq!(imported_profile.sync_policy(), ProfileSyncPolicy::Paused);
|
||||
assert_eq!(imported_space.default_profile_id(), imported_profile.id());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_snapshot_updates_existing_profile_metadata() -> Result<(), Box<dyn Error>> {
|
||||
let mut source = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let source_home_tab_id = source.snapshot()?.active_tab_id;
|
||||
source.set_tab_sync_enabled(&source_home_tab_id, false)?;
|
||||
let source_profile_id = source.create_profile("Research", 0x9fc9a2, ProfileKind::Standard)?;
|
||||
source.set_profile_sync_policy(&source_profile_id, ProfileSyncPolicy::Paused)?;
|
||||
let bytes = source.build_sync_snapshot_bytes()?;
|
||||
|
||||
let mut target = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let target_profile_id = target.create_profile("Research", 0x111111, ProfileKind::Standard)?;
|
||||
let summary = target.apply_sync_snapshot_bytes(&bytes)?;
|
||||
let snapshot = target.snapshot()?;
|
||||
let updated_profile = snapshot
|
||||
.profiles
|
||||
.iter()
|
||||
.find(|profile| profile.id() == &target_profile_id)
|
||||
.ok_or("missing updated profile")?;
|
||||
|
||||
assert_eq!(summary.imported(), 0);
|
||||
assert_eq!(summary.updated(), 1);
|
||||
assert_eq!(summary.skipped(), 0);
|
||||
assert_eq!(updated_profile.name(), "Research");
|
||||
assert_eq!(updated_profile.color_hex(), 0x9fc9a2);
|
||||
assert_eq!(updated_profile.sync_policy(), ProfileSyncPolicy::Paused);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paused_profile_data_is_omitted_from_sync_snapshots() -> Result<(), Box<dyn Error>> {
|
||||
let mut source = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let source_home_tab_id = source.snapshot()?.active_tab_id;
|
||||
source.set_tab_sync_enabled(&source_home_tab_id, false)?;
|
||||
let profile_id = source.create_profile("Research", 0x9fc9a2, ProfileKind::Standard)?;
|
||||
source.set_profile_sync_policy(&profile_id, ProfileSyncPolicy::Paused)?;
|
||||
source.open_tab(UrlText::parse("https://example.com/paused-profile")?);
|
||||
source.bookmark_active_tab()?;
|
||||
source.save_active_url_note("profile paused")?;
|
||||
source.save_active_tab_to_reading_list()?;
|
||||
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(), 1);
|
||||
assert_eq!(summary.updated(), 0);
|
||||
assert_eq!(summary.skipped(), 0);
|
||||
assert!(snapshot.profiles.iter().any(|profile| profile.name() == "Research"));
|
||||
assert!(
|
||||
snapshot.tabs.iter().all(|tab| tab.url().as_str() != "https://example.com/paused-profile")
|
||||
);
|
||||
assert!(snapshot.bookmarks.is_empty());
|
||||
assert!(snapshot.notes.is_empty());
|
||||
assert!(snapshot.reading_list.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
@@ -67,6 +67,12 @@ impl Profile {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn restore(id: ProfileId, mut profile: Self) -> Self {
|
||||
profile.id = id;
|
||||
profile
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn id(&self) -> &ProfileId {
|
||||
&self.id
|
||||
@@ -106,6 +112,11 @@ impl Profile {
|
||||
self.download_policy = download_policy;
|
||||
}
|
||||
|
||||
pub fn set_presentation(&mut self, name: impl Into<String>, color_hex: u32) {
|
||||
self.name = name.into();
|
||||
self.color_hex = color_hex;
|
||||
}
|
||||
|
||||
pub fn set_sync_policy(&mut self, sync_policy: ProfileSyncPolicy) {
|
||||
self.sync_policy = match self.kind {
|
||||
ProfileKind::Standard => sync_policy,
|
||||
@@ -113,3 +124,39 @@ impl Profile {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Profile, ProfileKind, ProfileSyncPolicy};
|
||||
use crate::ProfileId;
|
||||
|
||||
#[test]
|
||||
fn restores_existing_profile_identity() {
|
||||
let id = ProfileId::new();
|
||||
let profile = Profile::new("Research", 0x9fc9a2, ProfileKind::Standard);
|
||||
let profile = Profile::restore(id.clone(), profile);
|
||||
|
||||
assert_eq!(profile.id(), &id);
|
||||
assert_eq!(profile.name(), "Research");
|
||||
assert_eq!(profile.color_hex(), 0x9fc9a2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updates_profile_presentation() {
|
||||
let mut profile = Profile::new("Old", 0x111111, ProfileKind::Standard);
|
||||
|
||||
profile.set_presentation("New", 0x222222);
|
||||
|
||||
assert_eq!(profile.name(), "New");
|
||||
assert_eq!(profile.color_hex(), 0x222222);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_profile_sync_policy_stays_paused() {
|
||||
let mut profile = Profile::new("Private", 0x807d72, ProfileKind::Private);
|
||||
|
||||
profile.set_sync_policy(ProfileSyncPolicy::Enabled);
|
||||
|
||||
assert_eq!(profile.sync_policy(), ProfileSyncPolicy::Paused);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user