use std::collections::BTreeMap;
use std::sync::Arc;
use gio::glib;
use gio::glib::clone::Downgrade;
use gio::prelude::{IsA, *};
pub use glycin_utils::operations::{Operation, Operations};
use glycin_utils::safe_math::SafeConversion;
use glycin_utils::{BinaryData, ByteChanges, SparseEditorOutput};
use crate::api_common::*;
use crate::error::ResultExt;
use crate::pool::Pool;
use crate::{config, util, Error, ErrorCtx, MimeType};
#[derive(Debug)]
pub struct Editor {
source: Source,
pool: Arc<Pool>,
cancellable: gio::Cancellable,
pub(crate) sandbox_selector: SandboxSelector,
}
static_assertions::assert_impl_all!(Editor: Send, Sync);
impl Editor {
pub fn new(file: gio::File) -> Self {
Self {
source: Source::File(file),
pool: Pool::global(),
cancellable: gio::Cancellable::new(),
sandbox_selector: SandboxSelector::default(),
}
}
pub fn sandbox_selector(&mut self, sandbox_selector: SandboxSelector) -> &mut Self {
self.sandbox_selector = sandbox_selector;
self
}
pub fn cancellable(&mut self, cancellable: impl IsA<gio::Cancellable>) -> &mut Self {
self.cancellable = cancellable.upcast();
self
}
pub async fn apply_sparse(mut self, operations: Operations) -> Result<SparseEdit, ErrorCtx> {
let source = self.source.send();
let process_context = spin_up_editor(
source,
&self.pool,
&self.cancellable,
&self.sandbox_selector,
Arc::new(()).downgrade(),
)
.await
.err_no_context(&self.cancellable)?;
let process = process_context.process.use_();
let editor_output = process
.editor_apply_sparse(
&process_context.g_file_worker,
&operations,
&process_context.mime_type,
)
.await
.err_context(&process, &self.cancellable)?;
SparseEdit::try_from(editor_output).err_no_context(&self.cancellable)
}
pub async fn apply_complete(self, operations: &Operations) -> Result<BinaryData, ErrorCtx> {
let editor_output = self.apply_complete_full(operations).await?;
Ok(editor_output.data)
}
pub async fn apply_complete_full(mut self, operations: &Operations) -> Result<Edit, ErrorCtx> {
let source = self.source.send();
let process_context = spin_up_editor(
source,
&self.pool,
&self.cancellable,
&self.sandbox_selector,
Arc::new(()).downgrade(),
)
.await
.err_no_context(&self.cancellable)?;
let process = process_context.process.use_();
let editor_output = process
.editor_apply_complete(
&process_context.g_file_worker,
operations,
&process_context.mime_type,
)
.await
.err_context(&process, &self.cancellable)?;
Ok(Edit {
data: editor_output.data,
info: editor_output.info,
})
}
pub async fn supported_formats() -> BTreeMap<MimeType, config::ImageEditorConfig> {
let config = config::Config::cached().await;
config.image_editor.clone()
}
}
#[derive(Debug)]
pub enum SparseEdit {
Sparse(ByteChanges),
Complete(BinaryData),
}
#[derive(Debug)]
pub struct Edit {
data: BinaryData,
info: glycin_utils::EditorOutputInfo,
}
impl Edit {
pub fn data(&self) -> BinaryData {
self.data.clone()
}
pub fn is_lossless(&self) -> bool {
self.info.lossless
}
}
#[derive(Debug, PartialEq, Eq)]
#[must_use]
pub enum EditOutcome {
Changed,
Unchanged,
}
impl SparseEdit {
pub async fn apply_to(&self, file: gio::File) -> Result<EditOutcome, Error> {
match self {
Self::Sparse(bit_changes) => {
let bit_changes = bit_changes.clone();
util::spawn_blocking(move || {
let stream = file.open_readwrite(gio::Cancellable::NONE)?;
let output_stream = stream.output_stream();
for change in bit_changes.changes {
stream.seek(
change.offset.try_i64()?,
glib::SeekType::Set,
gio::Cancellable::NONE,
)?;
let (_, err) =
output_stream.write_all(&[change.new_value], gio::Cancellable::NONE)?;
if let Some(err) = err {
return Err(err.into());
}
}
Ok(EditOutcome::Changed)
})
.await
}
Self::Complete(_) => Ok(EditOutcome::Unchanged),
}
}
}
impl TryFrom<SparseEditorOutput> for SparseEdit {
type Error = Error;
fn try_from(value: SparseEditorOutput) -> std::result::Result<Self, Self::Error> {
if value.byte_changes.is_some() && value.data.is_some() {
Err(Error::RemoteError(
glycin_utils::RemoteError::InternalLoaderError(
"Sparse editor output with 'byte_changes' and 'data' returned.".into(),
),
))
} else if let Some(bit_changes) = value.byte_changes {
Ok(Self::Sparse(bit_changes))
} else if let Some(data) = value.data {
Ok(Self::Complete(data))
} else {
Err(Error::RemoteError(
glycin_utils::RemoteError::InternalLoaderError(
"Sparse editor output with neither 'bit_changes' nor 'data' returned.".into(),
),
))
}
}
}