Skip to content

Commit

Permalink
Change the tree to support multiple parents.
Browse files Browse the repository at this point in the history
  • Loading branch information
linabutler committed Jan 9, 2019
1 parent 14b2b54 commit 118fbed
Showing 1 changed file with 80 additions and 62 deletions.
142 changes: 80 additions & 62 deletions src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use guid::{Guid, ROOT_GUID, USER_CONTENT_ROOTS};
#[derive(Debug)]
pub struct Tree {
entries: Vec<Entry>,
index_by_guid: HashMap<Guid, usize>,
entry_indices_by_guid: HashMap<Guid, Vec<usize>>,
deleted_guids: HashSet<Guid>,
}

Expand All @@ -48,18 +48,22 @@ impl Default for Tree {
impl Tree {
/// Constructs a new rooted tree.
pub fn new(root: Item) -> Tree {
let mut index_by_guid = HashMap::new();
index_by_guid.insert(root.guid().clone(), 0);

let entries = vec![Entry { parent_index: None,
item: root,
level: 0,
is_syncable: false,
child_indices: Vec::new(), }];

Tree { entries,
index_by_guid,
deleted_guids: HashSet::new(), }
let mut entry_indices_by_guid = HashMap::new();
entry_indices_by_guid.insert(root.guid().clone(), vec![0]);

let entries = vec![Entry {
parent_index: None,
item: root,
level: 0,
is_syncable: false,
child_indices: Vec::new(),
}];

Tree {
entries,
entry_indices_by_guid,
deleted_guids: HashSet::new(),
}
}

#[inline]
Expand All @@ -80,56 +84,66 @@ impl Tree {
}

pub fn guids<'t>(&'t self) -> impl Iterator<Item = &Guid> + 't {
self.index_by_guid
self.entry_indices_by_guid
.keys()
.chain(self.deleted_guids.iter())
}

pub fn node_for_guid(&self, guid: &Guid) -> Option<Node> {
self.index_by_guid
self.entry_indices_by_guid
.get(guid)
.map(|index| self.node(*index))
.map(|indices| self.node(indices[0]))
}

