You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

168 lines
5.7 KiB

  1. /*
  2. * meli - ui crate.
  3. *
  4. * Copyright 2017-2018 Manos Pitsidianakis
  5. *
  6. * This file is part of meli.
  7. *
  8. * meli is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * meli is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with meli. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. use super::*;
  22. use std::io::Write;
  23. use std::process::{Command, Stdio};
  24. #[derive(Debug)]
  25. pub struct HtmlView {
  26. pager: Pager,
  27. bytes: Vec<u8>,
  28. id: ComponentId,
  29. }
  30. impl HtmlView {
  31. pub fn new(body: &Attachment, context: &mut Context, account_pos: usize) -> Self {
  32. let id = ComponentId::new_v4();
  33. let bytes: Vec<u8> = decode_rec(body, None);
  34. let settings = context.accounts[account_pos].runtime_settings.conf();
  35. let mut display_text = if let Some(filter_invocation) = settings.html_filter() {
  36. let parts = split_command!(filter_invocation);
  37. let (cmd, args) = (parts[0], &parts[1..]);
  38. let command_obj = Command::new(cmd)
  39. .args(args)
  40. .stdin(Stdio::piped())
  41. .stdout(Stdio::piped())
  42. .spawn();
  43. if command_obj.is_err() {
  44. context.replies.push_back(UIEvent::Notification(
  45. Some(format!(
  46. "Failed to start html filter process: {}",
  47. filter_invocation
  48. )),
  49. String::new(),
  50. ));
  51. String::from_utf8_lossy(&bytes).to_string()
  52. } else {
  53. let mut html_filter = command_obj.unwrap();
  54. html_filter
  55. .stdin
  56. .as_mut()
  57. .unwrap()
  58. .write_all(&bytes)
  59. .expect("Failed to write to html filter stdin");
  60. let mut display_text = format!(
  61. "Text piped through `{}`. Press `v` to open in web browser. \n\n",
  62. filter_invocation
  63. );
  64. display_text.push_str(&String::from_utf8_lossy(
  65. &html_filter.wait_with_output().unwrap().stdout,
  66. ));
  67. display_text
  68. }
  69. } else if let Ok(mut html_filter) = Command::new("w3m")
  70. .args(&["-I", "utf-8", "-T", "text/html"])
  71. .stdin(Stdio::piped())
  72. .stdout(Stdio::piped())
  73. .spawn()
  74. {
  75. html_filter
  76. .stdin
  77. .as_mut()
  78. .unwrap()
  79. .write_all(&bytes)
  80. .expect("Failed to write to html filter stdin");
  81. let mut display_text =
  82. String::from("Text piped through `w3m`. Press `v` to open in web browser. \n\n");
  83. display_text.push_str(&String::from_utf8_lossy(
  84. &html_filter.wait_with_output().unwrap().stdout,
  85. ));
  86. display_text
  87. } else {
  88. context.replies.push_back(UIEvent::Notification(
  89. Some("Failed to find any application to use as html filter".to_string()),
  90. String::new(),
  91. ));
  92. String::from_utf8_lossy(&bytes).to_string()
  93. };
  94. if body.count_attachments() > 1 {
  95. display_text =
  96. body.attachments()
  97. .iter()
  98. .enumerate()
  99. .fold(display_text, |mut s, (idx, a)| {
  100. s.push_str(&format!("[{}] {}\n\n\n", idx, a));
  101. s
  102. });
  103. }
  104. let pager = Pager::from_string(display_text, None, None, None);
  105. HtmlView { pager, bytes, id }
  106. }
  107. }
  108. impl fmt::Display for HtmlView {
  109. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  110. // TODO display subject/info
  111. write!(f, "view")
  112. }
  113. }
  114. impl Component for HtmlView {
  115. fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
  116. self.pager.draw(grid, area, context);
  117. }
  118. fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
  119. if self.pager.process_event(event, context) {
  120. return true;
  121. }
  122. if let UIEvent::Input(Key::Char('v')) = event {
  123. // TODO: Optional filter that removes outgoing resource requests (images and
  124. // scripts)
  125. let binary = query_default_app("text/html");
  126. if let Ok(binary) = binary {
  127. let p = create_temp_file(&self.bytes, None);
  128. Command::new(&binary)
  129. .arg(p.path())
  130. .stdin(Stdio::piped())
  131. .stdout(Stdio::piped())
  132. .spawn()
  133. .unwrap_or_else(|_| panic!("Failed to start {}", binary.display()));
  134. context.temp_files.push(p);
  135. } else {
  136. context
  137. .replies
  138. .push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(
  139. "Couldn't find a default application for html files.".to_string(),
  140. )));
  141. }
  142. return true;
  143. }
  144. false
  145. }
  146. fn is_dirty(&self) -> bool {
  147. self.pager.is_dirty()
  148. }
  149. fn set_dirty(&mut self) {
  150. self.pager.set_dirty();
  151. }
  152. fn id(&self) -> ComponentId {
  153. self.id
  154. }
  155. fn set_id(&mut self, id: ComponentId) {
  156. self.id = id;
  157. }
  158. }