Skip to content

Commit e392d7f

Browse files
committed
chore: optimize build recursive collab
1 parent e396428 commit e392d7f

File tree

3 files changed

+144
-174
lines changed

3 files changed

+144
-174
lines changed

collab/src/folder/folder.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -748,17 +748,17 @@ impl FolderBody {
748748
accumulated_views: &mut Vec<View>,
749749
uid: i64,
750750
) {
751-
if !visited.insert(view_id.to_string()) {
752-
return;
753-
}
754-
match self.views.get_view_with_txn(txn, view_id, uid) {
755-
None => (),
756-
Some(parent_view) => {
751+
let mut stack = vec![*view_id];
752+
while let Some(current_id) = stack.pop() {
753+
if !visited.insert(current_id.to_string()) {
754+
continue;
755+
}
756+
if let Some(parent_view) = self.views.get_view_with_txn(txn, &current_id, uid) {
757757
accumulated_views.push(parent_view.as_ref().clone());
758-
parent_view.children.items.iter().for_each(|child| {
759-
self.get_view_recursively_with_txn(txn, &child.id, visited, accumulated_views, uid)
760-
})
761-
},
758+
for child in parent_view.children.items.iter().rev() {
759+
stack.push(child.id);
760+
}
761+
}
762762
}
763763
}
764764

collab/src/folder/hierarchy_builder.rs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -354,11 +354,22 @@ pub struct FlattedViews;
354354

355355
impl FlattedViews {
356356
pub fn flatten_views(views: Vec<ParentChildViews>) -> Vec<View> {
357-
let mut result = vec![];
358-
for view in views {
359-
result.push(view.view);
360-
result.append(&mut Self::flatten_views(view.children));
357+
let mut result = Vec::new();
358+
let mut stack: Vec<ParentChildViews> = Vec::new();
359+
360+
for view in views.into_iter().rev() {
361+
stack.push(view);
361362
}
363+
364+
while let Some(mut current) = stack.pop() {
365+
let mut children = Vec::new();
366+
std::mem::swap(&mut children, &mut current.children);
367+
for child in children.into_iter().rev() {
368+
stack.push(child);
369+
}
370+
result.push(current.view);
371+
}
372+
362373
result
363374
}
364375
}
@@ -389,6 +400,37 @@ mod tests {
389400
assert_eq!(views.len(), 3);
390401
}
391402

403+
#[tokio::test]
404+
async fn flatten_views_handles_deep_hierarchy() {
405+
const DEPTH: usize = 2048;
406+
let workspace_id: ViewId =
407+
uuid::Uuid::parse_str("00000000-0000-0000-0000-0000000000aa").unwrap();
408+
let mut builder = NestedViewBuilder::new(workspace_id, 1);
409+
410+
builder
411+
.with_view_builder(|view_builder| async move {
412+
let mut current = view_builder.with_name("root");
413+
for depth in 0..DEPTH {
414+
current = current
415+
.with_child_view_builder(|child| async move {
416+
child.with_name(&format!("node_{depth}")).build()
417+
})
418+
.await;
419+
}
420+
current.build()
421+
})
422+
.await;
423+
424+
let workspace_views = builder.build();
425+
let flattened = FlattedViews::flatten_views(workspace_views.into_inner());
426+
assert_eq!(flattened.len(), DEPTH + 1);
427+
assert_eq!(flattened.first().unwrap().name, "root");
428+
assert_eq!(
429+
flattened.last().unwrap().name,
430+
format!("node_{}", DEPTH - 1)
431+
);
432+
}
433+
392434
#[tokio::test]
393435
async fn create_view_with_children_test() {
394436
let workspace_id: ViewId =

collab/src/importer/notion/page.rs

Lines changed: 88 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::document::importer::define::URL_FIELD;
99
use crate::document::importer::md_importer::{MDImporter, create_image_block};
1010
use crate::entity::CollabType;
1111
use crate::entity::uuid_validation::DatabaseId;
12-
use futures::stream::{self, StreamExt};
12+
use futures::stream;
1313

1414
use crate::core::collab::default_client_id;
1515
use crate::database::database_trait::NoPersistenceDatabaseCollabService;
@@ -214,75 +214,40 @@ impl NotionPage {
214214
{
215215
let mut document_resources = HashSet::new();
216216
if let Some(page_id) = document.get_page_id() {
217-
// Start the recursive processing with the root block (page_id)
218-
self
219-
.process_block_and_children(
220-
parent_path,
221-
document,
222-
&page_id,
223-
resources,
224-
&file_url_builder,
225-
&mut document_resources,
226-
)
227-
.await;
228-
}
229-
230-
document_resources.into_iter().collect()
231-
}
232-
233-
#[async_recursion::async_recursion(?Send)]
234-
async fn process_block_and_children<'a, B, O>(
235-
&'a self,
236-
parent_path: &Path,
237-
document: &mut Document,
238-
block_id: &str,
239-
resources: &[PathBuf],
240-
file_url_builder: &B,
241-
document_resources: &mut HashSet<PathBuf>,
242-
) where
243-
B: Fn(&'a str, PathBuf) -> O + Send + Sync + 'a,
244-
O: Future<Output = Option<String>> + Send + 'a,
245-
{
246-
// Process the current block
247-
if let Some((block_type, mut block_data)) = document.get_block_data(block_id) {
248-
if matches!(block_type, BlockType::Image) {
249-
if let Some(image_url) = block_data
250-
.get(URL_FIELD)
251-
.and_then(|v| v.as_str())
252-
.and_then(|s| percent_decode_str(s).decode_utf8().ok())
253-
{
254-
let full_image_url = parent_path.join(image_url.to_string());
255-
let pos = resources.iter().position(|r| r == &full_image_url);
256-
if let Some(pos) = pos {
257-
if let Some(url) = file_url_builder(&self.view_id, full_image_url).await {
258-
document_resources.insert(resources[pos].clone());
259-
block_data.insert(URL_FIELD.to_string(), json!(url));
260-
if let Err(err) = document.update_block(block_id, block_data) {
261-
error!(
262-
"Failed to update block when trying to replace image. error:{:?}",
263-
err
264-
);
217+
let mut stack = vec![page_id];
218+
while let Some(block_id) = stack.pop() {
219+
if let Some((block_type, mut block_data)) = document.get_block_data(&block_id) {
220+
if matches!(block_type, BlockType::Image) {
221+
if let Some(image_url) = block_data
222+
.get(URL_FIELD)
223+
.and_then(|v| v.as_str())
224+
.and_then(|s| percent_decode_str(s).decode_utf8().ok())
225+
{
226+
let full_image_url = parent_path.join(image_url.to_string());
227+
if let Some(pos) = resources.iter().position(|r| r == &full_image_url) {
228+
if let Some(url) = file_url_builder(&self.view_id, full_image_url).await {
229+
document_resources.insert(resources[pos].clone());
230+
block_data.insert(URL_FIELD.to_string(), json!(url));
231+
if let Err(err) = document.update_block(&block_id, block_data) {
232+
error!(
233+
"Failed to update block when trying to replace image. error:{:?}",
234+
err
235+
);
236+
}
237+
}
265238
}
266239
}
267240
}
268241
}
242+
243+
let block_children_ids = document.get_block_children_ids(&block_id);
244+
for child_id in block_children_ids.into_iter().rev() {
245+
stack.push(child_id);
246+
}
269247
}
270248
}
271249

272-
// Recursively process each child block
273-
let block_children_ids = document.get_block_children_ids(block_id);
274-
for child_id in block_children_ids.iter() {
275-
self
276-
.process_block_and_children(
277-
parent_path,
278-
document,
279-
child_id,
280-
resources,
281-
file_url_builder,
282-
document_resources,
283-
)
284-
.await;
285-
}
250+
document_resources.into_iter().collect()
286251
}
287252

288253
async fn replace_link_views<'a, 'b, B, O>(
@@ -300,94 +265,53 @@ impl NotionPage {
300265
{
301266
let mut delta_resources = HashSet::new();
302267
if let Some(first_page_id) = document.get_page_id() {
303-
// Start the recursive processing with the first page's root block
304-
self
305-
.process_link_views_recursive(
306-
parent_path,
307-
document,
308-
&first_page_id,
309-
resources,
310-
&external_link_views,
311-
file_url_builder,
312-
&mut delta_resources,
313-
)
314-
.await;
315-
}
268+
let mut stack = vec![first_page_id];
269+
while let Some(block_id) = stack.pop() {
270+
if let Some((block_type, deltas)) = document.get_block_delta(&block_id) {
271+
let block_deltas_result = self
272+
.process_block_deltas(
273+
parent_path,
274+
document,
275+
&block_id,
276+
block_type,
277+
deltas,
278+
&external_link_views,
279+
resources,
280+
file_url_builder,
281+
)
282+
.await;
316283

317-
delta_resources.into_iter().collect()
318-
}
284+
delta_resources.extend(block_deltas_result.delta_resources);
319285

320-
#[async_recursion::async_recursion]
321-
#[allow(clippy::too_many_arguments)]
322-
async fn process_link_views_recursive<'a, 'b, B, O>(
323-
&'b self,
324-
parent_path: &Path,
325-
document: &mut Document,
326-
block_id: &str,
327-
resources: &[PathBuf],
328-
external_link_views: &HashMap<String, NotionPage>,
329-
file_url_builder: &'a B,
330-
delta_resources: &mut HashSet<PathBuf>,
331-
) where
332-
B: Fn(&'a str, PathBuf) -> O + Send + Sync + 'a,
333-
O: Future<Output = Option<String>> + Send + 'a,
334-
'b: 'a,
335-
{
336-
if let Some((block_type, deltas)) = document.get_block_delta(block_id) {
337-
let block_deltas_result = self
338-
.process_block_deltas(
339-
parent_path,
340-
document,
341-
block_id,
342-
block_type,
343-
deltas,
344-
external_link_views,
345-
resources,
346-
file_url_builder,
347-
)
348-
.await;
349-
350-
// Collect resources from this block
351-
delta_resources.extend(block_deltas_result.delta_resources);
352-
353-
// Update document deltas if new ones are created
354-
if let Some(new_deltas) = block_deltas_result.new_deltas {
355-
if let Err(err) = document.set_block_delta(block_id, new_deltas) {
356-
error!(
357-
"Failed to set block delta when trying to replace ref link. error: {:?}",
358-
err
359-
);
286+
if let Some(new_deltas) = block_deltas_result.new_deltas {
287+
if let Err(err) = document.set_block_delta(&block_id, new_deltas) {
288+
error!(
289+
"Failed to set block delta when trying to replace ref link. error: {:?}",
290+
err
291+
);
292+
}
293+
}
294+
295+
for image_url in block_deltas_result.new_delta_image_blocks {
296+
let new_block_id = crate::document::document_data::generate_id();
297+
let image_block = create_image_block(&new_block_id, image_url, &block_id);
298+
if let Err(err) = document.insert_block(image_block, Some(block_id.clone())) {
299+
error!(
300+
"Failed to insert image block when trying to replace delta link. error: {:?}",
301+
err
302+
);
303+
}
304+
}
360305
}
361-
}
362306

363-
// Insert new image blocks if any are created
364-
for image_url in block_deltas_result.new_delta_image_blocks {
365-
let new_block_id = crate::document::document_data::generate_id();
366-
let image_block = create_image_block(&new_block_id, image_url, block_id);
367-
if let Err(err) = document.insert_block(image_block, Some(block_id.to_string())) {
368-
error!(
369-
"Failed to insert image block when trying to replace delta link. error: {:?}",
370-
err
371-
);
307+
let block_children_ids = document.get_block_children_ids(&block_id);
308+
for child_id in block_children_ids.into_iter().rev() {
309+
stack.push(child_id);
372310
}
373311
}
374312
}
375313

376-
// Recursively process each child block
377-
let block_children_ids = document.get_block_children_ids(block_id);
378-
for child_id in block_children_ids.iter() {
379-
self
380-
.process_link_views_recursive(
381-
parent_path,
382-
document,
383-
child_id,
384-
resources,
385-
external_link_views,
386-
file_url_builder,
387-
delta_resources,
388-
)
389-
.await;
390-
}
314+
delta_resources.into_iter().collect()
391315
}
392316

393317
/// Process the deltas for a block, looking for links to replace
@@ -690,23 +614,27 @@ impl NotionPage {
690614
pub async fn build_imported_collab_recursively<'a>(
691615
notion_page: NotionPage,
692616
) -> ImportedCollabInfoStream<'a> {
693-
let imported_collab_info = notion_page.build_imported_collab().await;
694-
let initial_stream: ImportedCollabInfoStream = match imported_collab_info {
695-
Ok(Some(info)) => Box::pin(stream::once(async { info })),
696-
Ok(None) => Box::pin(stream::empty()),
697-
Err(_) => Box::pin(stream::empty()),
698-
};
699-
700-
let child_streams = notion_page
701-
.children
702-
.into_iter()
703-
.map(|child| async move { build_imported_collab_recursively(child).await });
704-
705-
let child_stream = stream::iter(child_streams)
706-
.then(|stream_future| stream_future)
707-
.flatten();
708-
709-
Box::pin(initial_stream.chain(child_stream))
617+
let stack = vec![notion_page];
618+
let stream = stream::unfold(stack, |mut stack| async move {
619+
while let Some(page) = stack.pop() {
620+
let result = page.build_imported_collab().await;
621+
let children = page.children;
622+
for child in children.into_iter().rev() {
623+
stack.push(child);
624+
}
625+
match result {
626+
Ok(Some(info)) => return Some((info, stack)),
627+
Ok(None) => continue,
628+
Err(err) => {
629+
error!("Failed to build imported collab: {}", err);
630+
continue;
631+
},
632+
}
633+
}
634+
None
635+
});
636+
637+
Box::pin(stream)
710638
}
711639

712640
pub struct ProcessBlockDeltaResult {

0 commit comments

Comments
 (0)