diff options
author | Jiayi Zhao <jeff.no.zhao@gmail.com> | 2020-03-20 13:08:26 -0400 |
---|---|---|
committer | Jiayi Zhao <jeff.no.zhao@gmail.com> | 2020-03-20 14:00:05 -0400 |
commit | 8fc59f0a0c3729d201f844738db5275035f59f2d (patch) | |
tree | 6deeba8db7275387f44b5bdb03c1f801efe437ab | |
parent | 84e912b7779faaaeb09d1085e09630a548c7fb54 (diff) |
add initial support for detecting mimetypes
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/commands/open_file.rs | 96 | ||||
-rw-r--r-- | src/config/mimetype.rs | 4 | ||||
-rw-r--r-- | src/fs/metadata.rs | 33 | ||||
-rw-r--r-- | src/ui/widgets/tui_footer.rs | 10 |
5 files changed, 114 insertions, 32 deletions
@@ -45,4 +45,5 @@ branch="dev" # path = "lib/wordexp-rs" [features] -default = [] +file_mimetype = [] +default = [ "file_mimetype" ] diff --git a/src/commands/open_file.rs b/src/commands/open_file.rs index 5d1e7de..66c514c 100644 --- a/src/commands/open_file.rs +++ b/src/commands/open_file.rs @@ -4,6 +4,7 @@ use crate::commands::{ChangeDirectory, JoshutoCommand, JoshutoRunnable}; use crate::config::mimetype::JoshutoMimetypeEntry; use crate::context::JoshutoContext; use crate::error::{JoshutoError, JoshutoErrorKind, JoshutoResult}; +use crate::fs::{JoshutoDirEntry, JoshutoMetadata}; use crate::ui::widgets::{TuiMenu, TuiTextField}; use crate::ui::TuiBackend; use crate::util::load_child::LoadChild; @@ -21,45 +22,70 @@ impl OpenFile { "open_file" } - pub fn get_options<'a>(path: &Path) -> Vec<&'a JoshutoMimetypeEntry> { + pub fn get_options<'a>(entry: &JoshutoDirEntry) -> Vec<&'a JoshutoMimetypeEntry> { let mut mimetype_options: Vec<&JoshutoMimetypeEntry> = Vec::new(); /* extensions have priority */ - if let Some(file_ext) = path.extension() { + if let Some(file_ext) = entry.file_path().extension() { if let Some(file_ext) = file_ext.to_str() { let ext_entries = MIMETYPE_T.get_entries_for_ext(file_ext); mimetype_options.extend(ext_entries); } } + #[cfg(feature = "file_mimetype")] + { + if let Some(mimetype) = entry.metadata.mimetype.as_ref() { + let mime_entries = MIMETYPE_T.get_entries_for_mimetype(mimetype.as_str()); + mimetype_options.extend(mime_entries); + } + } mimetype_options } fn open(context: &mut JoshutoContext, backend: &mut TuiBackend) -> std::io::Result<()> { let mut dirpath = None; - let mut filepaths = None; - - if let Some(curr_list) = context.tabs[context.curr_tab_index].curr_list_ref() { - if let Some(index) = curr_list.index { - let child_path = curr_list.contents[index].file_path(); - if child_path.is_dir() { - dirpath = Some(child_path.clone()); - } else { - filepaths = Some(curr_list.get_selected_paths()); - } + let mut selected_entries = None; + + { + let curr_tab = context.curr_tab_ref(); + match curr_tab.curr_list_ref() { + None => return Ok(()), + Some(curr_list) => match curr_list.get_curr_ref() { + Some(entry) if entry.file_path().is_dir() => { + let path = entry.file_path().clone(); + dirpath = Some(path); + } + Some(entry) => { + let vec: Vec<&JoshutoDirEntry> = curr_list.selected_entries().collect(); + if vec.is_empty() { + selected_entries = Some(vec![entry]); + } else { + selected_entries = Some(vec); + } + } + None => return Ok(()), + }, } } + if let Some(path) = dirpath { ChangeDirectory::cd(path.as_path(), context)?; LoadChild::load_child(context)?; - } else if let Some(paths) = filepaths { - let options = Self::get_options(paths[0]); + } else if let Some(entries) = selected_entries { + let options = Self::get_options(entries[0]); + let entry_paths: Vec<&Path> = entries.iter().map(|e| e.file_path().as_path()).collect(); if !options.is_empty() { - backend.terminal_drop(); - let res = options[0].execute_with(&paths); - backend.terminal_restore()?; + let res = if options[0].get_fork() { + backend.terminal_drop(); + let res = options[0].execute_with(entry_paths.as_slice()); + backend.terminal_restore()?; + res + } else { + options[0].execute_with(entry_paths.as_slice()) + }; return res; } else { - OpenFileWith::open_with(context, backend, &paths)?; + OpenFileWith::open_with(context, backend, &entries)?; } } Ok(()) @@ -95,11 +121,11 @@ impl OpenFileWith { pub fn open_with( context: &JoshutoContext, backend: &mut TuiBackend, - paths: &[&PathBuf], + entries: &[&JoshutoDirEntry], ) -> std::io::Result<()> { const PROMPT: &str = "open_with "; - let mimetype_options: Vec<&JoshutoMimetypeEntry> = OpenFile::get_options(&paths[0]); + let mimetype_options: Vec<&JoshutoMimetypeEntry> = OpenFile::get_options(&entries[0]); let user_input: Option<String> = { let menu_options: Vec<String> = mimetype_options @@ -116,6 +142,7 @@ impl OpenFileWith { .menu(&mut menu_widget); textfield.get_input(backend, &context) }; + let entry_paths: Vec<&Path> = entries.iter().map(|e| e.file_path().as_path()).collect(); match user_input.as_ref() { Some(user_input) if user_input.starts_with(PROMPT) => { @@ -129,10 +156,10 @@ impl OpenFileWith { Ok(n) => { let mimetype_entry = &mimetype_options[n]; if mimetype_entry.get_fork() { - mimetype_entry.execute_with(paths) + mimetype_entry.execute_with(entry_paths.as_slice()) } else { backend.terminal_drop(); - let res = mimetype_entry.execute_with(paths); + let res = mimetype_entry.execute_with(entry_paths.as_slice()); backend.terminal_restore()?; res } @@ -144,7 +171,7 @@ impl OpenFileWith { backend.terminal_drop(); let res = JoshutoMimetypeEntry::new(String::from(cmd)) .args(args_iter) - .execute_with(paths); + .execute_with(entry_paths.as_slice()); backend.terminal_restore()?; res } @@ -168,18 +195,31 @@ impl std::fmt::Display for OpenFileWith { impl JoshutoRunnable for OpenFileWith { fn execute(&self, context: &mut JoshutoContext, backend: &mut TuiBackend) -> JoshutoResult<()> { - let paths = match &context.tabs[context.curr_tab_index].curr_list_ref() { - Some(curr_list) => curr_list.get_selected_paths(), - None => vec![], + let selected_entries = { + let curr_tab = context.curr_tab_ref(); + match curr_tab.curr_list_ref() { + None => vec![], + Some(curr_list) => match curr_list.get_curr_ref() { + Some(entry) => { + let vec: Vec<&JoshutoDirEntry> = curr_list.selected_entries().collect(); + if vec.is_empty() { + vec![entry] + } else { + vec + } + } + None => vec![], + }, + } }; - if paths.is_empty() { + if selected_entries.is_empty() { return Err(JoshutoError::new( JoshutoErrorKind::IONotFound, String::from("No files selected"), )); } - Self::open_with(context, backend, &paths)?; + Self::open_with(context, backend, &selected_entries)?; Ok(()) } } diff --git a/src/config/mimetype.rs b/src/config/mimetype.rs index f8c4f69..e6103dd 100644 --- a/src/config/mimetype.rs +++ b/src/config/mimetype.rs @@ -2,7 +2,7 @@ use serde_derive::Deserialize; use std::collections::HashMap; use std::fmt; use std::io::Read; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process; use super::{parse_config_file, ConfigStructure}; @@ -82,7 +82,7 @@ impl JoshutoMimetypeEntry { self._confirm_exit } - pub fn execute_with(&self, paths: &[&PathBuf]) -> std::io::Result<()> { + pub fn execute_with(&self, paths: &[&Path]) -> std::io::Result<()> { let program = String::from(self.get_command()); let mut command = process::Command::new(program); diff --git a/src/fs/metadata.rs b/src/fs/metadata.rs index e1e55e8..9f3ddd9 100644 --- a/src/fs/metadata.rs +++ b/src/fs/metadata.rs @@ -1,4 +1,4 @@ -use std::{fs, path, time}; +use std::{fs, path, process, time}; #[derive(Clone, Debug)] pub struct JoshutoMetadata { @@ -6,6 +6,7 @@ pub struct JoshutoMetadata { pub modified: time::SystemTime, pub permissions: fs::Permissions, pub file_type: fs::FileType, + pub mimetype: Option<String>, #[cfg(unix)] pub uid: u32, #[cfg(unix)] @@ -25,6 +26,14 @@ impl JoshutoMetadata { let modified = metadata.modified()?; let permissions = metadata.permissions(); let file_type = metadata.file_type(); + let mut mimetype = None; + + if file_type.is_file() { + #[cfg(feature = "file_mimetype")] + { + mimetype = file_mimetype(path) + } + } #[cfg(unix)] let uid = metadata.uid(); @@ -38,6 +47,7 @@ impl JoshutoMetadata { modified, permissions, file_type, + mimetype, #[cfg(unix)] uid, #[cfg(unix)] @@ -47,3 +57,24 @@ impl JoshutoMetadata { }) } } + +fn file_mimetype(path: &path::Path) -> Option<String> { + let output = process::Command::new("file") + .args(&["-Lb", "--mime-type"]) + .arg(path) + .output(); + + match output { + Ok(s) => { + if s.status.success() { + match String::from_utf8(s.stdout) { + Ok(s) => Some(s), + Err(_) => None, + } + } else { + None + } + } + Err(_) => None, + } +} diff --git a/src/ui/widgets/tui_footer.rs b/src/ui/widgets/tui_footer.rs index 3e044f4..072b143 100644 --- a/src/ui/widgets/tui_footer.rs +++ b/src/ui/widgets/tui_footer.rs @@ -33,12 +33,22 @@ impl<'a> Widget for TuiFooter<'a> { let size = self.entry.metadata.len; let size = format::file_size_to_string(size as f64); + #[cfg(unix)] + let mimetype = match self.entry.metadata.mimetype.as_ref() { + Some(s) => s, + None => "", + }; + let mut text = vec![ Text::styled(mode, mode_style), Text::raw(" "), Text::raw(mtime), Text::raw(" "), Text::raw(size), + #[cfg(unix)] + Text::raw(" "), + #[cfg(unix)] + Text::raw(mimetype), ]; if self.entry.metadata.file_type.is_symlink() { |