Skip to content

Commit

Permalink
feat: support for multiple search providers (nyaa.si!) (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
micielski authored Aug 13, 2024
1 parent 4b3673d commit c0218a4
Show file tree
Hide file tree
Showing 23 changed files with 1,251 additions and 520 deletions.
883 changes: 506 additions & 377 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ license = "GPL-3.0-or-later"
rm-config = { version = "0.4", path = "rm-config" }
rm-shared = { version = "0.4", path = "rm-shared" }

magnetease = "0.1"
magnetease = "0.3"
anyhow = "1"
serde = { version = "1", features = ["derive"] }
transmission-rpc = "0.4"
Expand Down
1 change: 1 addition & 0 deletions rm-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ ratatui.workspace = true
crossterm.workspace = true
thiserror.workspace = true
transmission-rpc.workspace = true
magnetease.workspace = true
5 changes: 5 additions & 0 deletions rm-config/defaults/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ free_space_refresh = 10
# Padding, UploadRatio, UploadedEver, AddedDate, ActivityDate, PeersConnected
# SmallStatus
headers = ["Name", "SizeWhenDone", "Progress", "DownloadRate", "UploadRate"]

[search_tab]
# If you uncomment this, providers won't be automatically added in future
# versions of Rustmission.
# providers = ["Knaben", "Nyaa"]
14 changes: 10 additions & 4 deletions rm-config/defaults/keymap.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[general]
keybindings = [
{ on = "?", action = "ShowHelp" },
{ on = "F1", action = "ShowHelp" },
{ on = "?", action = "ShowHelp", show_in_help = false },
{ on = "F1", action = "ShowHelp", show_in_help = false },

{ on = "q", action = "Quit" },
{ on = "Esc", action = "Close" },
Expand All @@ -16,8 +16,8 @@ keybindings = [

{ on = "Home", action = "GoToBeginning" },
{ on = "End", action = "GoToEnd" },
{ on = "PageUp", action = "ScrollPageUp" },
{ on = "PageDown", action = "ScrollPageDown" },
{ on = "PageUp", action = "ScrollPageUp", show_in_help = false },
{ on = "PageDown", action = "ScrollPageDown", show_in_help = false },

{ modifier = "Ctrl", on = "u", action = "ScrollPageUp" },
{ modifier = "Ctrl", on = "d", action = "ScrollPageDown" },
Expand Down Expand Up @@ -46,3 +46,9 @@ keybindings = [
{ on = "d", action = "DeleteWithoutFiles" },
{ on = "D", action = "DeleteWithFiles" },
]

[search_tab]
keybindings = [
{ on = "p", action = "ShowProvidersInfo" }
]

1 change: 1 addition & 0 deletions rm-config/src/keymap/actions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use rm_shared::action::Action;

pub mod general;
pub mod search_tab;
pub mod torrents_tab;

pub trait UserAction: Into<Action> {
Expand Down
25 changes: 25 additions & 0 deletions rm-config/src/keymap/actions/search_tab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use rm_shared::action::Action;
use serde::{Deserialize, Serialize};

use super::UserAction;

#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SearchAction {
ShowProvidersInfo,
}

impl UserAction for SearchAction {
fn desc(&self) -> &'static str {
match self {
SearchAction::ShowProvidersInfo => "show providers info",
}
}
}

impl From<SearchAction> for Action {
fn from(value: SearchAction) -> Self {
match value {
SearchAction::ShowProvidersInfo => Action::ShowProvidersInfo,
}
}
}
53 changes: 44 additions & 9 deletions rm-config/src/keymap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ use std::{
collections::HashMap, io::ErrorKind, marker::PhantomData, path::PathBuf, sync::OnceLock,
};

use anyhow::Result;
use actions::search_tab::SearchAction;
use anyhow::{Context, Result};
use crossterm::event::{KeyCode, KeyModifiers as CrosstermKeyModifiers};
use serde::{
de::{self, Visitor},
Deserialize, Serialize,
};

use crate::utils;
use crate::utils::{self, ConfigFetchingError};
use rm_shared::action::Action;

use self::actions::{general::GeneralAction, torrents_tab::TorrentsAction};
Expand All @@ -20,8 +21,13 @@ use self::actions::{general::GeneralAction, torrents_tab::TorrentsAction};
pub struct KeymapConfig {
pub general: KeybindsHolder<GeneralAction>,
pub torrents_tab: KeybindsHolder<TorrentsAction>,
pub search_tab: KeybindsHolder<SearchAction>,
#[serde(skip)]
pub keymap: HashMap<(KeyCode, CrosstermKeyModifiers), Action>,
pub general_keymap: HashMap<(KeyCode, CrosstermKeyModifiers), Action>,
#[serde(skip)]
pub torrent_keymap: HashMap<(KeyCode, CrosstermKeyModifiers), Action>,
#[serde(skip)]
pub search_keymap: HashMap<(KeyCode, CrosstermKeyModifiers), Action>,
}

#[derive(Serialize, Deserialize, Clone)]
Expand All @@ -35,6 +41,7 @@ pub struct Keybinding<T: Into<Action>> {
#[serde(default)]
pub modifier: KeyModifier,
pub action: T,
pub show_in_help: bool,
}

impl<T: Into<Action>> Keybinding<T> {
Expand Down Expand Up @@ -84,11 +91,12 @@ impl<T: Into<Action>> Keybinding<T> {
}

impl<T: Into<Action>> Keybinding<T> {
fn new(on: KeyCode, action: T, modifier: Option<KeyModifier>) -> Self {
fn new(on: KeyCode, action: T, modifier: Option<KeyModifier>, show_in_help: bool) -> Self {
Self {
on,
modifier: modifier.unwrap_or(KeyModifier::None),
action,
show_in_help,
}
}
}
Expand All @@ -99,11 +107,12 @@ impl<'de, T: Into<Action> + Deserialize<'de>> Deserialize<'de> for Keybinding<T>
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
#[serde(field_identifier, rename_all = "snake_case")]
enum Field {
On,
Modifier,
Action,
ShowInHelp,
}

struct KeybindingVisitor<T> {
Expand All @@ -124,6 +133,7 @@ impl<'de, T: Into<Action> + Deserialize<'de>> Deserialize<'de> for Keybinding<T>
let mut on = None;
let mut modifier = None;
let mut action = None;
let mut show_in_help = None;
while let Some(key) = map.next_key()? {
match key {
Field::On => {
Expand Down Expand Up @@ -181,11 +191,18 @@ impl<'de, T: Into<Action> + Deserialize<'de>> Deserialize<'de> for Keybinding<T>
}
action = Some(map.next_value());
}
Field::ShowInHelp => {
if show_in_help.is_some() {
return Err(de::Error::duplicate_field("action"));
}
show_in_help = Some(map.next_value());
}
}
}
let on = on.ok_or_else(|| de::Error::missing_field("on"))?;
let action = action.ok_or_else(|| de::Error::missing_field("action"))??;
let modifier = modifier.transpose().unwrap();
let show_in_help = show_in_help.transpose().unwrap().unwrap_or(true);

if modifier.is_some() {
if let KeyCode::Char(char) = on {
Expand All @@ -197,7 +214,7 @@ impl<'de, T: Into<Action> + Deserialize<'de>> Deserialize<'de> for Keybinding<T>
}
}

Ok(Keybinding::new(on, action, modifier))
Ok(Keybinding::new(on, action, modifier, show_in_help))
}
}

Expand Down Expand Up @@ -269,12 +286,18 @@ impl KeymapConfig {
Ok(keymap_config)
}
Err(e) => match e {
utils::ConfigFetchingError::Io(e) if e.kind() == ErrorKind::NotFound => {
ConfigFetchingError::Io(e) if e.kind() == ErrorKind::NotFound => {
let mut keymap_config =
utils::put_config::<Self>(Self::DEFAULT_CONFIG, Self::FILENAME)?;
keymap_config.populate_hashmap();
Ok(keymap_config)
}
ConfigFetchingError::Toml(e) => Err(e).with_context(|| {
format!(
"Failed to parse config located at {:?}",
utils::get_config_path(Self::FILENAME)
)
}),
_ => anyhow::bail!(e),
},
}
Expand All @@ -293,6 +316,11 @@ impl KeymapConfig {
keys.push(keybinding.keycode_string());
}
}
for keybinding in &self.search_tab.keybindings {
if action == keybinding.action.into() {
keys.push(keybinding.keycode_string());
}
}

if keys.is_empty() {
None
Expand All @@ -304,11 +332,18 @@ impl KeymapConfig {
fn populate_hashmap(&mut self) {
for keybinding in &self.general.keybindings {
let hash_value = (keybinding.on, keybinding.modifier.into());
self.keymap.insert(hash_value, keybinding.action.into());
self.general_keymap
.insert(hash_value, keybinding.action.into());
}
for keybinding in &self.torrents_tab.keybindings {
let hash_value = (keybinding.on, keybinding.modifier.into());
self.keymap.insert(hash_value, keybinding.action.into());
self.torrent_keymap
.insert(hash_value, keybinding.action.into());
}
for keybinding in &self.search_tab.keybindings {
let hash_value = (keybinding.on, keybinding.modifier.into());
self.search_keymap
.insert(hash_value, keybinding.action.into());
}
}

Expand Down
2 changes: 2 additions & 0 deletions rm-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct Config {
pub general: main_config::General,
pub connection: main_config::Connection,
pub torrents_tab: main_config::TorrentsTab,
pub search_tab: main_config::SearchTab,
pub keybindings: KeymapConfig,
pub directories: Directories,
}
Expand All @@ -35,6 +36,7 @@ impl Config {
general: main_config.general,
connection: main_config.connection,
torrents_tab: main_config.torrents_tab,
search_tab: main_config.search_tab,
keybindings: keybindings.clone(),
directories,
})
Expand Down
32 changes: 29 additions & 3 deletions rm-config/src/main_config.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
use std::{io::ErrorKind, path::PathBuf, sync::OnceLock};

use anyhow::Result;
use anyhow::{Context, Result};
use magnetease::WhichProvider;
use ratatui::style::Color;
use rm_shared::header::Header;
use serde::Deserialize;
use url::Url;

use crate::utils::{self};
use crate::utils::{self, ConfigFetchingError};

#[derive(Deserialize)]
pub struct MainConfig {
pub general: General,
pub connection: Connection,
#[serde(default)]
pub torrents_tab: TorrentsTab,
#[serde(default)]
pub search_tab: SearchTab,
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -78,6 +81,23 @@ impl Default for TorrentsTab {
}
}

#[derive(Deserialize)]
pub struct SearchTab {
pub providers: Vec<WhichProvider>,
}

fn default_providers() -> Vec<WhichProvider> {
vec![WhichProvider::Knaben, WhichProvider::Nyaa]
}

impl Default for SearchTab {
fn default() -> Self {
Self {
providers: default_providers(),
}
}
}

impl MainConfig {
pub(crate) const FILENAME: &'static str = "config.toml";
const DEFAULT_CONFIG: &'static str = include_str!("../defaults/config.toml");
Expand All @@ -86,11 +106,17 @@ impl MainConfig {
match utils::fetch_config::<Self>(Self::FILENAME) {
Ok(config) => Ok(config),
Err(e) => match e {
utils::ConfigFetchingError::Io(e) if e.kind() == ErrorKind::NotFound => {
ConfigFetchingError::Io(e) if e.kind() == ErrorKind::NotFound => {
utils::put_config::<Self>(Self::DEFAULT_CONFIG, Self::FILENAME)?;
println!("Update {:?} and start rustmission again", Self::path());
std::process::exit(0);
}
ConfigFetchingError::Toml(e) => Err(e).with_context(|| {
format!(
"Failed to parse config located at {:?}",
utils::get_config_path(Self::FILENAME)
)
}),
_ => anyhow::bail!(e),
},
}
Expand Down
Loading

0 comments on commit c0218a4

Please sign in to comment.