in components/places/src/storage/bookmarks.rs [287:413]
fn insert_bookmark_in_tx(db: &PlacesDb, bm: InsertableItem) -> Result<SyncGuid> {
// find the row ID of the parent.
if bm.parent_guid() == BookmarkRootGuid::Root {
return Err(InvalidPlaceInfo::CannotUpdateRoot(BookmarkRootGuid::Root).into());
}
let parent_guid = bm.parent_guid();
let parent = get_raw_bookmark(db, parent_guid)?
.ok_or_else(|| InvalidPlaceInfo::NoSuchGuid(parent_guid.to_string()))?;
if parent.bookmark_type != BookmarkType::Folder {
return Err(InvalidPlaceInfo::InvalidParent(parent_guid.to_string()).into());
}
// Do the "position" dance.
let position = resolve_pos_for_insert(db, *bm.position(), &parent)?;
// Note that we could probably do this 'fk' work as a sub-query (although
// markh isn't clear how we could perform the insert) - it probably doesn't
// matter in practice though...
let fk = match bm {
InsertableItem::Bookmark { ref b } => {
let page_info = match fetch_page_info(db, &b.url)? {
Some(info) => info.page,
None => new_page_info(db, &b.url, None)?,
};
Some(page_info.row_id)
}
_ => None,
};
let sql = "INSERT INTO moz_bookmarks
(fk, type, parent, position, title, dateAdded, lastModified,
guid, syncStatus, syncChangeCounter) VALUES
(:fk, :type, :parent, :position, :title, :dateAdded, :lastModified,
:guid, :syncStatus, :syncChangeCounter)";
let guid = bm.guid().clone().unwrap_or_else(SyncGuid::random);
if !guid.is_valid_for_places() || !guid.is_valid_for_sync_server() {
return Err(InvalidPlaceInfo::InvalidGuid.into());
}
let date_added = bm.date_added().unwrap_or_else(Timestamp::now);
// last_modified can't be before date_added
let last_modified = max(
bm.last_modified().unwrap_or_else(Timestamp::now),
date_added,
);
let bookmark_type = bm.bookmark_type();
match bm {
InsertableItem::Bookmark { ref b } => {
let title = maybe_truncate_title(&b.title.as_deref());
db.execute_cached(
sql,
&[
(":fk", &fk as &dyn rusqlite::ToSql),
(":type", &bookmark_type),
(":parent", &parent.row_id),
(":position", &position),
(":title", &title),
(":dateAdded", &date_added),
(":lastModified", &last_modified),
(":guid", &guid),
(":syncStatus", &SyncStatus::New),
(":syncChangeCounter", &1),
],
)?;
}
InsertableItem::Separator { .. } => {
db.execute_cached(
sql,
&[
(":type", &bookmark_type as &dyn rusqlite::ToSql),
(":parent", &parent.row_id),
(":position", &position),
(":dateAdded", &date_added),
(":lastModified", &last_modified),
(":guid", &guid),
(":syncStatus", &SyncStatus::New),
(":syncChangeCounter", &1),
],
)?;
}
InsertableItem::Folder { f } => {
let title = maybe_truncate_title(&f.title.as_deref());
db.execute_cached(
sql,
&[
(":type", &bookmark_type as &dyn rusqlite::ToSql),
(":parent", &parent.row_id),
(":title", &title),
(":position", &position),
(":dateAdded", &date_added),
(":lastModified", &last_modified),
(":guid", &guid),
(":syncStatus", &SyncStatus::New),
(":syncChangeCounter", &1),
],
)?;
// now recurse for children
for mut child in f.children.into_iter() {
// As a special case for trees, each child in a folder can specify
// Guid::Empty as the parent.
let specified_parent_guid = child.parent_guid();
if specified_parent_guid.is_empty() {
child.set_parent_guid(guid.clone());
} else if *specified_parent_guid != guid {
return Err(
InvalidPlaceInfo::InvalidParent(specified_parent_guid.to_string()).into(),
);
}
// If children have defaults for last_modified and date_added we use the parent
if child.last_modified().is_none() {
child.set_last_modified(last_modified);
}
if child.date_added().is_none() {
child.set_date_added(date_added);
}
insert_bookmark_in_tx(db, child)?;
}
}
};
// Bump the parent's change counter.
let sql_counter = "
UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + 1
WHERE id = :parent_id";
db.execute_cached(sql_counter, &[(":parent_id", &parent.row_id)])?;
Ok(guid)
}