/src/process.rs

https://github.com/Spotifyd/spotifyd · Rust · 169 lines · 138 code · 16 blank · 15 comment · 10 complexity · ac56db4a1739d5625d1122dbff673d97 MD5 · raw file

  1. use crate::error::Error;
  2. use librespot::playback::player::PlayerEvent;
  3. use log::info;
  4. use std::{
  5. collections::HashMap,
  6. io::{self, Read, Write},
  7. process::{Command, ExitStatus, Stdio},
  8. };
  9. /// Blocks while provided command is run in a subprocess using the provided
  10. /// shell. If successful, returns the contents of the subprocess's `stdout` as a
  11. /// `String`.
  12. pub(crate) fn run_program(shell: &str, cmd: &str) -> Result<String, Error> {
  13. info!("Running {:?} using {:?}", cmd, shell);
  14. let output = Command::new(shell)
  15. .arg("-c")
  16. .arg(cmd)
  17. .output()
  18. .map_err(|e| Error::subprocess_with_err(shell, cmd, e))?;
  19. if !output.status.success() {
  20. let s = std::str::from_utf8(&output.stderr).map_err(|_| Error::subprocess(shell, cmd))?;
  21. return Err(Error::subprocess_with_str(shell, cmd, s));
  22. }
  23. let s = String::from_utf8(output.stdout).map_err(|_| Error::subprocess(shell, cmd))?;
  24. Ok(s)
  25. }
  26. /// Spawns provided command in a subprocess using the provided shell.
  27. fn spawn_program(shell: &str, cmd: &str, env: HashMap<&str, String>) -> Result<Child, Error> {
  28. info!(
  29. "Running {:?} using {:?} with environment variables {:?}",
  30. cmd, shell, env
  31. );
  32. let inner = Command::new(shell)
  33. .arg("-c")
  34. .arg(cmd)
  35. .envs(env.iter())
  36. .stdin(Stdio::piped())
  37. .stdout(Stdio::piped())
  38. .stderr(Stdio::piped())
  39. .spawn()
  40. .map_err(|e| Error::subprocess_with_err(shell, cmd, e))?;
  41. let child = Child::new(cmd.to_string(), inner, shell.to_string());
  42. Ok(child)
  43. }
  44. /// Spawns provided command in a subprocess using the provided shell.
  45. /// Various environment variables are included in the subprocess's environment
  46. /// depending on the `PlayerEvent` that was passed in.
  47. pub(crate) fn spawn_program_on_event(
  48. shell: &str,
  49. cmd: &str,
  50. event: PlayerEvent,
  51. ) -> Result<Child, Error> {
  52. let mut env = HashMap::new();
  53. match event {
  54. PlayerEvent::Changed {
  55. old_track_id,
  56. new_track_id,
  57. } => {
  58. env.insert("OLD_TRACK_ID", old_track_id.to_base62());
  59. env.insert("PLAYER_EVENT", "change".to_string());
  60. env.insert("TRACK_ID", new_track_id.to_base62());
  61. }
  62. PlayerEvent::Started { track_id } => {
  63. env.insert("PLAYER_EVENT", "start".to_string());
  64. env.insert("TRACK_ID", track_id.to_base62());
  65. }
  66. PlayerEvent::Stopped { track_id } => {
  67. env.insert("PLAYER_EVENT", "stop".to_string());
  68. env.insert("TRACK_ID", track_id.to_base62());
  69. }
  70. }
  71. spawn_program(shell, cmd, env)
  72. }
  73. /// Same as a `std::process::Child` except when this `Child` exits:
  74. /// * successfully: It writes the contents of it's stdout to the stdout of the
  75. /// main process.
  76. /// * unsuccesfully: It returns an error that includes the contents it's stderr
  77. /// as well as information on the command that was run and the shell that
  78. /// invoked it.
  79. #[derive(Debug)]
  80. pub(crate) struct Child {
  81. cmd: String,
  82. inner: std::process::Child,
  83. shell: String,
  84. }
  85. impl Child {
  86. pub(crate) fn new(cmd: String, child: std::process::Child, shell: String) -> Self {
  87. Self {
  88. cmd,
  89. inner: child,
  90. shell,
  91. }
  92. }
  93. #[allow(unused)]
  94. pub(crate) fn wait(&mut self) -> Result<(), Error> {
  95. match self.inner.wait() {
  96. Ok(status) => {
  97. self.write_output(status)?;
  98. Ok(())
  99. }
  100. Err(e) => Err(Error::subprocess_with_err(&self.shell, &self.cmd, e)),
  101. }
  102. }
  103. #[allow(unused)]
  104. pub(crate) fn try_wait(&mut self) -> Result<Option<()>, Error> {
  105. match self.inner.try_wait() {
  106. Ok(Some(status)) => {
  107. self.write_output(status)?;
  108. Ok(Some(()))
  109. }
  110. Ok(None) => Ok(None),
  111. Err(e) => Err(Error::subprocess_with_err(&self.shell, &self.cmd, e)),
  112. }
  113. }
  114. fn write_output(&mut self, status: ExitStatus) -> Result<(), Error> {
  115. if status.success() {
  116. // If successful, write subprocess's stdout to main process's stdout...
  117. let mut stdout_of_child = self.inner.stdout.as_mut().unwrap();
  118. let reader = &mut stdout_of_child;
  119. let stdout_of_main = io::stdout();
  120. let mut guard = stdout_of_main.lock();
  121. let writer = &mut guard;
  122. io::copy(reader, writer)
  123. .map_err(|e| Error::subprocess_with_err(&self.shell, &self.cmd, e))?;
  124. writer
  125. .flush()
  126. .map_err(|e| Error::subprocess_with_err(&self.shell, &self.cmd, e))?;
  127. Ok(())
  128. } else {
  129. // If unsuccessful, return an error that includes the contents of stderr...
  130. let mut buf = String::new();
  131. match self.inner.stderr.as_mut().unwrap().read_to_string(&mut buf) {
  132. Ok(_nread) => Err(Error::subprocess_with_str(&self.shell, &self.cmd, &buf)),
  133. Err(e) => Err(Error::subprocess_with_err(&self.shell, &self.cmd, e)),
  134. }
  135. }
  136. }
  137. }
  138. impl std::ops::Deref for Child {
  139. type Target = std::process::Child;
  140. fn deref(&self) -> &Self::Target {
  141. &self.inner
  142. }
  143. }
  144. impl std::ops::DerefMut for Child {
  145. fn deref_mut(&mut self) -> &mut Self::Target {
  146. &mut self.inner
  147. }
  148. }
  149. impl From<Child> for std::process::Child {
  150. fn from(child: Child) -> Self {
  151. child.inner
  152. }
  153. }