Skip to content

Commit

Permalink
Merge pull request #299 from boinkor-net/localize-registration
Browse files Browse the repository at this point in the history
Use rhai::CustomType to localize registration of methods
  • Loading branch information
antifuchs authored Feb 1, 2024
2 parents 8807b4a + 0e5b99f commit 9204379
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 61 deletions.
70 changes: 15 additions & 55 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use self::policy::{Condition, PolicyMatch};
use crate::config::policy::DeletePolicy;
use crate::config::transmission::Transmission;
use rhai::{module_resolvers::FileModuleResolver, Array};
use rhai::{serde::from_dynamic, Dynamic, Engine, EvalAltResult};
use rhai::{CustomType, TypeBuilder};
use rhai::{Dynamic, Engine, EvalAltResult};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};

Expand All @@ -14,27 +15,15 @@ pub fn configure(file: &Path) -> Result<Vec<Instance>, Box<EvalAltResult>> {
let resolver = FileModuleResolver::new_with_path(file.parent().unwrap_or(&PathBuf::from(".")));
engine.set_module_resolver(resolver);
engine
// Transmission type:
.register_type_with_name::<Transmission>("Transmission")
.register_fn("transmission", Transmission::new)
.register_fn("user", Transmission::with_user)
.register_fn("password", Transmission::with_password)
.register_fn("poll_interval", Transmission::with_poll_interval)
// Instance type:
.register_type_with_name::<Instance>("Instance")
.register_fn("rules", Instance::new)
// A transmission API endpoint:
.build_type::<Transmission>()
// Instances:
.build_type::<Instance>()
// Policies
.register_fn("noop_delete_policy", construct_noop_delete_policy)
.register_fn("delete_policy", construct_real_delete_policy)
// Preconditions
.register_fn("on_trackers", PolicyMatch::new)
.register_fn("min_file_count", PolicyMatch::with_min_file_count)
.register_fn("max_file_count", PolicyMatch::with_max_file_count)
.build_type::<PolicyMatch>()
.build_type::<DeletePolicy>()
// Conditions
.register_fn("matching", Condition::new)
.register_fn("max_ratio", Condition::with_max_ratio)
.register_fn("min_seeding_time", Condition::with_min_seeding_time)
.register_fn("max_seeding_time", Condition::with_max_seeding_time);
.build_type::<Condition>();

Dynamic::from(
engine
Expand All @@ -45,47 +34,18 @@ pub fn configure(file: &Path) -> Result<Vec<Instance>, Box<EvalAltResult>> {
.map_err(|e| e.to_string().into())
}

pub fn construct_transmission(d: &Dynamic) -> Result<Transmission, Box<EvalAltResult>> {
from_dynamic::<Transmission>(d)
}

pub fn construct_condition(c: Condition) -> Condition {
c
}

pub fn construct_noop_delete_policy(
name: &str,
apply_when: PolicyMatch,
match_when: Condition,
) -> Result<DeletePolicy, Box<EvalAltResult>> {
Ok(DeletePolicy {
name: Some(name.to_string()),
precondition: apply_when,
match_when: match_when.sanity_check()?,
delete_data: false,
})
}

pub fn construct_real_delete_policy(
name: &str,
apply_when: PolicyMatch,
match_when: Condition,
) -> Result<DeletePolicy, Box<EvalAltResult>> {
Ok(DeletePolicy {
name: Some(name.to_string()),
precondition: apply_when,
match_when: match_when.sanity_check()?,
delete_data: true,
})
}

#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, CustomType)]
#[rhai_type(extra = Self::build_rhai)]
pub struct Instance {
pub transmission: Transmission,
pub policies: Vec<DeletePolicy>,
}

impl Instance {
fn build_rhai(builder: &mut TypeBuilder<Self>) {
builder.with_fn("rules", Self::new);
}

pub fn new(transmission: Transmission, policies: Array) -> Result<Self, Box<EvalAltResult>> {
Ok(Instance {
transmission,
Expand Down
63 changes: 59 additions & 4 deletions src/config/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{borrow::Cow, collections::HashSet, fmt};

use crate::util::chrono_optional_duration;
use chrono::{Duration, Utc};
use rhai::{Array, Dynamic, EvalAltResult};
use rhai::{Array, CustomType, Dynamic, EvalAltResult, TypeBuilder};
use serde::{Deserialize, Serialize};
use tracing::debug;
use transmission_rpc::types::TorrentStatus;
Expand All @@ -14,22 +14,33 @@ use crate::Torrent;
///
/// The policy itself doesn't need to match, this is just to indicate
/// that it *could* even match.
#[derive(PartialEq, Eq, Clone, Default, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Clone, Default, Debug, Serialize, Deserialize, CustomType)]
#[rhai_type(extra = Self::build_rhai)]
pub struct PolicyMatch {
/// The tracker URL hostnames (only the host, not the path or
/// port) that the policy should apply to.
#[rhai_type(readonly)]
pub trackers: HashSet<String>,

/// The number of files that must be present in a torrent for the
/// policy to match. If None, any number of files matches.
#[rhai_type(readonly)]
pub min_file_count: Option<i64>,

/// The maximum number of files that may be present in a torrent
/// for the policy to match. If None, any number of files matches.
#[rhai_type(readonly)]
pub max_file_count: Option<i64>,
}

impl PolicyMatch {
fn build_rhai(builder: &mut TypeBuilder<Self>) {
builder
.with_fn("on_trackers", Self::new)
.with_fn("min_file_count", Self::with_min_file_count)
.with_fn("max_file_count", Self::with_max_file_count);
}

pub fn new(trackers: Array) -> Result<Self, Box<EvalAltResult>> {
let trackers: Vec<String> = Dynamic::from(trackers).into_typed_array()?;
Ok(PolicyMatch {
Expand Down Expand Up @@ -121,7 +132,8 @@ impl fmt::Display for PolicyMatch {
/// a transmission instance.
///
/// There's a second set of conditions that need to match: See [PolicyMatch].
#[derive(PartialEq, Clone, Default, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Default, Serialize, Deserialize, CustomType)]
#[rhai_type(extra = Self::build_rhai)]
pub struct Condition {
/// The ratio at which a torrent qualifies for deletion, even if
/// it has been seeded for less than [`max_seeding_time`].
Expand All @@ -141,6 +153,14 @@ pub struct Condition {
}

impl Condition {
fn build_rhai(builder: &mut TypeBuilder<Self>) {
builder
.with_fn("matching", Self::new)
.with_fn("max_ratio", Self::with_max_ratio)
.with_fn("min_seeding_time", Self::with_min_seeding_time)
.with_fn("max_seeding_time", Self::with_max_seeding_time);
}

pub fn new() -> Result<Self, Box<EvalAltResult>> {
Ok(Condition {
..Default::default()
Expand Down Expand Up @@ -316,7 +336,8 @@ impl<'a> ApplicableDeletePolicy<'a> {
}

/// Specifies a condition for torrents that can be deleted.
#[derive(PartialEq, Clone, Serialize, Deserialize)]
#[derive(PartialEq, Clone, Serialize, Deserialize, CustomType)]
#[rhai_type(extra = Self::build_rhai)]
pub struct DeletePolicy {
pub name: Option<String>,

Expand All @@ -332,6 +353,40 @@ pub struct DeletePolicy {
}

impl DeletePolicy {
fn build_rhai(builder: &mut TypeBuilder<Self>) {
builder
.with_fn("noop_delete_policy", Self::new_noop)
.with_fn("delete_policy", Self::new_real);
}

/// Constructs a "no-op" deletion policy that will not delete data if matched.
pub fn new_noop(
name: &str,
apply_when: PolicyMatch,
match_when: Condition,
) -> Result<Self, Box<EvalAltResult>> {
Ok(Self {
name: Some(name.to_string()),
precondition: apply_when,
match_when: match_when.sanity_check()?,
delete_data: false,
})
}

/// Constructs a deletion policy that actually does delete data if matched.
pub fn new_real(
name: &str,
apply_when: PolicyMatch,
match_when: Condition,
) -> Result<DeletePolicy, Box<EvalAltResult>> {
Ok(DeletePolicy {
name: Some(name.to_string()),
precondition: apply_when,
match_when: match_when.sanity_check()?,
delete_data: true,
})
}

/// Ensures that the policy can be applied to a torrent, and only
/// if it is, allows chaining a `.matches` call.
pub fn applicable<'a>(&'a self, t: &'a Torrent) -> Option<ApplicableDeletePolicy> {
Expand Down
17 changes: 15 additions & 2 deletions src/config/transmission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,35 @@ use std::fmt;

use crate::util::chrono_duration;
use chrono::Duration;
use rhai::EvalAltResult;
use rhai::{CustomType, EvalAltResult, TypeBuilder};
use serde::{Deserialize, Serialize};

pub const DEFAULT_POLL_INTERVAL_MINS: i64 = 5;

/// A transmission instance
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, CustomType)]
#[rhai_type(extra = Self::build_rhai)]
pub struct Transmission {
#[rhai_type(readonly)]
pub url: String,
#[rhai_type(readonly)]
pub user: Option<String>,
#[rhai_type(readonly)]
pub password: Option<String>,
#[rhai_type(readonly)]
#[serde(with = "chrono_duration")]
pub poll_interval: Duration,
}

impl Transmission {
fn build_rhai(builder: &mut TypeBuilder<Self>) {
builder
.with_fn("transmission", Self::new)
.with_fn("user", Self::with_user)
.with_fn("password", Self::with_password)
.with_fn("poll_interval", Self::with_poll_interval);
}

pub fn new(url: &str) -> Self {
Self {
url: url.to_string(),
Expand Down

0 comments on commit 9204379

Please sign in to comment.