1use std::collections::BTreeMap;
2use std::pin::Pin;
3use std::sync::Arc;
4
5#[cfg(feature = "builtin")]
6use futures_util::FutureExt;
7use gio::glib;
8use gio::prelude::{IsA, *};
9#[cfg(feature = "builtin")]
10use glycin_utils::EditorImplementation;
11use glycin_utils::safe_math::SafeConversion;
12use glycin_utils::{
13 ByteChanges, ByteData, CompleteEditorOutput, FungibleMemory, Operations, SparseEditorOutput,
14};
15#[cfg(feature = "external")]
16use zbus::zvariant::OwnedObjectPath;
17
18use crate::api::*;
19#[cfg(feature = "external")]
20use crate::dbus::EditorProxy;
21use crate::error::{ErrorKind, ResultExt};
22use crate::main_context::{MainContextSelector, ProvidesMainContext};
23#[cfg(feature = "external")]
24use crate::pool::PooledProcess;
25use crate::util::{self, CancellableFuture, ShortcutErrorFuture};
26use crate::{Error, MimeType, Pool, config};
27
28#[derive(Debug)]
30pub struct Editor {
31 source: Source,
32 pool: Arc<Pool>,
33 cancellable: gio::Cancellable,
34 pub(crate) sandbox_selector: SandboxSelector,
35 pub(crate) main_context_selector: MainContextSelector,
36}
37
38static_assertions::assert_impl_all!(Editor: Send, Sync);
39
40impl Editor {
41 pub fn new(file: gio::File) -> Self {
43 Self::new_source(Source::File(file))
44 }
45
46 pub unsafe fn new_stream(stream: impl IsA<gio::InputStream>) -> Self {
52 unsafe { Self::new_source(Source::Stream(GInputStreamSend::new(stream.upcast()))) }
53 }
54
55 pub fn new_bytes(bytes: glib::Bytes) -> Self {
57 let stream = gio::MemoryInputStream::from_bytes(&bytes);
58 unsafe { Self::new_stream(stream) }
59 }
60
61 pub fn new_vec(buf: Vec<u8>) -> Self {
63 let bytes = glib::Bytes::from_owned(buf);
64 Self::new_bytes(bytes)
65 }
66
67 pub(crate) fn new_source(source: Source) -> Self {
68 Self {
69 source,
70 pool: Pool::global(),
71 cancellable: gio::Cancellable::new(),
72 sandbox_selector: SandboxSelector::default(),
73 main_context_selector: MainContextSelector::Auto,
74 }
75 }
76
77 pub fn main_context_selector(&mut self, selector: MainContextSelector) -> &mut Self {
78 self.main_context_selector = selector;
79 self
80 }
81
82 pub fn edit(self) -> Pin<Box<dyn Future<Output = Result<EditableImage, Error>> + Send>> {
83 self.edit_with_sync(false)
84 }
85
86 fn edit_with_sync(
90 self,
91 sync: bool,
92 ) -> Pin<Box<dyn Future<Output = Result<EditableImage, Error>> + Send>> {
93 Box::pin(async move {
94 let main_context = self.main_context();
95 let cancellable = self.cancellable.clone();
96
97 let f =
98 move || async move { self.edit_internal(sync).await }.make_cancellable(cancellable);
99
100 main_context.spawn_from_within(f).await?
101 })
102 }
103
104 async fn edit_internal(mut self, sync: bool) -> Result<EditableImage, Error> {
105 let source: Source = self.source.send();
106
107 let editor_context =
108 ProcessorContext::new(source, false, &self.sandbox_selector, sync).await?;
109
110 let editor = editor_context
111 .editor(self.pool.clone(), &self.cancellable)
112 .await?;
113
114 match editor {
115 #[cfg(feature = "external")]
116 Processor::Binary(editor) => {
117 let process = editor.process.use_();
118
119 let (external_reader, load_image_future) =
120 editor.source_transmission.spawn_external()?;
121
122 let editable_image_future = process.edit(external_reader, &editor.mime_type);
123
124 let editable_image = editable_image_future
125 .join_abort_on_error(load_image_future)
126 .await
127 .err_context(&process)?;
128
129 self.cancellable.connect_cancelled(glib::clone!(
130 #[strong(rename_to=process)]
131 editor.process,
132 #[strong(rename_to=path)]
133 editable_image.edit_request,
134 move |_| {
135 tracing::debug!("Terminating loader");
136 util::spawn_detached(process.use_().done(path))
137 }
138 ));
139
140 Ok(EditableImage {
141 editor: self,
142 image_editor: ImageEditor::External(ImageEditorExternal {
143 _active_sandbox_mechanism: editor.sandbox_mechanism,
144 process: editor.process,
145 editor_alive: Default::default(),
146 edit_request: editable_image.edit_request,
147 }),
148 _mime_type: editor.mime_type,
149 })
150 }
151 #[cfg(feature = "builtin")]
152 Processor::Builtin(builtin) => {
153 let mime_type = builtin.mime_type.to_string();
154 let details = glycin_utils::InitializationDetails::default();
155 let edit_function: Box<dyn FnOnce() -> _ + Send>;
156
157 let (reader, read_data_future) = builtin.source_transmission.spawn_builtin();
158
159 match builtin.builtin {
160 #[cfg(feature = "builtin-image-rs")]
161 config::BuiltinProcessor::ImageRs(_) => {
162 edit_function = Box::new(move || {
163 glycin_image_rs::ImgEditor::edit(reader, mime_type, details)
164 .map(|e| ImageEditorBuiltin::ImageRs(Arc::new(e)))
165 });
166 }
167 #[cfg(feature = "builtin-test")]
168 config::BuiltinProcessor::Test(_) => {
169 edit_function = Box::new(move || {
170 glycin_test::ImgEditor::edit(reader, mime_type, details)
171 .map(|e| ImageEditorBuiltin::Test(Arc::new(e)))
172 });
173 }
174 }
175
176 let editor_future = gio::spawn_blocking(move || {
177 edit_function().map_err(|err| Error::from(err.into_editor_error()))
178 })
179 .map(|x| x.map_err(|e| ErrorKind::panic(e).err()));
180
181 let editor = editor_future
182 .join_abort_on_error(read_data_future)
183 .await??;
184
185 Ok(EditableImage {
186 editor: self,
187 image_editor: ImageEditor::Builtin(editor),
188 _mime_type: builtin.mime_type,
189 })
190 }
191 }
192 }
193
194 pub fn sandbox_selector(&mut self, sandbox_selector: SandboxSelector) -> &mut Self {
198 self.sandbox_selector = sandbox_selector;
199 self
200 }
201
202 pub fn cancellable(&mut self, cancellable: impl IsA<gio::Cancellable>) -> &mut Self {
204 self.cancellable = cancellable.upcast();
205 self
206 }
207}
208
209#[derive(Debug)]
213pub struct EditableImage {
214 pub(crate) editor: Editor,
215 image_editor: ImageEditor,
216 _mime_type: MimeType,
218}
219
220impl Drop for EditableImage {
221 fn drop(&mut self) {
222 #[cfg(feature = "external")]
223 #[allow(irrefutable_let_patterns)]
224 if let ImageEditor::External(editor) = &self.image_editor {
225 editor.process.use_().done_background(self);
226 *editor.editor_alive.lock().unwrap() = Arc::new(());
227 util::spawn_detached(self.editor.pool.clone().clean_loaders());
228 }
229 }
230}
231
232impl EditableImage {
233 pub fn apply_sparse(
239 self,
240 operations: &Operations,
241 ) -> Pin<Box<dyn Future<Output = Result<SparseEdit, Error>> + Send>> {
242 let operations = operations.to_owned();
243 Box::pin(self.apply_sparse_internal(operations))
244 }
245
246 async fn apply_sparse_internal(self, operations: Operations) -> Result<SparseEdit, Error> {
247 match &self.image_editor {
248 #[cfg(feature = "external")]
249 ImageEditor::External(editor) => {
250 let process = editor.process.use_();
251
252 let mut editor_output = process
253 .editor_apply_sparse(&operations, &self)
254 .await
255 .err_context(&process)?;
256
257 editor_output.final_seal().await?;
258
259 SparseEdit::try_from(editor_output.into_fungible())
260 }
261 #[cfg(feature = "builtin")]
262 ImageEditor::Builtin(editor) => {
263 let editor_function: Box<dyn FnOnce() -> _ + Send>;
264
265 match editor {
266 #[cfg(feature = "builtin-image-rs")]
267 ImageEditorBuiltin::ImageRs(editor) => {
268 let editor = editor.clone();
269 editor_function = Box::new(move || editor.apply_sparse(operations));
270 }
271 #[cfg(feature = "builtin-test")]
272 ImageEditorBuiltin::Test(editor) => {
273 let editor = editor.clone();
274 editor_function = Box::new(move || editor.apply_sparse(operations));
275 }
276 }
277
278 let editor_output = gio::spawn_blocking(|| {
279 editor_function().map_err(|e| Error::from(e.into_editor_error()))
280 })
281 .await
282 .map_err(|e| ErrorKind::panic(e))??;
283
284 SparseEdit::try_from(editor_output)
285 }
286 }
287 }
288
289 pub fn apply_complete(
291 &self,
292 operations: &Operations,
293 ) -> Pin<Box<dyn Future<Output = Result<Edit, Error>> + Send + '_>> {
294 let operations = operations.to_owned();
295
296 Box::pin(self.apply_complete_internal(operations))
297 }
298
299 async fn apply_complete_internal(&self, operations: Operations) -> Result<Edit, Error> {
300 match &self.image_editor {
301 #[cfg(feature = "external")]
302 ImageEditor::External(editor) => {
303 let process = editor.process.use_();
304
305 let mut editor_output = process
306 .editor_apply_complete(&operations, self)
307 .await
308 .err_context(&process)?
309 .into_fungible();
310
311 editor_output.final_seal().await?;
312
313 Ok(Edit {
314 inner: editor_output,
315 })
316 }
317 #[cfg(feature = "builtin")]
318 ImageEditor::Builtin(editor) => {
319 let apply_function: Box<dyn FnOnce() -> _ + Send + 'static>;
320
321 match editor {
322 #[cfg(feature = "builtin-image-rs")]
323 ImageEditorBuiltin::ImageRs(editor) => {
324 let editor = editor.clone();
325 apply_function = Box::new(move || editor.apply_complete(operations));
326 }
327 #[cfg(feature = "builtin-test")]
328 ImageEditorBuiltin::Test(editor) => {
329 let editor = editor.clone();
330 apply_function = Box::new(move || editor.apply_complete(operations));
331 }
332 }
333
334 let editor_output = gio::spawn_blocking(|| {
335 apply_function().map_err(|e| Error::from(e.into_editor_error()))
336 })
337 .await
338 .map_err(|e| ErrorKind::panic(e))??;
339
340 Ok(Edit {
341 inner: editor_output,
342 })
343 }
344 }
345 }
346
347 pub async fn supported_formats() -> BTreeMap<MimeType, config::EditorConfig> {
349 let config = config::Config::cached().await;
350 config.image_editor.clone()
351 }
352
353 #[cfg(feature = "external")]
354 pub(crate) fn edit_request_path(&self) -> OwnedObjectPath {
355 #[allow(irrefutable_let_patterns)]
356 if let ImageEditor::External(editor) = &self.image_editor {
357 editor.edit_request.clone()
358 } else {
359 todo!()
360 }
361 }
362}
363
364#[derive(Debug)]
365enum ImageEditor {
366 #[cfg(feature = "external")]
367 External(ImageEditorExternal),
368 #[cfg(feature = "builtin")]
369 Builtin(ImageEditorBuiltin),
370}
371
372#[cfg(feature = "external")]
373#[derive(Debug)]
374struct ImageEditorExternal {
375 pub(crate) process: Arc<PooledProcess<EditorProxy<'static>>>,
376 edit_request: OwnedObjectPath,
377 _active_sandbox_mechanism: SandboxMechanism,
378 editor_alive: std::sync::Mutex<Arc<()>>,
379}
380
381#[cfg(feature = "builtin")]
382#[derive(Clone)]
383enum ImageEditorBuiltin {
384 #[cfg(feature = "builtin-image-rs")]
385 ImageRs(Arc<glycin_image_rs::ImgEditor>),
386 #[cfg(feature = "builtin-test")]
387 Test(Arc<glycin_test::ImgEditor>),
388}
389
390#[cfg(feature = "builtin")]
391impl std::fmt::Debug for ImageEditorBuiltin {
392 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
393 f.write_str("ImageEditorBuiltin")
394 }
395}
396
397#[derive(Debug)]
398pub enum SparseEdit {
402 Sparse(ByteChanges),
406 Complete(FungibleMemory),
408}
409
410#[derive(Debug)]
412pub struct Edit {
413 inner: CompleteEditorOutput<FungibleMemory>,
414}
415
416impl Edit {
417 pub fn data(&self) -> &[u8] {
418 &self.inner.data
419 }
420
421 pub fn is_lossless(&self) -> bool {
422 self.inner.info.lossless
423 }
424}
425
426#[derive(Debug, PartialEq, Eq)]
427#[must_use]
428pub enum EditOutcome {
430 Changed,
431 Unchanged,
432}
433
434impl SparseEdit {
435 pub async fn apply_to(&self, file: gio::File) -> Result<EditOutcome, Error> {
440 match self {
441 Self::Sparse(bit_changes) => {
442 let bit_changes = bit_changes.clone();
443 util::spawn_blocking(move || {
444 let stream = file.open_readwrite(gio::Cancellable::NONE)?;
445 let output_stream = stream.output_stream();
446 for change in bit_changes.changes {
447 stream.seek(
448 change.offset.try_i64()?,
449 glib::SeekType::Set,
450 gio::Cancellable::NONE,
451 )?;
452 let (_, err) =
453 output_stream.write_all(&[change.new_value], gio::Cancellable::NONE)?;
454
455 if let Some(err) = err {
456 return Err(err.into());
457 }
458 }
459 Ok(EditOutcome::Changed)
460 })
461 .await?
462 }
463 Self::Complete(_) => Ok(EditOutcome::Unchanged),
464 }
465 }
466}
467
468impl TryFrom<SparseEditorOutput<FungibleMemory>> for SparseEdit {
469 type Error = Error;
470
471 fn try_from(
472 value: SparseEditorOutput<FungibleMemory>,
473 ) -> std::result::Result<Self, Self::Error> {
474 if value.byte_changes.is_some() && value.data.is_some() {
475 Err(
476 ErrorKind::RemoteError(glycin_utils::RemoteError::InternalLoaderError(
477 "Sparse editor output with 'byte_changes' and 'data' returned.".into(),
478 ))
479 .into(),
480 )
481 } else if let Some(bit_changes) = value.byte_changes {
482 Ok(Self::Sparse(bit_changes))
483 } else if let Some(data) = value.data {
484 Ok(Self::Complete(data.into_fungible()))
485 } else {
486 Err(
487 ErrorKind::RemoteError(glycin_utils::RemoteError::InternalLoaderError(
488 "Sparse editor output with neither 'bit_changes' nor 'data' returned.".into(),
489 ))
490 .into(),
491 )
492 }
493 }
494}