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]
|
[dependencies]
|
||||||
serde = { version = "1.0.152", features = ["derive"] }
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
serde_json = "1.0.93"
|
serde_json = { version = "1.0.93", features = ["preserve_order"] }
|
||||||
|
|
||||||
[profile.release-full]
|
[profile.release-full]
|
||||||
inherits = "release"
|
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};
|
pub mod browser;
|
||||||
use serde::Deserialize;
|
pub mod command;
|
||||||
|
pub mod config;
|
||||||
#[derive(Deserialize)]
|
pub mod error;
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
37
src/main.rs
37
src/main.rs
|
@ -1,26 +1,25 @@
|
||||||
use std::process::{Command, self};
|
use std::env;
|
||||||
use ff2mpv_rust::{get_mpv_message, send_browser_message};
|
use std::process;
|
||||||
|
|
||||||
|
use ff2mpv_rust::command::Command;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let message = match get_mpv_message() {
|
let mut args = env::args();
|
||||||
Ok(msg) => msg,
|
args.next(); // Skip binary path
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{e}");
|
|
||||||
process::exit(-1)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mpv = Command::new("mpv")
|
let command_name = args.next().unwrap_or_default();
|
||||||
.arg(message.url)
|
let command = get_command(&command_name);
|
||||||
.spawn();
|
if let Err(e) = command.execute() {
|
||||||
|
|
||||||
if let Err(e) = mpv {
|
|
||||||
eprintln!("{e}");
|
|
||||||
process::exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = send_browser_message("ok") {
|
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
process::exit(-1);
|
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