Skip to content

Commit 18825b6

Browse files
committed
Refactor frontend message handling to consolidate auto-save document loading
1 parent add1bc5 commit 18825b6

File tree

9 files changed

+76
-134
lines changed

9 files changed

+76
-134
lines changed

desktop/src/app.rs

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -314,39 +314,25 @@ impl App {
314314
responses.push(message);
315315
}
316316
}
317-
DesktopFrontendMessage::PersistenceLoadCurrentDocument => {
318-
if let Some((id, document)) = self.persistent_data.current_document() {
319-
let message = DesktopWrapperMessage::LoadDocument {
320-
id,
321-
document,
322-
to_front: false,
323-
select_after_open: true,
324-
};
325-
responses.push(message);
326-
}
327-
}
328-
DesktopFrontendMessage::PersistenceLoadRemainingDocuments => {
329-
for (id, document) in self.persistent_data.documents_before_current().into_iter().rev() {
330-
let message = DesktopWrapperMessage::LoadDocument {
331-
id,
332-
document,
333-
to_front: true,
334-
select_after_open: false,
335-
};
336-
responses.push(message);
337-
}
338-
for (id, document) in self.persistent_data.documents_after_current() {
339-
let message = DesktopWrapperMessage::LoadDocument {
317+
DesktopFrontendMessage::PersistenceLoadDocuments => {
318+
let current_id = self.persistent_data.current_document_id();
319+
let mut seen_current = false;
320+
321+
for (id, document) in self.persistent_data.all_documents() {
322+
let is_current = current_id == Some(id);
323+
let to_front = !seen_current && !is_current;
324+
seen_current |= is_current;
325+
326+
responses.push(DesktopWrapperMessage::LoadDocument {
340327
id,
341328
document,
342-
to_front: false,
329+
to_front,
343330
select_after_open: false,
344-
};
345-
responses.push(message);
331+
});
346332
}
347-
if let Some(id) = self.persistent_data.current_document_id() {
348-
let message = DesktopWrapperMessage::SelectDocument { id };
349-
responses.push(message);
333+
334+
if let Some(id) = current_id {
335+
responses.push(DesktopWrapperMessage::SelectDocument { id });
350336
}
351337
}
352338
DesktopFrontendMessage::OpenLaunchDocuments => {

desktop/src/cef/dirs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::path::PathBuf;
22

33
use crate::dirs::{app_data_dir, ensure_dir_exists};
44

5-
static CEF_DIR_NAME: &str = "browser";
5+
static CEF_DIR_NAME: &str = "cef";
66

77
pub(crate) fn delete_instance_dirs() {
88
let cef_dir = app_data_dir().join(CEF_DIR_NAME);

desktop/src/persist.rs

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -55,32 +55,8 @@ impl PersistentData {
5555
}
5656
}
5757

58-
pub(crate) fn current_document(&self) -> Option<(DocumentId, Document)> {
59-
let current_id = self.current_document_id()?;
60-
Some((current_id, self.read_document(&current_id)?))
61-
}
62-
63-
pub(crate) fn documents_before_current(&self) -> Vec<(DocumentId, Document)> {
64-
let Some(current_id) = self.current_document_id() else {
65-
return Vec::new();
66-
};
67-
self.documents
68-
.iter()
69-
.take_while(|doc| doc.id != current_id)
70-
.filter_map(|doc| Some((doc.id, self.read_document(&doc.id)?)))
71-
.collect()
72-
}
73-
74-
pub(crate) fn documents_after_current(&self) -> Vec<(DocumentId, Document)> {
75-
let Some(current_id) = self.current_document_id() else {
76-
return Vec::new();
77-
};
78-
self.documents
79-
.iter()
80-
.skip_while(|doc| doc.id != current_id)
81-
.skip(1)
82-
.filter_map(|doc| Some((doc.id, self.read_document(&doc.id)?)))
83-
.collect()
58+
pub(crate) fn all_documents(&self) -> Vec<(DocumentId, Document)> {
59+
self.documents.iter().filter_map(|doc| Some((doc.id, self.read_document(&doc.id)?))).collect()
8460
}
8561

8662
pub(crate) fn set_current_document(&mut self, id: DocumentId) {
@@ -154,6 +130,32 @@ impl PersistentData {
154130
}
155131
};
156132
*self = loaded;
133+
134+
self.garbage_collect_document_files();
135+
}
136+
137+
// Remove orphaned document content files that have no corresponding entry in the persisted state
138+
fn garbage_collect_document_files(&self) {
139+
let valid_paths: std::collections::HashSet<_> = self.documents.iter().map(|doc| Self::document_content_path(&doc.id)).collect();
140+
141+
let directory = crate::dirs::app_autosave_documents_dir();
142+
let entries = match std::fs::read_dir(&directory) {
143+
Ok(entries) => entries,
144+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return,
145+
Err(e) => {
146+
tracing::error!("Failed to read autosave documents directory: {e}");
147+
return;
148+
}
149+
};
150+
151+
for entry in entries.flatten() {
152+
let path = entry.path();
153+
if path.is_file() && !valid_paths.contains(&path) {
154+
if let Err(e) = std::fs::remove_file(&path) {
155+
tracing::error!("Failed to remove orphaned document file {path:?}: {e}");
156+
}
157+
}
158+
}
157159
}
158160

159161
fn state_file_path() -> std::path::PathBuf {

desktop/wrapper/src/intercept_frontend_message.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,8 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
9595
// Forward this to update the UI
9696
return Some(FrontendMessage::UpdateOpenDocumentsList { open_documents });
9797
}
98-
FrontendMessage::TriggerLoadFirstAutoSaveDocument => {
99-
dispatcher.respond(DesktopFrontendMessage::PersistenceLoadCurrentDocument);
100-
}
101-
FrontendMessage::TriggerLoadRestAutoSaveDocuments => {
102-
dispatcher.respond(DesktopFrontendMessage::PersistenceLoadRemainingDocuments);
98+
FrontendMessage::TriggerLoadAutoSaveDocuments => {
99+
dispatcher.respond(DesktopFrontendMessage::PersistenceLoadDocuments);
103100
}
104101
FrontendMessage::TriggerOpenLaunchDocuments => {
105102
dispatcher.respond(DesktopFrontendMessage::OpenLaunchDocuments);

desktop/wrapper/src/messages.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ pub enum DesktopFrontendMessage {
5050
PersistenceUpdateCurrentDocument {
5151
id: DocumentId,
5252
},
53-
PersistenceLoadCurrentDocument,
54-
PersistenceLoadRemainingDocuments,
53+
PersistenceLoadDocuments,
5554
PersistenceUpdateDocumentsList {
5655
ids: Vec<DocumentId>,
5756
},

editor/src/messages/frontend/frontend_message.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,7 @@ pub enum FrontendMessage {
123123
document: String,
124124
details: DocumentDetails,
125125
},
126-
TriggerLoadFirstAutoSaveDocument,
127-
TriggerLoadRestAutoSaveDocuments,
126+
TriggerLoadAutoSaveDocuments,
128127
TriggerOpenLaunchDocuments,
129128
TriggerLoadPreferences,
130129
TriggerLoadWorkspaceLayout,

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -109,20 +109,20 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
109109
// Before loading any documents, initially prepare the welcome screen buttons layout
110110
responses.add(PortfolioMessage::RequestWelcomeScreenButtonsLayout);
111111

112-
// Tell frontend to load the current document
113-
responses.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument);
112+
// Tell frontend to load persistent auto-saved documents (placed early so IndexedDB reads overlap with subsequent UI setup)
113+
responses.add(FrontendMessage::TriggerLoadAutoSaveDocuments);
114+
115+
// Tell frontend to load documents passed in as launch arguments
116+
responses.add(FrontendMessage::TriggerOpenLaunchDocuments);
114117

115118
// Display the menu bar at the top of the window
116119
responses.add(MenuBarMessage::SendLayout);
117120

118121
// Send the initial workspace panel layout to the frontend
119122
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
120123

121-
// Send the information for tooltips and categories for each node/input.
122-
responses.add(FrontendMessage::SendUIMetadata {
123-
node_descriptions: document_node_definitions::collect_node_descriptions(),
124-
node_types: document_node_definitions::collect_node_types(),
125-
});
124+
// Request status bar info layout
125+
responses.add(PortfolioMessage::RequestStatusBarInfoLayout);
126126

127127
// Send shortcuts for widgets created in the frontend which need shortcut tooltips
128128
responses.add(FrontendMessage::SendShortcutFullscreen {
@@ -136,14 +136,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
136136
shortcut: action_shortcut_manual!(Key::Shift, Key::MouseLeft),
137137
});
138138

139-
// Request status bar info layout
140-
responses.add(PortfolioMessage::RequestStatusBarInfoLayout);
141-
142-
// Tell frontend to finish loading persistent documents
143-
responses.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
144-
145-
// Tell frontend to load documented passed in as launch arguments
146-
responses.add(FrontendMessage::TriggerOpenLaunchDocuments);
139+
// Send the information for tooltips and categories for each node/input.
140+
responses.add(FrontendMessage::SendUIMetadata {
141+
node_descriptions: document_node_definitions::collect_node_descriptions(),
142+
node_types: document_node_definitions::collect_node_types(),
143+
});
147144
}
148145
PortfolioMessage::DocumentPassMessage { document_id, message } => {
149146
if let Some(document) = self.documents.get_mut(&document_id) {

frontend/src/managers/persistence.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import {
77
loadWorkspaceLayout,
88
storeDocument,
99
removeDocument,
10-
loadFirstDocument,
11-
loadRestDocuments,
10+
loadDocuments,
1211
saveActiveDocument,
1312
} from "/src/utility-functions/persistence";
1413
import type { EditorWrapper } from "/wrapper/pkg/graphite_wasm_wrapper";
@@ -48,12 +47,8 @@ export function createPersistenceManager(subscriptions: SubscriptionsRouter, edi
4847
await removeDocument(String(data.documentId), portfolio);
4948
});
5049

51-
subscriptions.subscribeFrontendMessage("TriggerLoadFirstAutoSaveDocument", async () => {
52-
await loadFirstDocument(editor);
53-
});
54-
55-
subscriptions.subscribeFrontendMessage("TriggerLoadRestAutoSaveDocuments", async () => {
56-
await loadRestDocuments(editor);
50+
subscriptions.subscribeFrontendMessage("TriggerLoadAutoSaveDocuments", async () => {
51+
await loadDocuments(editor);
5752
});
5853

5954
subscriptions.subscribeFrontendMessage("TriggerOpenLaunchDocuments", async () => {
@@ -75,8 +70,7 @@ export function destroyPersistenceManager() {
7570
subscriptions.unsubscribeFrontendMessage("TriggerLoadWorkspaceLayout");
7671
subscriptions.unsubscribeFrontendMessage("TriggerPersistenceWriteDocument");
7772
subscriptions.unsubscribeFrontendMessage("TriggerPersistenceRemoveDocument");
78-
subscriptions.unsubscribeFrontendMessage("TriggerLoadFirstAutoSaveDocument");
79-
subscriptions.unsubscribeFrontendMessage("TriggerLoadRestAutoSaveDocuments");
73+
subscriptions.unsubscribeFrontendMessage("TriggerLoadAutoSaveDocuments");
8074
subscriptions.unsubscribeFrontendMessage("TriggerOpenLaunchDocuments");
8175
subscriptions.unsubscribeFrontendMessage("TriggerSaveActiveDocument");
8276
}

frontend/src/utility-functions/persistence.ts

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -119,62 +119,30 @@ export async function removeDocument(id: string, portfolio: PortfolioStore) {
119119
});
120120
}
121121

122-
export async function loadFirstDocument(editor: EditorWrapper) {
122+
export async function loadDocuments(editor: EditorWrapper) {
123123
await migrateToNewFormat();
124124
await garbageCollectDocuments();
125125

126126
const state = await databaseGet<PersistedState>("state");
127127
const documentContents = await databaseGet<Record<string, string>>("documents");
128128
if (!state || !documentContents || state.documents.length === 0) return;
129129

130-
// Find and open the current document (or fall back to the last document in the list)
130+
// Find the current document (or fall back to the last document in the list)
131131
const currentId = state.current_document;
132132
const currentEntry = currentId !== undefined ? state.documents.find((doc) => doc.id === currentId) : undefined;
133-
const entryToOpen = currentEntry || state.documents[state.documents.length - 1];
134-
135-
const content = documentContents[String(entryToOpen.id)];
136-
if (content === undefined) return;
137-
138-
editor.openAutoSavedDocument(entryToOpen.id, entryToOpen.name, entryToOpen.is_saved, content, false);
139-
editor.selectDocument(entryToOpen.id);
140-
}
141-
142-
export async function loadRestDocuments(editor: EditorWrapper) {
143-
const state = await databaseGet<PersistedState>("state");
144-
const documentContents = await databaseGet<Record<string, string>>("documents");
145-
if (!state || !documentContents || state.documents.length === 0) return;
146-
147-
const currentId = state.current_document;
148-
const currentIndex = currentId !== undefined ? state.documents.findIndex((doc) => doc.id === currentId) : -1;
133+
const current = currentEntry || state.documents[state.documents.length - 1];
134+
const currentIndex = state.documents.indexOf(current);
149135

150136
// Open documents in order around the current document, placing earlier ones before it and later ones after
151-
if (currentIndex !== -1 && currentId !== undefined) {
152-
for (let i = currentIndex - 1; i >= 0; i--) {
153-
const entry = state.documents[i];
154-
const content = documentContents[String(entry.id)];
155-
if (content !== undefined) editor.openAutoSavedDocument(entry.id, entry.name, entry.is_saved, content, true);
156-
}
157-
158-
for (let i = currentIndex + 1; i < state.documents.length; i++) {
159-
const entry = state.documents[i];
160-
const content = documentContents[String(entry.id)];
161-
if (content !== undefined) editor.openAutoSavedDocument(entry.id, entry.name, entry.is_saved, content, false);
162-
}
137+
state.documents.forEach((entry, index) => {
138+
const content = documentContents[String(entry.id)];
139+
if (content === undefined) return;
163140

164-
editor.selectDocument(currentId);
165-
}
166-
// No valid current document: open all remaining documents and select the last one
167-
else {
168-
const length = state.documents.length;
169-
170-
for (let i = length - 2; i >= 0; i--) {
171-
const entry = state.documents[i];
172-
const content = documentContents[String(entry.id)];
173-
if (content !== undefined) editor.openAutoSavedDocument(entry.id, entry.name, entry.is_saved, content, true);
174-
}
141+
const toFront = index < currentIndex;
142+
editor.openAutoSavedDocument(entry.id, entry.name, entry.is_saved, content, toFront);
143+
});
175144

176-
if (length > 0) editor.selectDocument(state.documents[length - 1].id);
177-
}
145+
editor.selectDocument(current.id);
178146
}
179147

180148
export async function saveActiveDocument(documentId: bigint) {
@@ -244,7 +212,7 @@ async function wipeOldFormat() {
244212
}
245213

246214
// TODO: Eventually remove this document upgrade code
247-
export async function migrateToNewFormat() {
215+
async function migrateToNewFormat() {
248216
// Detect the old format by checking for the existence of the "documents_tab_order" key
249217
const oldTabOrder = await databaseGet<string[]>("documents_tab_order");
250218
if (oldTabOrder === undefined) return;

0 commit comments

Comments
 (0)