Skip to main content

glycin_utils/
dbus_editor_api.rs

1// Copyright (c) 2024 GNOME Foundation Inc.
2
3use std::io::{Cursor, Seek, SeekFrom, Write};
4use std::marker::PhantomData;
5use std::os::fd::OwnedFd;
6use std::os::unix::net::UnixStream;
7use std::sync::{Arc, Mutex};
8
9use futures_util::FutureExt;
10use glycin_common::{BinaryData, Operations};
11use serde::{Deserialize, Serialize};
12use zbus::zvariant::{DeserializeDict, OwnedObjectPath, SerializeDict, Type};
13
14use crate::dbus_types::{self, *};
15use crate::error::*;
16
17#[derive(DeserializeDict, SerializeDict, Type, Debug)]
18#[zvariant(signature = "dict")]
19#[non_exhaustive]
20pub struct EditRequest {
21    pub operations: BinaryData,
22}
23
24impl EditRequest {
25    pub fn for_operations(operations: &Operations) -> Result<Self, RemoteError> {
26        let operations = operations
27            .to_message_pack()
28            .expected_error()
29            .map_err(|x| x.into_editor_error())?;
30        let operations = BinaryData::from_data(operations)
31            .expected_error()
32            .map_err(|x| x.into_editor_error())?;
33        Ok(Self { operations })
34    }
35
36    pub fn operations(&self) -> Result<Operations, RemoteError> {
37        let binary_data = self
38            .operations
39            .get()
40            .expected_error()
41            .map_err(|x| x.into_editor_error())?;
42
43        let operations = Operations::from_slice(&binary_data)
44            .expected_error()
45            .map_err(|x| x.into_editor_error())?;
46
47        Ok(operations)
48    }
49}
50
51/// Result of a sparse editor operation
52///
53/// This either contains `byte_changes` or `data`, depending on whether a sparse
54/// application of the operations was possible.
55#[derive(DeserializeDict, SerializeDict, Type, Debug, Clone)]
56#[zvariant(signature = "dict")]
57#[non_exhaustive]
58pub struct SparseEditorOutput {
59    pub byte_changes: Option<ByteChanges>,
60    pub data: Option<BinaryData>,
61    pub info: EditorOutputInfo,
62}
63
64impl SparseEditorOutput {
65    pub fn byte_changes(byte_changes: ByteChanges) -> Self {
66        SparseEditorOutput {
67            byte_changes: Some(byte_changes),
68            data: None,
69            info: EditorOutputInfo { lossless: true },
70        }
71    }
72}
73
74impl From<CompleteEditorOutput> for SparseEditorOutput {
75    fn from(value: CompleteEditorOutput) -> Self {
76        Self {
77            byte_changes: None,
78            data: Some(value.data),
79            info: value.info,
80        }
81    }
82}
83
84#[derive(DeserializeDict, SerializeDict, Type, Debug, Clone)]
85#[zvariant(signature = "dict")]
86#[non_exhaustive]
87pub struct ByteChanges {
88    pub changes: Vec<ByteChange>,
89}
90
91#[derive(Deserialize, Serialize, Type, Debug, Clone)]
92pub struct ByteChange {
93    pub offset: u64,
94    pub new_value: u8,
95}
96
97impl ByteChanges {
98    pub fn from_slice(changes: &[(u64, u8)]) -> Self {
99        ByteChanges {
100            changes: changes
101                .iter()
102                .map(|(offset, new_value)| ByteChange {
103                    offset: *offset,
104                    new_value: *new_value,
105                })
106                .collect(),
107        }
108    }
109
110    pub fn apply(&self, data: &mut [u8]) {
111        let mut cur = Cursor::new(data);
112        for change in self.changes.iter() {
113            cur.seek(SeekFrom::Start(change.offset)).unwrap();
114            cur.write_all(&[change.new_value]).unwrap();
115        }
116    }
117}
118
119#[derive(DeserializeDict, SerializeDict, Type, Debug, Clone)]
120#[zvariant(signature = "dict")]
121#[non_exhaustive]
122pub struct CompleteEditorOutput {
123    pub data: BinaryData,
124    pub info: EditorOutputInfo,
125}
126
127impl CompleteEditorOutput {
128    pub fn new(data: BinaryData) -> Self {
129        Self {
130            data,
131            info: Default::default(),
132        }
133    }
134
135    pub fn new_lossless(data: Vec<u8>) -> Result<Self, ProcessError> {
136        let data = BinaryData::from_data(data).expected_error()?;
137        let info = EditorOutputInfo { lossless: true };
138        Ok(Self { data, info })
139    }
140}
141
142#[derive(DeserializeDict, SerializeDict, Type, Debug, Default, Clone)]
143#[zvariant(signature = "dict")]
144#[non_exhaustive]
145pub struct EditorOutputInfo {
146    /// Operation is considered to be lossless
147    ///
148    /// Operations are considered lossless when all metadata are kept, no image
149    /// data is lost, and no image quality is lost.
150    pub lossless: bool,
151}
152
153pub struct Editor<E: EditorImplementation> {
154    pub editor: PhantomData<E>,
155    pub image_id: Mutex<u64>,
156}
157
158/// D-Bus interface for image editors
159#[zbus::interface(name = "org.gnome.glycin.Editor")]
160impl<E: EditorImplementation> Editor<E> {
161    async fn create(
162        &self,
163        mime_type: String,
164        new_image: NewImage,
165        encoding_options: EncodingOptions,
166    ) -> Result<EncodedImage, RemoteError> {
167        E::create(mime_type, new_image, encoding_options).map_err(|x| x.into_editor_error())
168    }
169
170    async fn edit(
171        &self,
172        init_request: InitRequest,
173        #[zbus(connection)] dbus_connection: &zbus::Connection,
174    ) -> Result<dbus_types::RemoteEditableImage, RemoteError> {
175        let fd = OwnedFd::from(init_request.fd);
176        let stream = UnixStream::from(fd);
177
178        let editor_state = E::edit(stream, init_request.mime_type, init_request.details)
179            .map_err(|x| x.into_loader_error())?;
180
181        let image_id = {
182            let lock = self.image_id.lock();
183            let mut image_id = match lock {
184                Ok(id) => id,
185                Err(err) => return Err(RemoteError::InternalLoaderError(err.to_string())),
186            };
187            let id = *image_id;
188            *image_id = id + 1;
189            id
190        };
191
192        let path =
193            OwnedObjectPath::try_from(format!("/org/gnome/glycin/editable_image/{image_id}"))
194                .internal_error()
195                .map_err(|x| x.into_loader_error())?;
196
197        let dbus_image = dbus_types::RemoteEditableImage::new(path.clone());
198
199        dbus_connection
200            .object_server()
201            .at(
202                &path,
203                EditableImage {
204                    editor_implementation: Arc::new(Box::new(editor_state)),
205                    path: path.clone(),
206                    dropped: Default::default(),
207                },
208            )
209            .await
210            .internal_error()
211            .map_err(|x| x.into_loader_error())?;
212
213        Ok(dbus_image)
214    }
215}
216
217pub struct EditableImage<E: EditorImplementation> {
218    pub editor_implementation: Arc<Box<E>>,
219    pub path: OwnedObjectPath,
220    dropped: async_lock::OnceCell<()>,
221}
222
223#[zbus::interface(name = "org.gnome.glycin.EditableImage")]
224impl<E: EditorImplementation> EditableImage<E> {
225    async fn apply_sparse(
226        &self,
227        edit_request: EditRequest,
228    ) -> Result<SparseEditorOutput, RemoteError> {
229        let operations = edit_request.operations()?;
230
231        let editor_implementation = self.editor_implementation.clone();
232        let mut editor_output = blocking::unblock(move || {
233            editor_implementation
234                .apply_sparse(operations)
235                .map_err(|x| x.into_loader_error())
236        })
237        .fuse();
238
239        futures_util::select! {
240            result = editor_output => result,
241            _ = self.dropped.wait().fuse() => Err(RemoteError::Aborted),
242        }
243    }
244
245    /// Same as [`Self::apply()`] but without potential to return sparse changes
246    async fn apply_complete(
247        &self,
248        edit_request: EditRequest,
249    ) -> Result<CompleteEditorOutput, RemoteError> {
250        let operations = edit_request.operations()?;
251
252        let editor_implementation = self.editor_implementation.clone();
253        let mut editor_output = blocking::unblock(move || {
254            editor_implementation
255                .apply_complete(operations)
256                .map_err(|x| x.into_loader_error())
257        })
258        .fuse();
259
260        futures_util::select! {
261            result = editor_output => result,
262            _ = self.dropped.wait().fuse() => Err(RemoteError::Aborted),
263        }
264    }
265
266    async fn done(
267        &self,
268        #[zbus(object_server)] object_server: &zbus::ObjectServer,
269    ) -> Result<(), RemoteError> {
270        log::debug!("Disconnecting {}", self.path);
271        let removed = object_server
272            .remove::<EditableImage<E>, _>(&self.path)
273            .await?;
274        if removed {
275            log::debug!("Removed {}", self.path);
276        } else {
277            log::error!("Failed to remove {}", self.path);
278        }
279        let _ = self.dropped.set(()).await;
280        Ok(())
281    }
282}
283
284/// Implement this trait to create an image editor
285pub trait EditorImplementation: Send + Sync + Sized + 'static {
286    const USEABLE: bool = true;
287
288    fn edit(
289        stream: UnixStream,
290        mime_type: String,
291        details: InitializationDetails,
292    ) -> Result<Self, ProcessError>;
293
294    fn create(
295        mime_type: String,
296        new_image: NewImage,
297        encoding_options: EncodingOptions,
298    ) -> Result<EncodedImage, ProcessError>;
299
300    fn apply_sparse(&self, operations: Operations) -> Result<SparseEditorOutput, ProcessError> {
301        let complete = Self::apply_complete(self, operations)?;
302
303        Ok(SparseEditorOutput::from(complete))
304    }
305
306    fn apply_complete(&self, operations: Operations) -> Result<CompleteEditorOutput, ProcessError>;
307}
308
309/// Give a `None` for a non-existent `EditorImplementation`
310pub enum VoidEditorImplementation {}
311
312impl EditorImplementation for VoidEditorImplementation {
313    const USEABLE: bool = false;
314
315    fn edit(
316        _stream: UnixStream,
317        _mime_type: String,
318        _details: InitializationDetails,
319    ) -> Result<Self, ProcessError> {
320        unreachable!()
321    }
322
323    fn create(
324        _mime_type: String,
325        _new_image: NewImage,
326        _encoding_options: EncodingOptions,
327    ) -> Result<EncodedImage, ProcessError> {
328        unreachable!()
329    }
330
331    fn apply_complete(
332        &self,
333        _operations: Operations,
334    ) -> Result<CompleteEditorOutput, ProcessError> {
335        unreachable!()
336    }
337}