Skip to content

Commit 058da30

Browse files
serde embedded resources as base64
1 parent ad1ec00 commit 058da30

4 files changed

Lines changed: 129 additions & 12 deletions

File tree

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay
1919
use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings, Pivot};
2020
use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext;
2121
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
22+
use crate::messages::portfolio::document::utility_types::embedded_resources::EmbeddedResources;
2223
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, PTZ};
2324
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
2425
use crate::messages::portfolio::utility_types::{CachedData, PanelType};
@@ -109,9 +110,8 @@ pub struct DocumentMessageHandler {
109110
/// The current opacity of the faded node graph background that covers up the artwork.
110111
pub graph_fade_artwork_percentage: f64,
111112
/// The resources that are currently used by the document.
112-
#[allow(clippy::type_complexity)]
113-
#[serde(skip_serializing_if = "Option::is_none")]
114-
pub resources: Option<Vec<(ResourceHash, Box<[u8]>)>>,
113+
#[serde(skip_serializing_if = "Option::is_none", rename = "resources")]
114+
pub embedded_resources: Option<EmbeddedResources>,
115115

116116
// =============================================
117117
// Fields omitted from the saved document format
@@ -174,7 +174,7 @@ impl Default for DocumentMessageHandler {
174174
graph_view_overlay_open: false,
175175
snapping_state: SnappingState::default(),
176176
graph_fade_artwork_percentage: 80.,
177-
resources: None,
177+
embedded_resources: None,
178178
// =============================================
179179
// Fields omitted from the saved document format
180180
// =============================================
@@ -907,15 +907,15 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
907907
}
908908
let folder = self.path.as_ref().and_then(|path| path.parent()).map(|parent| parent.to_path_buf());
909909

910-
let resources = resources.export(&Vec::from_iter(self.used_resources()));
911-
let resources = resources.iter().map(|(hash, resource)| (*hash, Box::from(resource.as_ref()))).collect::<Vec<_>>();
912-
if !resources.is_empty() {
913-
self.resources = Some(resources);
910+
let exported = resources.export(&Vec::from_iter(self.used_resources()));
911+
let embedded = EmbeddedResources::from_iter(exported);
912+
if !embedded.is_empty() {
913+
self.embedded_resources = Some(embedded);
914914
}
915915

916916
let content = self.serialize_document();
917917

918-
self.resources = None;
918+
self.embedded_resources = None;
919919

920920
responses.add(FrontendMessage::TriggerSaveDocument {
921921
document_id,
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
use base64::Engine;
2+
use base64::engine::general_purpose::STANDARD as BASE64;
3+
use graph_craft::application_io::{Resource, ResourceHash};
4+
use std::collections::HashMap;
5+
use std::fmt;
6+
7+
#[derive(Clone, Default, Debug, PartialEq)]
8+
pub struct EmbeddedResources {
9+
resources: HashMap<ResourceHash, Resource>,
10+
}
11+
12+
impl EmbeddedResources {
13+
pub fn new() -> Self {
14+
Self::default()
15+
}
16+
17+
pub fn insert(&mut self, hash: ResourceHash, resource: Resource) -> Option<Resource> {
18+
self.resources.insert(hash, resource)
19+
}
20+
21+
pub fn get(&self, hash: &ResourceHash) -> Option<&Resource> {
22+
self.resources.get(hash)
23+
}
24+
25+
pub fn remove(&mut self, hash: &ResourceHash) -> Option<Resource> {
26+
self.resources.remove(hash)
27+
}
28+
29+
pub fn contains(&self, hash: &ResourceHash) -> bool {
30+
self.resources.contains_key(hash)
31+
}
32+
33+
pub fn len(&self) -> usize {
34+
self.resources.len()
35+
}
36+
37+
pub fn is_empty(&self) -> bool {
38+
self.resources.is_empty()
39+
}
40+
41+
pub fn iter(&self) -> impl Iterator<Item = (&ResourceHash, &Resource)> {
42+
self.resources.iter()
43+
}
44+
45+
pub fn hashes(&self) -> impl Iterator<Item = &ResourceHash> {
46+
self.resources.keys()
47+
}
48+
}
49+
50+
impl FromIterator<(ResourceHash, Resource)> for EmbeddedResources {
51+
fn from_iter<T: IntoIterator<Item = (ResourceHash, Resource)>>(iter: T) -> Self {
52+
Self { resources: iter.into_iter().collect() }
53+
}
54+
}
55+
56+
impl IntoIterator for EmbeddedResources {
57+
type Item = (ResourceHash, Resource);
58+
type IntoIter = std::collections::hash_map::IntoIter<ResourceHash, Resource>;
59+
60+
fn into_iter(self) -> Self::IntoIter {
61+
self.resources.into_iter()
62+
}
63+
}
64+
65+
impl serde::Serialize for EmbeddedResources {
66+
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
67+
use serde::ser::SerializeMap;
68+
69+
let human_readable = serializer.is_human_readable();
70+
let mut map = serializer.serialize_map(Some(self.resources.len()))?;
71+
for (hash, resource) in &self.resources {
72+
let bytes: &[u8] = resource.as_ref();
73+
if human_readable {
74+
map.serialize_entry(hash, &BASE64.encode(bytes))?;
75+
} else {
76+
map.serialize_entry(hash, serde_bytes::Bytes::new(bytes))?;
77+
}
78+
}
79+
map.end()
80+
}
81+
}
82+
83+
impl<'de> serde::Deserialize<'de> for EmbeddedResources {
84+
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
85+
struct EmbeddedResourcesVisitor {
86+
human_readable: bool,
87+
}
88+
89+
impl<'de> serde::de::Visitor<'de> for EmbeddedResourcesVisitor {
90+
type Value = EmbeddedResources;
91+
92+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
93+
formatter.write_str("a map of ResourceHash to resource bytes")
94+
}
95+
96+
fn visit_map<A: serde::de::MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
97+
let mut resources = HashMap::with_capacity(map.size_hint().unwrap_or(0));
98+
while let Some(hash) = map.next_key::<ResourceHash>()? {
99+
let resource = if self.human_readable {
100+
let encoded: String = map.next_value()?;
101+
let bytes = BASE64.decode(&encoded).map_err(serde::de::Error::custom)?;
102+
Resource::new(bytes)
103+
} else {
104+
let bytes: serde_bytes::ByteBuf = map.next_value()?;
105+
Resource::new(bytes.into_vec())
106+
};
107+
resources.insert(hash, resource);
108+
}
109+
Ok(EmbeddedResources { resources })
110+
}
111+
}
112+
113+
let human_readable = deserializer.is_human_readable();
114+
deserializer.deserialize_map(EmbeddedResourcesVisitor { human_readable })
115+
}
116+
}

editor/src/messages/portfolio/document/utility_types/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod clipboards;
22
pub mod document_metadata;
3+
pub mod embedded_resources;
34
pub mod error;
45
pub mod misc;
56
pub mod network_interface;

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -778,9 +778,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
778778
}
779779
};
780780

781-
if let Some(resources) = document.resources.take() {
782-
resources.into_iter().for_each(|(hash, data)| {
783-
let data: Arc<[u8]> = Arc::from(data);
781+
if let Some(resources) = document.embedded_resources.take() {
782+
resources.into_iter().for_each(|(hash, resource)| {
783+
let data: Arc<[u8]> = Arc::from(resource.as_ref());
784784
if ResourceHash::from(data.as_ref()) != hash {
785785
log::error!("Resource hash mismatch for resource with hash {hash}");
786786
return;

0 commit comments

Comments
 (0)