in components/places/src/bookmark_sync/engine.rs [775:904]
fn fetch_outgoing_records(db: &PlacesDb, scope: &SqlInterruptScope) -> Result<Vec<OutgoingBso>> {
let mut changes = Vec::new();
let mut child_record_ids_by_local_parent_id: HashMap<i64, Vec<BookmarkRecordId>> =
HashMap::new();
let mut tags_by_local_id: HashMap<i64, Vec<String>> = HashMap::new();
let mut stmt = db.prepare(
"SELECT parentId, guid FROM structureToUpload
ORDER BY parentId, position",
)?;
let mut results = stmt.query([])?;
while let Some(row) = results.next()? {
scope.err_if_interrupted()?;
let local_parent_id = row.get::<_, i64>("parentId")?;
let child_guid = row.get::<_, SyncGuid>("guid")?;
let child_record_ids = child_record_ids_by_local_parent_id
.entry(local_parent_id)
.or_default();
child_record_ids.push(child_guid.into());
}
let mut stmt = db.prepare("SELECT id, tag FROM tagsToUpload")?;
let mut results = stmt.query([])?;
while let Some(row) = results.next()? {
scope.err_if_interrupted()?;
let local_id = row.get::<_, i64>("id")?;
let tag = row.get::<_, String>("tag")?;
let tags = tags_by_local_id.entry(local_id).or_default();
tags.push(tag);
}
let mut stmt = db.prepare(
"SELECT i.id, i.syncChangeCounter, i.guid, i.isDeleted, i.kind, i.keyword,
i.url, IFNULL(i.title, '') AS title, i.position, i.parentGuid,
IFNULL(i.parentTitle, '') AS parentTitle, i.dateAdded, m.unknownFields
FROM itemsToUpload i
LEFT JOIN moz_bookmarks_synced m ON i.guid == m.guid
",
)?;
let mut results = stmt.query([])?;
while let Some(row) = results.next()? {
scope.err_if_interrupted()?;
let guid = row.get::<_, SyncGuid>("guid")?;
let is_deleted = row.get::<_, bool>("isDeleted")?;
if is_deleted {
changes.push(OutgoingBso::new_tombstone(
BookmarkRecordId::from(guid).as_guid().clone().into(),
));
continue;
}
let parent_guid = row.get::<_, SyncGuid>("parentGuid")?;
let parent_title = row.get::<_, String>("parentTitle")?;
let date_added = row.get::<_, i64>("dateAdded")?;
let unknown_fields = match row.get::<_, Option<String>>("unknownFields")? {
None => UnknownFields::new(),
Some(s) => serde_json::from_str(&s)?,
};
let record: BookmarkItemRecord = match SyncedBookmarkKind::from_u8(row.get("kind")?)? {
SyncedBookmarkKind::Bookmark => {
let local_id = row.get::<_, i64>("id")?;
let title = row.get::<_, String>("title")?;
let url = row.get::<_, String>("url")?;
BookmarkRecord {
record_id: guid.into(),
parent_record_id: Some(parent_guid.into()),
parent_title: Some(parent_title),
date_added: Some(date_added),
has_dupe: true,
title: Some(title),
url: Some(url),
keyword: row.get::<_, Option<String>>("keyword")?,
tags: tags_by_local_id.remove(&local_id).unwrap_or_default(),
unknown_fields,
}
.into()
}
SyncedBookmarkKind::Query => {
let title = row.get::<_, String>("title")?;
let url = row.get::<_, String>("url")?;
QueryRecord {
record_id: guid.into(),
parent_record_id: Some(parent_guid.into()),
parent_title: Some(parent_title),
date_added: Some(date_added),
has_dupe: true,
title: Some(title),
url: Some(url),
tag_folder_name: None,
unknown_fields,
}
.into()
}
SyncedBookmarkKind::Folder => {
let title = row.get::<_, String>("title")?;
let local_id = row.get::<_, i64>("id")?;
let children = child_record_ids_by_local_parent_id
.remove(&local_id)
.unwrap_or_default();
FolderRecord {
record_id: guid.into(),
parent_record_id: Some(parent_guid.into()),
parent_title: Some(parent_title),
date_added: Some(date_added),
has_dupe: true,
title: Some(title),
children,
unknown_fields,
}
.into()
}
SyncedBookmarkKind::Livemark => continue,
SyncedBookmarkKind::Separator => {
let position = row.get::<_, i64>("position")?;
SeparatorRecord {
record_id: guid.into(),
parent_record_id: Some(parent_guid.into()),
parent_title: Some(parent_title),
date_added: Some(date_added),
has_dupe: true,
position: Some(position),
unknown_fields,
}
.into()
}
};
changes.push(OutgoingBso::from_content_with_id(record)?);
}
Ok(changes)
}