in components/places/src/storage/bookmarks.rs [584:739]
fn update_bookmark_in_tx(
db: &PlacesDb,
guid: &SyncGuid,
item: &UpdatableItem,
raw: RawBookmark,
) -> Result<()> {
// if guid is root
if BookmarkRootGuid::well_known(guid.as_str()).is_some() {
return Err(InvalidPlaceInfo::CannotUpdateRoot(BookmarkRootGuid::Root).into());
}
let existing_parent_guid = raw
.parent_guid
.as_ref()
.ok_or_else(|| Corruption::NonRootWithoutParent(guid.to_string()))?;
let existing_parent_id = raw
.parent_id
.ok_or_else(|| Corruption::NoParent(guid.to_string(), existing_parent_guid.to_string()))?;
if raw.bookmark_type != item.bookmark_type() {
return Err(InvalidPlaceInfo::MismatchedBookmarkType(
raw.bookmark_type as u8,
item.bookmark_type() as u8,
)
.into());
}
let update_old_parent_status;
let update_new_parent_status;
// to make our life easier we update every field, using existing when
// no value is specified.
let parent_id;
let position;
match item.location() {
UpdateTreeLocation::None => {
parent_id = existing_parent_id;
position = raw.position;
update_old_parent_status = false;
update_new_parent_status = false;
}
UpdateTreeLocation::Position { pos } => {
parent_id = existing_parent_id;
update_old_parent_status = true;
update_new_parent_status = false;
let parent = get_raw_bookmark(db, existing_parent_guid)?.ok_or_else(|| {
Corruption::NoParent(guid.to_string(), existing_parent_guid.to_string())
})?;
position = update_pos_for_move(db, *pos, &raw, &parent)?;
}
UpdateTreeLocation::Parent {
guid: new_parent_guid,
pos,
} => {
if new_parent_guid == BookmarkRootGuid::Root {
return Err(InvalidPlaceInfo::CannotUpdateRoot(BookmarkRootGuid::Root).into());
}
let new_parent = get_raw_bookmark(db, new_parent_guid)?
.ok_or_else(|| InvalidPlaceInfo::NoSuchGuid(new_parent_guid.to_string()))?;
if new_parent.bookmark_type != BookmarkType::Folder {
return Err(InvalidPlaceInfo::InvalidParent(new_parent_guid.to_string()).into());
}
parent_id = new_parent.row_id;
update_old_parent_status = true;
update_new_parent_status = true;
let existing_parent = get_raw_bookmark(db, existing_parent_guid)?.ok_or_else(|| {
Corruption::NoParent(guid.to_string(), existing_parent_guid.to_string())
})?;
update_pos_for_deletion(db, raw.position, existing_parent.row_id)?;
position = resolve_pos_for_insert(db, *pos, &new_parent)?;
}
};
let place_id = match item {
UpdatableItem::Bookmark { b } => match &b.url {
None => raw.place_id,
Some(url) => {
let page_info = match fetch_page_info(db, url)? {
Some(info) => info.page,
None => new_page_info(db, url, None)?,
};
Some(page_info.row_id)
}
},
_ => {
// Updating a non-bookmark item, so the existing item must not
// have a place_id
assert_eq!(raw.place_id, None);
None
}
};
// While we could let the SQL take care of being clever about the update
// via, say `title = NULLIF(IFNULL(:title, title), '')`, this code needs
// to know if it changed so the sync counter can be managed correctly.
let update_title = match item {
UpdatableItem::Bookmark { b } => &b.title,
UpdatableItem::Folder { f } => &f.title,
UpdatableItem::Separator { .. } => &None,
};
let title: Option<String> = match update_title {
None => raw.title.clone(),
// We don't differentiate between null and the empty string for title,
// just like desktop doesn't post bug 1360872, hence an empty string
// means "set to null".
Some(val) => {
if val.is_empty() {
None
} else {
Some(val.clone())
}
}
};
let change_incr = title != raw.title || place_id != raw.place_id;
let now = Timestamp::now();
let sql = "
UPDATE moz_bookmarks SET
fk = :fk,
parent = :parent,
position = :position,
title = :title,
lastModified = :now,
syncChangeCounter = syncChangeCounter + :change_incr
WHERE id = :id";
db.execute_cached(
sql,
&[
(":fk", &place_id as &dyn rusqlite::ToSql),
(":parent", &parent_id),
(":position", &position),
(":title", &maybe_truncate_title(&title.as_deref())),
(":now", &now),
(":change_incr", &(change_incr as u32)),
(":id", &raw.row_id),
],
)?;
let sql_counter = "
UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + 1
WHERE id = :parent_id";
// The lastModified of the existing parent ancestors (which may still be
// the current parent) is always updated, even if the change counter for it
// isn't.
set_ancestors_last_modified(db, existing_parent_id, now)?;
if update_old_parent_status {
db.execute_cached(sql_counter, &[(":parent_id", &existing_parent_id)])?;
}
if update_new_parent_status {
set_ancestors_last_modified(db, parent_id, now)?;
db.execute_cached(sql_counter, &[(":parent_id", &parent_id)])?;
}
Ok(())
}