Skip to content

Commit

Permalink
Refactor arg parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
MaeIsBad committed Sep 24, 2024
1 parent 8936d4a commit ebd21ff
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 54 deletions.
24 changes: 23 additions & 1 deletion src/args.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
use clap::Parser;
use std::path::PathBuf;
use url::Url;

#[derive(Clone, Parser)]
pub struct Args {
pub input: String,
#[arg(
name = "file",
short,
long,
required_unless_present = "url",
conflicts_with = "url",
help = "file to convert"
)]
/// Local file to convert.
/// Mutually exclusive with `--url`
pub input_file: Option<PathBuf>,

#[arg(name = "url", short, long, help = "url to convert")]
/// Url to convert.
/// This option is slightly broken, and fails to print if a website ever redirects.
/// Mutually exclusive with `--file`
///
/// Security note: no validation is performed on these urls, they can be used to convert local
/// files, with the `file://` protocol, perform SSRF or use whatever protocols gio happens to
/// support. You are responsible for sanitizing them to prevent these issues
pub input_url: Option<Url>,

#[arg(default_value = "output.pdf")]
pub output_file: PathBuf,
}
62 changes: 46 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,76 @@
pub mod args;
mod printer;
mod utils;
mod webview;
use anyhow::{bail, Context, Result};
use args::Args;
use glib_macros::clone;
use gtk4::{prelude::*, Application, ApplicationWindow};
use gtk4::{glib::ExitCode, prelude::*, Application, ApplicationWindow};
use url::Url;
use utils::runtime_oneshot;
use webkit6::glib;

pub fn print(args: Args) -> glib::ExitCode {
async fn do_print(args: Args, window: ApplicationWindow) -> Result<()> {
let uri = match (args.input_url, args.input_file) {
(Some(url), None) => url,
(None, Some(file)) => {
let file = std::path::absolute(file).context("Failed to resolve path to input file")?;
// Unwrapping here is fine since we resolved the path to be absolute
Url::from_file_path(file).unwrap()
}
// The argument parsing logic should prevent this from happening
_ => unreachable!(),
};
let webview_cfg = webview::WebviewConfig {
uri: uri.to_string(),
};
let webview = webview_cfg.run(&window).await?;

printer::PrintConfig::new(args.output_file.clone())
.print(&webview)
.await?;

Ok(())
}

pub fn print(args: Args) -> Result<()> {
let app = Application::new(Some("com.helloprima.webkit-pdf-inator"), Default::default());
let (s, mut r) = runtime_oneshot();

app.connect_activate(clone!(
#[strong]
args,
#[strong]
s,
move |app| {
let window = ApplicationWindow::new(app);

let path = std::path::absolute(&args.input).unwrap();
let uri = Url::from_file_path(&path).unwrap().to_string();
glib::spawn_future_local(clone!(
#[strong]
args,
#[weak]
window,
#[weak]
app,
#[weak]
window,
#[strong]
s,
async move {
let webview_cfg = webview::WebviewConfig { uri };
let webview = webview_cfg.run(&window).await.unwrap();

printer::PrintConfig::new(args.output_file.clone())
.print(&webview)
.await
.unwrap();
let res = do_print(args, window).await;
s.send(res).unwrap();

app.quit();
app.quit()
}
));
}
));

// Use run_with_args here, since we rely on clap to do our arg parsing
app.run_with_args::<&str>(&[])
let exit_code = app.run_with_args::<&str>(&[]);
let res = r.try_recv().ok().flatten();
match (exit_code, res) {
(ExitCode::SUCCESS, Some(res)) => res,
(_, None) => bail!("Printing operation didn't return result"),
(_, _) => bail!("GTK app returned an exit code indicating failure"),
}
}

#[cfg(test)]
Expand Down
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use anyhow::Result;
use clap::Parser;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
use webkit6::glib;
use webkit_pdf_inator::args::Args;

fn main() -> glib::ExitCode {
fn main() -> Result<()> {
tracing_subscriber::registry()
.with(fmt::layer())
.with(EnvFilter::from_default_env())
Expand Down
45 changes: 29 additions & 16 deletions src/printer.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use anyhow::Result;
use futures::channel::oneshot;
use gtk4::PrintSettings;
use std::cell::Cell;
use anyhow::{Context, Result};
use glib_macros::clone;
use gtk4::{prelude::ObjectExt, PrintSettings};
use std::path::PathBuf;
use url::Url;
use webkit6::{PrintOperation, WebView};

use crate::utils;

pub struct PrintConfig {
output_file: PathBuf,
}
Expand All @@ -25,19 +26,31 @@ impl PrintConfig {
settings.set(gtk4::PRINT_SETTINGS_OUTPUT_URI, Some(output_uri.as_str()));
print_op.set_print_settings(&settings);

let (s, r) = oneshot::channel();
let s: Cell<Option<oneshot::Sender<()>>> = Cell::new(Some(s));
print_op.connect_finished(move |_| {
if let Some(s) = s.take() {
s.send(()).unwrap();
} else {
tracing::warn!(
"print operation connect_finished called multiple times. This shouldn't happen"
);
};
});
let (s, r) = utils::runtime_oneshot();
let failed_signal = print_op.connect_failed(clone!(
#[strong]
s,
move |_, err| {
let err = Err(err.clone());
let err = err.context("Printing operation failed");
s.send(err).unwrap();
}
));

let finished_signal = print_op.connect_finished(clone!(
#[strong]
s,
move |_| {
s.send(Ok(())).unwrap();
}
));

print_op.print();
Ok(r.await?)
let res = r.await;

print_op.disconnect(failed_signal);
print_op.disconnect(finished_signal);

res?
}
}
33 changes: 33 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use futures::channel::oneshot;
use std::cell::Cell;
use std::rc::Rc;

pub struct RuntimeOneshotSender<T>(Rc<Cell<Option<oneshot::Sender<T>>>>);

impl<T> Clone for RuntimeOneshotSender<T> {
fn clone(&self) -> Self {
Self(Rc::clone(&self.0))
}
}

impl<T> RuntimeOneshotSender<T> {
fn new(s: oneshot::Sender<T>) -> Self {
Self(Rc::new(Cell::new(Some(s))))
}

pub fn send(&self, value: T) -> Result<(), T> {
if let Some(s) = self.0.take() {
s.send(value)
} else {
tracing::warn!("Runtime oneshot::send called multiple times.");
Err(value)
}
}
}

pub fn runtime_oneshot<T>() -> (RuntimeOneshotSender<T>, oneshot::Receiver<T>) {
let (s, r) = oneshot::channel();
let s = RuntimeOneshotSender::new(s);

(s, r)
}
47 changes: 28 additions & 19 deletions src/webview.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use anyhow::Result;
use futures::channel::oneshot::{self, Sender};
use anyhow::{Context, Result};
use glib_macros::clone;
use gtk4::{prelude::*, ApplicationWindow};
use std::cell::Cell;
use webkit6::{prelude::*, LoadEvent, WebView};

use crate::utils::runtime_oneshot;

pub struct WebviewConfig {
pub uri: String,
}
Expand All @@ -14,26 +15,34 @@ impl WebviewConfig {
webview.load_uri(&self.uri);

window.set_child(Some(&webview));
let (s, r) = oneshot::channel();
let (s, r) = runtime_oneshot::<Result<()>>();
let handle = webview.connect_load_changed(clone!(
#[strong]
s,
move |_webview, event| {
if event != LoadEvent::Finished {
return;
}

let s: Cell<Option<Sender<()>>> = Cell::new(Some(s));
let handle = webview.connect_load_changed(move |_webview, event| {
if event != LoadEvent::Finished {
return;
// Confirm that load finished.
// If this gets called multiple times
// (which it shouldn't, since we always disconnect the handle immiedately)
// ignore
s.send(Ok(())).ok();
}
));

// Confirm that load finished.
// If this gets called multiple times
// (which it shouldn't, since we always disconnect the handle immiedately)
// ignore
if let Some(s) = s.take() {
s.send(()).unwrap();
} else {
tracing::warn!("connect_load_changed called multiple times. This shouldn't happen");
};
});
webview.connect_load_failed(clone!(
#[strong]
s,
move |_, _, url, err| {
let err = Err(err.clone()).context(format!("While loading {url}"));
s.send(err).unwrap();
false
}
));

r.await?;
r.await??;
webview.disconnect(handle);
Ok(webview)
}
Expand Down

0 comments on commit ebd21ff

Please sign in to comment.