pub fn insert(&mut self, parent_guid: &Guid, item: Item) -> Result<()> {
if self.index_by_guid.contains_key(item.guid()) {
return Err(ErrorKind::DuplicateItem(item.guid().clone()).into());
}
let child_index = self.entries.len();
let (parent_index, level, is_syncable) = match self.index_by_guid.get(&parent_guid) {
Some(parent_index) => {
let parent = &mut self.entries[*parent_index];
if !parent.item.is_folder() {
return Err(ErrorKind::InvalidParent(item.guid().clone(),
parent.item.guid().clone()).into());
let entry_index = self.entries.len();
// (parent_index, level, is_syncable)
let parent_infos = match self.entry_indices_by_guid.get(&parent_guid) {
Some(parent_indices) => {
let mut parent_infos = Vec::with_capacity(parent_indices.len());
for parent_index in parent_indices.iter() {
let parent_entry = &self.entries[*parent_index];
if !parent_entry.item.is_folder() {
return Err(ErrorKind::InvalidParent(item.guid().clone(),
parent_entry.item.guid().clone()).into());
}
}
parent.child_indices.push(child_index);

// Syncable items descend from the four user content roots. Any
// other roots and their descendants, like the left pane root,
// left pane queries, and custom roots, are non-syncable.
//
// Newer Desktops should never reupload non-syncable items
// (bug 1274496), and should have removed them in Places
// migrations (bug 1310295). However, these items may be
// orphaned in the unfiled root, in which case they're seen as
// syncable locally. If the remote tree has the missing parents
// and roots, we'll determine that the items are non-syncable
// when merging, remove them locally, and mark them for deletion
// remotely.
let is_syncable = USER_CONTENT_ROOTS.contains(item.guid()) || parent.is_syncable;

(*parent_index, parent.level + 1, is_syncable)
for (i, parent_index) in parent_indices.iter().enumerate() {
let parent_entry = &mut self.entries[*parent_index];
parent_entry.child_indices.push(entry_index + i);

// Syncable items descend from the four user content roots. Any
// other roots and their descendants, like the left pane root,
// left pane queries, and custom roots, are non-syncable.
//
// Newer Desktops should never reupload non-syncable items
// (bug 1274496), and should have removed them in Places
// migrations (bug 1310295). However, these items may be
// orphaned in the unfiled root, in which case they're seen as
// syncable locally. If the remote tree has the missing parents
// and roots, we'll determine that the items are non-syncable
// when merging, remove them locally, and mark them for deletion
// remotely.
let is_syncable = USER_CONTENT_ROOTS.contains(&item.guid()) || parent_entry.is_syncable;

parent_infos.push((*parent_index, parent_entry.level + 1, is_syncable));
}
parent_infos
},
None => return Err(ErrorKind::MissingParent(item.guid().clone(),
parent_guid.clone()).into()),
};
self.index_by_guid.insert(item.guid().clone(), child_index);
self.entries.push(Entry { parent_index: Some(parent_index),
item,
level,
is_syncable,
child_indices: Vec::new(), });
let entry_indices = self.entry_indices_by_guid.entry(item.guid().clone()).or_default();
for (i, (parent_index, level, is_syncable)) in parent_infos.into_iter().enumerate() {
entry_indices.push(entry_index + i);
self.entries.push(Entry {
parent_index: Some(parent_index),
item: item.clone(),
level,
is_syncable,
child_indices: Vec::new(),
});
}
Ok(())
}

Expand Down Expand Up @@ -160,20 +174,24 @@ impl fmt::Display for Tree {
#[cfg(test)]
impl PartialEq for Tree {
fn eq(&self, other: &Tree) -> bool {
if self.index_by_guid.len() != other.index_by_guid.len() {
if self.entry_indices_by_guid.len() != other.entry_indices_by_guid.len() {
return false;
}
for (guid, index) in &self.index_by_guid {
if let Some(other_index) = other.index_by_guid.get(guid) {
let entry = &self.entries[*index];
let other_entry = &other.entries[*other_index];
for (guid, indices) in &self.entry_indices_by_guid {
if let Some(other_indices) = other.entry_indices_by_guid.get(guid) {
if indices.len() > 1 || other_indices.len() > 1 {
// TODO(lina): Compare divergent trees.
return false;
}
let entry = &self.entries[indices[0]];
let other_entry = &other.entries[other_indices[0]];
if entry.item != other_entry.item {
return false;
}
match (entry.parent_index, other_entry.parent_index) {
(Some(parent_index), Some(other_parent_index)) => {
let parent_guid = &self.entries[parent_index].item.guid();
let other_parent_guid = &other.entries[other_parent_index].item.guid();
let parent_guid = self.entries[parent_index].item.guid();
let other_parent_guid = other.entries[other_parent_index].item.guid();
if parent_guid != other_parent_guid {
return false;
}
Expand All @@ -182,21 +200,21 @@ impl PartialEq for Tree {
_ => return false,
};
let child_guids = entry.child_indices
.iter()
.map(|index| self.entries[*index].item.guid());
.iter()
.map(|index| self.entries[*index].item.guid());
let other_child_guids =
other_entry.child_indices
.iter()
.map(|other_index| other.entries[*other_index].item.guid());
.iter()
.map(|other_index| other.entries[*other_index].item.guid());
if child_guids.ne(other_child_guids) {
return false;
}
} else {
return false;
}
}
for other_guid in other.index_by_guid.keys() {
if !self.index_by_guid.contains_key(other_guid) {
for other_guid in other.entry_indices_by_guid.keys() {
if !self.entry_indices_by_guid.contains_key(other_guid) {
return false;
}
}
Expand Down

0 comments on commit 118fbed

Please sign in to comment.