Refactor, added config file for custom players and cli interface
This commit is contained in:
parent
7de78329fb
commit
ab586ba197
7 changed files with 258 additions and 62 deletions
|
@ -5,7 +5,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.93"
|
||||
serde_json = { version = "1.0.93", features = ["preserve_order"] }
|
||||
|
||||
[profile.release-full]
|
||||
inherits = "release"
|
||||
|
|
44
src/browser.rs
Normal file
44
src/browser.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use serde::Deserialize;
|
||||
use std::io;
|
||||
use std::io::BufReader;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use crate::error::FF2MpvError;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FF2MpvMessage {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
pub fn send_reply() -> Result<(), io::Error> {
|
||||
send_message("ok")
|
||||
}
|
||||
|
||||
pub fn get_mpv_message() -> Result<FF2MpvMessage, FF2MpvError> {
|
||||
let message = read_message()?;
|
||||
let ff2mpv_message = serde_json::from_str(&message)?;
|
||||
Ok(ff2mpv_message)
|
||||
}
|
||||
|
||||
fn read_message() -> Result<String, io::Error> {
|
||||
let mut stdin = io::stdin();
|
||||
let mut buf: [u8; 4] = [0; 4];
|
||||
stdin.read_exact(&mut buf)?;
|
||||
|
||||
let length = u32::from_ne_bytes(buf);
|
||||
let mut reader = BufReader::new(stdin.take(length as u64));
|
||||
|
||||
let mut string = String::with_capacity(length as usize);
|
||||
reader.read_to_string(&mut string)?;
|
||||
Ok(string)
|
||||
}
|
||||
|
||||
fn send_message(message: &str) -> Result<(), io::Error> {
|
||||
let length = (message.len() as u32).to_ne_bytes();
|
||||
let message = message.as_bytes();
|
||||
|
||||
let mut stdout = io::stdout();
|
||||
stdout.write_all(&length)?;
|
||||
stdout.write_all(message)?;
|
||||
Ok(())
|
||||
}
|
79
src/command.rs
Normal file
79
src/command.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use std::env;
|
||||
use std::io;
|
||||
use std::process;
|
||||
use serde_json::{self, json};
|
||||
|
||||
use crate::browser;
|
||||
use crate::config::Config;
|
||||
use crate::error::FF2MpvError;
|
||||
|
||||
pub enum Command {
|
||||
ShowHelp,
|
||||
ShowManifest,
|
||||
ValidateConfig,
|
||||
FF2Mpv
|
||||
}
|
||||
|
||||
|
||||
impl Command {
|
||||
pub fn execute(&self) -> Result<(), FF2MpvError> {
|
||||
match self {
|
||||
Command::ShowHelp => Self::show_help(),
|
||||
Command::ShowManifest => Self::show_manifest(),
|
||||
Command::ValidateConfig => Self::validate_config(),
|
||||
Command::FF2Mpv => Self::ff2mpv()
|
||||
}
|
||||
}
|
||||
|
||||
fn show_help() -> Result<(), FF2MpvError> {
|
||||
println!("Usage: ff2mpv-rust <command>");
|
||||
println!("Commands:");
|
||||
println!(" help: prints help message");
|
||||
println!(" manifest: prints manifest for browser configuration");
|
||||
println!(" validate: checks configration file for validity");
|
||||
println!("Note: Invalid commands won't fail");
|
||||
println!("Note: It will assume that binary is called from browser, blocking for input");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_manifest() -> Result<(), FF2MpvError>{
|
||||
let executable_path = env::current_exe()?;
|
||||
let manifest = json!({
|
||||
"name": "ff2mpv",
|
||||
"description": "ff2mpv's external manifest",
|
||||
"path": executable_path,
|
||||
"type": "stdio",
|
||||
"allowed_extensions": ["ff2mpv@yossarian.net"]
|
||||
});
|
||||
|
||||
let manifest = serde_json::to_string_pretty(&manifest)?;
|
||||
println!("{manifest}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_config() -> Result<(), FF2MpvError> {
|
||||
Config::parse_config_file()?;
|
||||
println!("Config is valid!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ff2mpv() -> Result<(), FF2MpvError> {
|
||||
let config = Config::build();
|
||||
let ff2mpv_message = browser::get_mpv_message()?;
|
||||
Command::launch_mpv(config.player_command, config.player_args, &ff2mpv_message.url)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn launch_mpv(command: String, args: Vec<String>, url: &str) -> Result<(), io::Error> {
|
||||
process::Command::new(command)
|
||||
.args(args)
|
||||
.arg(url)
|
||||
.spawn()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
81
src/config.rs
Normal file
81
src/config.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::error::FF2MpvError;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
#[serde(default = "default_player_command")]
|
||||
pub player_command: String,
|
||||
|
||||
#[serde(default = "default_player_args")]
|
||||
pub player_args: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
player_command: default_player_command(),
|
||||
player_args: default_player_args(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn build() -> Self {
|
||||
if let Ok(config) = Config::parse_config_file() {
|
||||
config
|
||||
} else {
|
||||
Config::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_config_file() -> Result<Self, FF2MpvError> {
|
||||
let config_path = Config::get_config_location();
|
||||
if !config_path.exists() {
|
||||
return Err(FF2MpvError::NoConfig);
|
||||
}
|
||||
|
||||
let string = fs::read_to_string(config_path)?;
|
||||
let config = serde_json::from_str(&string)?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
fn get_config_location() -> PathBuf {
|
||||
let mut path = PathBuf::new();
|
||||
|
||||
if let Ok(home) = env::var("XDG_CONFIG_HOME") {
|
||||
path.push(home);
|
||||
} else if let Ok(home) = env::var("HOME") {
|
||||
path.push(home);
|
||||
path.push(".config");
|
||||
} else {
|
||||
path.push("/etc");
|
||||
}
|
||||
|
||||
path.push("ff2mpv-rust.json");
|
||||
path
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
fn get_config_location() -> PathBuf {
|
||||
let mut path = PathBuf::new();
|
||||
let appdata = env::var("APPDATA").unwrap();
|
||||
|
||||
path.push(appdata);
|
||||
path.push("ff2mpv-rust.json");
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
fn default_player_command() -> String {
|
||||
"mpv".to_owned()
|
||||
}
|
||||
|
||||
fn default_player_args() -> Vec<String> {
|
||||
vec![]
|
||||
}
|
31
src/error.rs
Normal file
31
src/error.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use std::io;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
|
||||
pub enum FF2MpvError {
|
||||
NoConfig,
|
||||
IOError(io::Error),
|
||||
JSONError(serde_json::Error),
|
||||
}
|
||||
|
||||
impl From<io::Error> for FF2MpvError {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::IOError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for FF2MpvError {
|
||||
fn from(value: serde_json::Error) -> Self {
|
||||
Self::JSONError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FF2MpvError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::NoConfig => write!(f, "Config doesn't exist"),
|
||||
Self::IOError(e) => write!(f, "IO Error: {e}"),
|
||||
Self::JSONError(e) => write!(f, "JSON Error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
46
src/lib.rs
46
src/lib.rs
|
@ -1,42 +1,4 @@
|
|||
use std::io::{self, Read, Write};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MpvMessage {
|
||||
pub url: String
|
||||
}
|
||||
|
||||
pub fn get_mpv_message() -> Result<MpvMessage, String> {
|
||||
let message = match get_browser_message() {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => return Err(format!("IO Error: {e}"))
|
||||
};
|
||||
|
||||
match serde_json::from_str(&message) {
|
||||
Ok(msg) => Ok(msg),
|
||||
Err(e) => Err(format!("JSON Error: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_browser_message() -> io::Result<String> {
|
||||
let mut stdin = io::stdin();
|
||||
let mut buf: [u8; 4] = [0; 4];
|
||||
stdin.read_exact(&mut buf)?;
|
||||
|
||||
let length = u32::from_ne_bytes(buf);
|
||||
let mut reader = io::BufReader::new(stdin.take(length as u64));
|
||||
|
||||
let mut string = String::with_capacity(length as usize);
|
||||
reader.read_to_string(&mut string)?;
|
||||
Ok(string)
|
||||
}
|
||||
|
||||
pub fn send_browser_message(message: &str) -> io::Result<()> {
|
||||
let length = (message.len() as u32).to_ne_bytes();
|
||||
let message = message.as_bytes();
|
||||
|
||||
let mut stdout = io::stdout();
|
||||
stdout.write_all(&length)?;
|
||||
stdout.write_all(message)?;
|
||||
Ok(())
|
||||
}
|
||||
pub mod browser;
|
||||
pub mod command;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
|
|
37
src/main.rs
37
src/main.rs
|
@ -1,26 +1,25 @@
|
|||
use std::process::{Command, self};
|
||||
use ff2mpv_rust::{get_mpv_message, send_browser_message};
|
||||
use std::env;
|
||||
use std::process;
|
||||
|
||||
use ff2mpv_rust::command::Command;
|
||||
|
||||
fn main() {
|
||||
let message = match get_mpv_message() {
|
||||
Ok(msg) => msg,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
process::exit(-1)
|
||||
}
|
||||
};
|
||||
let mut args = env::args();
|
||||
args.next(); // Skip binary path
|
||||
|
||||
let mpv = Command::new("mpv")
|
||||
.arg(message.url)
|
||||
.spawn();
|
||||
|
||||
if let Err(e) = mpv {
|
||||
eprintln!("{e}");
|
||||
process::exit(-1);
|
||||
}
|
||||
|
||||
if let Err(e) = send_browser_message("ok") {
|
||||
let command_name = args.next().unwrap_or_default();
|
||||
let command = get_command(&command_name);
|
||||
if let Err(e) = command.execute() {
|
||||
eprintln!("{e}");
|
||||
process::exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_command(name: &str) -> Command {
|
||||
match name {
|
||||
"help" => Command::ShowHelp,
|
||||
"manifest" => Command::ShowManifest,
|
||||
"validate" => Command::ValidateConfig,
|
||||
_ => Command::FF2Mpv,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue