rust-chg: add callback to handle pager and shell command requests
authorYuya Nishihara <yuya@tcha.org>
Mon, 24 Sep 2018 18:18:35 +0900
changeset 39974 a9c5fc436fd5
parent 39973 ba447b83cd56
child 39975 571d8eb39095
rust-chg: add callback to handle pager and shell command requests This could be inlined into the ChgRunCommand state to be introduced by the next patch, but it seemed good to separate any user interactions from the IPC code.
rust/chg/src/lib.rs
rust/chg/src/uihandler.rs
--- a/rust/chg/src/lib.rs	Sat Sep 29 21:59:07 2018 +0900
+++ b/rust/chg/src/lib.rs	Mon Sep 24 18:18:35 2018 +0900
@@ -9,7 +9,11 @@
 extern crate libc;
 extern crate tokio;
 extern crate tokio_hglib;
+extern crate tokio_process;
 
 pub mod attachio;
 pub mod message;
 pub mod procutil;
+mod uihandler;
+
+pub use uihandler::{ChgUiHandler, SystemHandler};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/chg/src/uihandler.rs	Mon Sep 24 18:18:35 2018 +0900
@@ -0,0 +1,83 @@
+// Copyright 2018 Yuya Nishihara <yuya@tcha.org>
+//
+// This software may be used and distributed according to the terms of the
+// GNU General Public License version 2 or any later version.
+
+use futures::Future;
+use futures::future::IntoFuture;
+use std::io;
+use std::os::unix::io::AsRawFd;
+use std::os::unix::process::ExitStatusExt;
+use std::process::{Command, Stdio};
+use tokio;
+use tokio_process::{ChildStdin, CommandExt};
+
+use super::message::CommandSpec;
+use super::procutil;
+
+/// Callback to process shell command requests received from server.
+pub trait SystemHandler: Sized {
+    type PagerStdin: AsRawFd;
+    type SpawnPagerResult: IntoFuture<Item = (Self, Self::PagerStdin), Error = io::Error>;
+    type RunSystemResult: IntoFuture<Item = (Self, i32), Error = io::Error>;
+
+    /// Handles pager command request.
+    ///
+    /// Returns the pipe to be attached to the server if the pager is spawned.
+    fn spawn_pager(self, spec: CommandSpec) -> Self::SpawnPagerResult;
+
+    /// Handles system command request.
+    ///
+    /// Returns command exit code (positive) or signal number (negative).
+    fn run_system(self, spec: CommandSpec) -> Self::RunSystemResult;
+}
+
+/// Default cHg implementation to process requests received from server.
+pub struct ChgUiHandler {
+}
+
+impl ChgUiHandler {
+    pub fn new() -> ChgUiHandler {
+        ChgUiHandler {}
+    }
+}
+
+impl SystemHandler for ChgUiHandler {
+    type PagerStdin = ChildStdin;
+    type SpawnPagerResult = io::Result<(Self, Self::PagerStdin)>;
+    type RunSystemResult = Box<dyn Future<Item = (Self, i32), Error = io::Error> + Send>;
+
+    fn spawn_pager(self, spec: CommandSpec) -> Self::SpawnPagerResult {
+        let mut pager = new_shell_command(&spec)
+            .stdin(Stdio::piped())
+            .spawn_async()?;
+        let pin = pager.stdin().take().unwrap();
+        procutil::set_blocking_fd(pin.as_raw_fd())?;
+        tokio::spawn(pager.map(|_| ()).map_err(|_| ()));  // just ignore errors
+        Ok((self, pin))
+    }
+
+    fn run_system(self, spec: CommandSpec) -> Self::RunSystemResult {
+        let fut = new_shell_command(&spec)
+            .spawn_async()
+            .into_future()
+            .flatten()
+            .map(|status| {
+                let code = status.code().or_else(|| status.signal().map(|n| -n))
+                    .expect("either exit code or signal should be set");
+                (self, code)
+            });
+        Box::new(fut)
+    }
+}
+
+fn new_shell_command(spec: &CommandSpec) -> Command {
+    let mut builder = Command::new("/bin/sh");
+    builder
+        .arg("-c")
+        .arg(&spec.command)
+        .current_dir(&spec.current_dir)
+        .env_clear()
+        .envs(spec.envs.iter().cloned());
+    builder
+ }