jellyflow_runtime/io/files/
editor_state.rs1use std::path::Path;
2
3use jellyflow_core::core::GraphId;
4use serde::{Deserialize, Serialize};
5
6use crate::io::config::NodeGraphEditorConfig;
7use crate::io::view_state::{NodeGraphPureViewState, NodeGraphViewState};
8
9pub const EDITOR_STATE_FILE_VERSION: u32 = 1;
11
12#[derive(Debug, thiserror::Error)]
14pub enum NodeGraphEditorStateFileError {
15 #[error("failed to read node graph editor-state file: {path}")]
17 Read {
18 path: String,
19 source: std::io::Error,
20 },
21 #[error("failed to parse node graph editor-state file JSON: {path}")]
23 Parse {
24 path: String,
25 source: serde_json::Error,
26 },
27 #[error("failed to write node graph editor-state file: {path}")]
29 Write {
30 path: String,
31 source: std::io::Error,
32 },
33 #[error("failed to serialize node graph editor-state JSON: {path}")]
35 Serialize {
36 path: String,
37 source: serde_json::Error,
38 },
39 #[error("editor-state file wrapper graph_id does not match requested graph_id")]
41 InconsistentGraphId,
42 #[error("unsupported node graph editor-state version {version}; expected {expected}")]
44 UnsupportedVersion { version: u32, expected: u32 },
45}
46
47#[derive(Debug, Clone, PartialEq)]
52pub struct NodeGraphEditorStateFile {
53 pub graph_id: GraphId,
55 pub editor_state_version: u32,
57 pub view_state: NodeGraphViewState,
59 pub editor_config: NodeGraphEditorConfig,
61}
62
63impl NodeGraphEditorStateFile {
64 pub fn new(
66 graph_id: GraphId,
67 view_state: NodeGraphViewState,
68 editor_config: NodeGraphEditorConfig,
69 ) -> Self {
70 Self {
71 graph_id,
72 editor_state_version: EDITOR_STATE_FILE_VERSION,
73 view_state,
74 editor_config,
75 }
76 }
77
78 pub fn load_json(
80 path: impl AsRef<Path>,
81 graph_id: GraphId,
82 ) -> Result<Self, NodeGraphEditorStateFileError> {
83 let path = path.as_ref();
84 let bytes = std::fs::read(path).map_err(|source| NodeGraphEditorStateFileError::Read {
85 path: path.display().to_string(),
86 source,
87 })?;
88
89 let persisted: PersistedNodeGraphEditorStateFile =
90 serde_json::from_slice(&bytes).map_err(|source| {
91 NodeGraphEditorStateFileError::Parse {
92 path: path.display().to_string(),
93 source,
94 }
95 })?;
96 persisted.validate_for_graph(graph_id)?;
97 Ok(persisted.into_editor_state_file())
98 }
99
100 pub fn load_json_if_exists(
102 path: impl AsRef<Path>,
103 graph_id: GraphId,
104 ) -> Result<Option<Self>, NodeGraphEditorStateFileError> {
105 let path = path.as_ref();
106 if !path.exists() {
107 return Ok(None);
108 }
109 Self::load_json(path, graph_id).map(Some)
110 }
111
112 pub fn save_json(&self, path: impl AsRef<Path>) -> Result<(), NodeGraphEditorStateFileError> {
114 let path = path.as_ref();
115 if let Some(parent) = path.parent() {
116 std::fs::create_dir_all(parent).map_err(|source| {
117 NodeGraphEditorStateFileError::Write {
118 path: path.display().to_string(),
119 source,
120 }
121 })?;
122 }
123 let persisted = PersistedNodeGraphEditorStateFile::from_editor_state_file(self);
124 let bytes = serde_json::to_vec_pretty(&persisted).map_err(|source| {
125 NodeGraphEditorStateFileError::Serialize {
126 path: path.display().to_string(),
127 source,
128 }
129 })?;
130 std::fs::write(path, bytes).map_err(|source| NodeGraphEditorStateFileError::Write {
131 path: path.display().to_string(),
132 source,
133 })
134 }
135}
136
137#[derive(Serialize, Deserialize)]
138struct PersistedNodeGraphEditorStateFile {
139 graph_id: GraphId,
140 editor_state_version: u32,
141 view_state: NodeGraphPureViewState,
142 #[serde(default, skip_serializing_if = "NodeGraphEditorConfig::is_default")]
143 editor_config: NodeGraphEditorConfig,
144}
145
146impl PersistedNodeGraphEditorStateFile {
147 fn from_editor_state_file(file: &NodeGraphEditorStateFile) -> Self {
148 Self {
149 graph_id: file.graph_id,
150 editor_state_version: EDITOR_STATE_FILE_VERSION,
151 view_state: NodeGraphPureViewState::from(&file.view_state),
152 editor_config: file.editor_config.clone(),
153 }
154 }
155
156 fn validate_for_graph(&self, graph_id: GraphId) -> Result<(), NodeGraphEditorStateFileError> {
157 if self.graph_id != graph_id {
158 return Err(NodeGraphEditorStateFileError::InconsistentGraphId);
159 }
160 if self.editor_state_version != EDITOR_STATE_FILE_VERSION {
161 return Err(NodeGraphEditorStateFileError::UnsupportedVersion {
162 version: self.editor_state_version,
163 expected: EDITOR_STATE_FILE_VERSION,
164 });
165 }
166 Ok(())
167 }
168
169 fn into_editor_state_file(self) -> NodeGraphEditorStateFile {
170 let mut view_state = NodeGraphViewState::from(self.view_state);
171 view_state.sanitize_viewport();
172 NodeGraphEditorStateFile {
173 graph_id: self.graph_id,
174 editor_state_version: self.editor_state_version,
175 view_state,
176 editor_config: self.editor_config,
177 }
178 }
179}