Patchwork [08,of,13] rust-chg: add callback to handle pager and shell command requests

login
register
mail settings
Submitter Yuya Nishihara
Date Oct. 2, 2018, 2:25 p.m.
Message ID <05f387db9f9290ce464e.1538490347@mimosa>
Download mbox | patch
Permalink /patch/35315/
State Accepted
Headers show

Comments

Yuya Nishihara - Oct. 2, 2018, 2:25 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1537780715 -32400
#      Mon Sep 24 18:18:35 2018 +0900
# Node ID 05f387db9f9290ce464e2f80902b83766938e740
# Parent  caec87128374791fb7a9d1fa8a43d115a8e899ef
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.

Patch

diff --git a/rust/chg/src/lib.rs b/rust/chg/src/lib.rs
--- a/rust/chg/src/lib.rs
+++ b/rust/chg/src/lib.rs
@@ -9,7 +9,11 @@  extern crate futures;
 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};
diff --git a/rust/chg/src/uihandler.rs b/rust/chg/src/uihandler.rs
new file mode 100644
--- /dev/null
+++ b/rust/chg/src/uihandler.rs
@@ -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
+ }