use std::path::PathBuf;
use std::sync::Arc;
#[cfg(feature = "gobject")]
use gio::glib;
use gio::prelude::*;
use crate::config::{Config, ImageEditorConfig, ImageLoaderConfig};
use crate::dbus::{EditorProxy, GFileWorker, LoaderProxy, ZbusProxy};
use crate::pool::{Pool, PooledProcess, UsageTracker};
use crate::util::RunEnvironment;
use crate::{Error, MimeType, config};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SandboxMechanism {
Bwrap,
FlatpakSpawn,
NotSandboxed,
}
impl SandboxMechanism {
pub async fn detect() -> Self {
match RunEnvironment::cached().await {
RunEnvironment::FlatpakDevel => Self::NotSandboxed,
RunEnvironment::Flatpak => Self::FlatpakSpawn,
RunEnvironment::Host => Self::Bwrap,
RunEnvironment::HostBwrapSyscallsBlocked => Self::NotSandboxed,
}
}
pub fn into_selector(self) -> SandboxSelector {
match self {
Self::Bwrap => SandboxSelector::Bwrap,
Self::FlatpakSpawn => SandboxSelector::FlatpakSpawn,
Self::NotSandboxed => SandboxSelector::NotSandboxed,
}
}
}
#[derive(Debug, Copy, Clone, Default)]
#[cfg_attr(feature = "gobject", derive(gio::glib::Enum))]
#[cfg_attr(feature = "gobject", enum_type(name = "GlySandboxSelector"))]
#[repr(i32)]
pub enum SandboxSelector {
#[default]
Auto,
Bwrap,
FlatpakSpawn,
NotSandboxed,
}
impl SandboxSelector {
pub async fn determine_sandbox_mechanism(self) -> SandboxMechanism {
match self {
Self::Auto => SandboxMechanism::detect().await,
Self::Bwrap => SandboxMechanism::Bwrap,
Self::FlatpakSpawn => SandboxMechanism::FlatpakSpawn,
Self::NotSandboxed => SandboxMechanism::NotSandboxed,
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ColorState {
Srgb,
Cicp(crate::Cicp),
}
pub(crate) struct RemoteProcessContext<P: ZbusProxy<'static> + 'static> {
pub process: Arc<PooledProcess<P>>,
pub g_file_worker: Option<GFileWorker>,
pub mime_type: MimeType,
pub sandbox_mechanism: SandboxMechanism,
pub usage_tracker: Arc<UsageTracker>,
}
#[derive(Debug, Clone)]
pub(crate) struct GInputStreamSend(gio::InputStream);
unsafe impl Send for GInputStreamSend {}
unsafe impl Sync for GInputStreamSend {}
impl GInputStreamSend {
pub(crate) unsafe fn new(stream: gio::InputStream) -> Self {
Self(stream)
}
#[cfg(feature = "gobject")]
pub(crate) fn stream(&self) -> gio::InputStream {
self.0.clone()
}
}
#[derive(Debug, Clone)]
pub(crate) enum Source {
File(gio::File),
Stream(GInputStreamSend),
TransferredStream,
}
impl Source {
pub fn file(&self) -> Option<gio::File> {
match self {
Self::File(file) => Some(file.clone()),
_ => None,
}
}
pub fn to_stream(&self, cancellable: &gio::Cancellable) -> Result<gio::InputStream, Error> {
match self {
Self::File(file) => file
.read(Some(cancellable))
.map(|x| x.upcast())
.map_err(Error::ImageSource),
Self::Stream(stream) => Ok(stream.0.clone()),
Self::TransferredStream => Err(Error::TransferredStream),
}
}
pub fn send(&mut self) -> Self {
let new = self
.file()
.map(Self::File)
.unwrap_or(Self::TransferredStream);
std::mem::replace(self, new)
}
}
#[derive(Debug)]
pub(crate) struct ProcessBasics<T> {
pub mime_type: MimeType,
pub sandbox_mechanism: SandboxMechanism,
pub config_entry: T,
pub g_file_worker: Option<GFileWorker>,
pub base_dir: Option<PathBuf>,
}
pub trait GetConfig {
fn config_entry<'a>(config: &'a Config, mime_type: &'a MimeType) -> Result<&'a Self, Error>;
fn expose_base_dir(&self) -> bool;
}
impl GetConfig for ImageLoaderConfig {
fn config_entry<'a>(
config: &'a Config,
mime_type: &'a MimeType,
) -> Result<&'a ImageLoaderConfig, Error> {
config.loader(mime_type)
}
fn expose_base_dir(&self) -> bool {
self.expose_base_dir
}
}
impl GetConfig for ImageEditorConfig {
fn config_entry<'a>(
config: &'a Config,
mime_type: &'a MimeType,
) -> Result<&'a ImageEditorConfig, Error> {
config.editor(mime_type)
}
fn expose_base_dir(&self) -> bool {
self.expose_base_dir
}
}
pub(crate) async fn spin_up<T: GetConfig + Clone>(
source: Source,
use_expose_base_dir: bool,
cancellable: &gio::Cancellable,
sandbox_selector: &SandboxSelector,
) -> Result<ProcessBasics<T>, Error> {
let file = source.file();
let g_file_worker: GFileWorker = GFileWorker::spawn(source, cancellable.clone());
let mime_type = guess_mime_type(&g_file_worker).await?;
let config = config::Config::cached().await;
let config_entry = T::config_entry(&config, &mime_type)?.clone().clone();
let base_dir = if use_expose_base_dir && config_entry.expose_base_dir() {
file.and_then(|x| x.parent()).and_then(|x| x.path())
} else {
None
};
let sandbox_mechanism = sandbox_selector.determine_sandbox_mechanism().await;
Ok(ProcessBasics {
config_entry,
base_dir,
mime_type,
sandbox_mechanism,
g_file_worker: Some(g_file_worker),
})
}
pub(crate) async fn spin_up_editor<'a>(
source: Source,
pool: Arc<Pool>,
cancellable: &gio::Cancellable,
sandbox_selector: &SandboxSelector,
) -> Result<RemoteProcessContext<EditorProxy<'static>>, Error> {
let process_basics =
spin_up::<ImageEditorConfig>(source, false, cancellable, sandbox_selector).await?;
let (process, usage_tracker) = pool
.get_editor(
process_basics.config_entry,
process_basics.sandbox_mechanism,
process_basics.base_dir,
cancellable,
)
.await?;
Ok(RemoteProcessContext {
process,
g_file_worker: process_basics.g_file_worker,
mime_type: process_basics.mime_type,
sandbox_mechanism: process_basics.sandbox_mechanism,
usage_tracker,
})
}
pub(crate) async fn spin_up_encoder<'a>(
mime_type: MimeType,
pool: Arc<Pool>,
cancellable: &gio::Cancellable,
sandbox_selector: &SandboxSelector,
) -> Result<RemoteProcessContext<EditorProxy<'static>>, Error> {
let config_entry = Config::cached().await.editor(&mime_type)?.clone();
let sandbox_mechanism = sandbox_selector.determine_sandbox_mechanism().await;
let (process, usage_tracker) = pool
.get_editor(config_entry, sandbox_mechanism, None, cancellable)
.await?;
Ok(RemoteProcessContext {
process,
g_file_worker: None,
mime_type,
sandbox_mechanism,
usage_tracker,
})
}
pub(crate) async fn spin_up_loader<'a>(
source: Source,
use_expose_base_dir: bool,
pool: Arc<Pool>,
cancellable: &gio::Cancellable,
sandbox_selector: &SandboxSelector,
) -> Result<RemoteProcessContext<LoaderProxy<'static>>, Error> {
let process_basics =
spin_up(source, use_expose_base_dir, cancellable, sandbox_selector).await?;
let (process, usage_tracker) = pool
.clone()
.get_loader(
process_basics.config_entry,
process_basics.sandbox_mechanism,
process_basics.base_dir,
cancellable,
)
.await?;
Ok(RemoteProcessContext {
process,
usage_tracker,
g_file_worker: process_basics.g_file_worker,
mime_type: process_basics.mime_type,
sandbox_mechanism: process_basics.sandbox_mechanism,
})
}
pub(crate) async fn guess_mime_type(gfile_worker: &GFileWorker) -> Result<MimeType, Error> {
let head = gfile_worker.head().await?;
let (content_type, unsure) = gio::content_type_guess(None::<String>, head.as_slice());
let mime_type = gio::content_type_get_mime_type(&content_type)
.ok_or_else(|| Error::UnknownContentType(content_type.to_string()));
let is_tiff = mime_type.clone().ok() == Some("image/tiff".into());
let is_xml = mime_type.clone().ok() == Some("application/xml".into());
let is_gzip = mime_type.clone().ok() == Some("application/gzip".into());
let is_text = mime_type.clone().ok() == Some("text/plain".into());
if (unsure || is_tiff || is_xml || is_gzip || is_text)
&& let Some(filename) = gfile_worker.file().and_then(|x| x.basename())
{
let content_type_fn = gio::content_type_guess(Some(filename), head.as_slice()).0;
return gio::content_type_get_mime_type(&content_type_fn)
.ok_or_else(|| Error::UnknownContentType(content_type_fn.to_string()))
.map(|x| MimeType::new(x.to_string()));
}
mime_type.map(|x| MimeType::new(x.to_string()))
}