<commit_before>use {Error, FakeDebug, atom_syndication, futures, lettre, reqwest, rss, std};
use config::Config;
use escapade::Escapable;
use log::{LogKind, LogLevel, Logger};
use std::str::FromStr;
use std::sync::{Arc, Mutex};

const NUM_FETCHERS: usize = 32;
const CHANNEL_CAPACITY: usize = 2 * NUM_FETCHERS;
const FETCH_TIMEOUT_SECS: u64 = 60;

#[derive(Clone, Debug, PartialEq)]
pub struct Feed {
    pub meta: FeedMeta,
    pub items: Vec<FeedItem>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct FeedMeta {
    pub feed_url: String, // this is the same URL as what's in the database
    pub title: String,
}

#[derive(Clone, Debug, PartialEq)]
pub struct FeedItem {
    pub id: String,
    pub title: Option<String>,
    pub link: Option<String>,
    pub content: Option<String>,
}

pub trait Sender {
    fn send(&self, logger: &Logger, feed_meta: &FeedMeta, feed_item: &FeedItem) -> Result<(), Error>;
}

#[cfg(test)]
#[derive(Debug)]
pub struct RecorderSender {
    recorded_items: Mutex<Vec<(FeedMeta, FeedItem)>>,
}

#[cfg(test)]
impl RecorderSender {
    pub fn new() -> Self {
        RecorderSender { recorded_items: Mutex::new(Vec::new()) }
    }

    pub fn recorded_items(self) -> Vec<(FeedMeta, FeedItem)> {
        self.recorded_items.into_inner().unwrap()
    }
}

#[cfg(test)]
impl Sender for RecorderSender {
    fn send(&self, _logger: &Logger, feed_meta: &FeedMeta, feed_item: &FeedItem) -> Result<(), Error> {
        self.recorded_items.lock().unwrap().push((
            feed_meta.clone(),
            feed_item.clone(),
        ));
        Ok(())
    }
}

#[derive(Debug)]
pub struct EmailSender {
    config: Config,
    mail_client: FakeDebug<Mutex<lettre::transport::smtp::SmtpTransport>>, // TODO: Need newer lettre crate for Debug impl
    no_send: bool,
}

impl EmailSender {
    pub fn new(config: &Config) -> Result<Self, Error> {

        let mail_client = lettre::transport::smtp::SmtpTransportBuilder::new(&config.smtp_server)
            .map_err(|e| ("Failed to construct mail client", e))?
            .credentials(&config.smtp_username, &config.smtp_password)
            .security_level(lettre::transport::smtp::SecurityLevel::AlwaysEncrypt)
            .build();

        Ok(EmailSender {
            config: config.clone(),
            mail_client: FakeDebug(Mutex::new(mail_client)),
            no_send: false,
        })
    }

    pub fn with_no_send(mut self, no_send: bool) -> Self {
        self.no_send = no_send;
        self
    }
}

impl Sender for EmailSender {
    fn send(&self, logger: &Logger, feed_meta: &FeedMeta, feed_item: &FeedItem) -> Result<(), Error> {

        use lettre::transport::EmailTransport;

        let item_title = feed_item.title.as_ref().map(|x| x.as_str()).unwrap_or(
            "(N/a)",
        );

        if feed_item.content.is_none() {
            panic!("Got no content for {}", feed_meta.feed_url);
        }<commit_msg>Don't panic on feed item without content<commit_after>use {Error, FakeDebug, atom_syndication, futures, lettre, reqwest, rss, std};
use config::Config;
use escapade::Escapable;
use log::{LogKind, LogLevel, Logger};
use std::str::FromStr;
use std::sync::{Arc, Mutex};

const NUM_FETCHERS: usize = 32;
const CHANNEL_CAPACITY: usize = 2 * NUM_FETCHERS;
const FETCH_TIMEOUT_SECS: u64 = 60;

#[derive(Clone, Debug, PartialEq)]
pub struct Feed {
    pub meta: FeedMeta,
    pub items: Vec<FeedItem>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct FeedMeta {
    pub feed_url: String, // this is the same URL as what's in the database
    pub title: String,
}

#[derive(Clone, Debug, PartialEq)]
pub struct FeedItem {
    pub id: String,
    pub title: Option<String>,
    pub link: Option<String>,
    pub content: Option<String>,
}

pub trait Sender {
    fn send(&self, logger: &Logger, feed_meta: &FeedMeta, feed_item: &FeedItem) -> Result<(), Error>;
}

#[cfg(test)]
#[derive(Debug)]
pub struct RecorderSender {
    recorded_items: Mutex<Vec<(FeedMeta, FeedItem)>>,
}

#[cfg(test)]
impl RecorderSender {
    pub fn new() -> Self {
        RecorderSender { recorded_items: Mutex::new(Vec::new()) }
    }

    pub fn recorded_items(self) -> Vec<(FeedMeta, FeedItem)> {
        self.recorded_items.into_inner().unwrap()
    }
}

#[cfg(test)]
impl Sender for RecorderSender {
    fn send(&self, _logger: &Logger, feed_meta: &FeedMeta, feed_item: &FeedItem) -> Result<(), Error> {
        self.recorded_items.lock().unwrap().push((
            feed_meta.clone(),
            feed_item.clone(),
        ));
        Ok(())
    }
}

#[derive(Debug)]
pub struct EmailSender {
    config: Config,
    mail_client: FakeDebug<Mutex<lettre::transport::smtp::SmtpTransport>>, // TODO: Need newer lettre crate for Debug impl
    no_send: bool,
}

impl EmailSender {
    pub fn new(config: &Config) -> Result<Self, Error> {

        let mail_client = lettre::transport::smtp::SmtpTransportBuilder::new(&config.smtp_server)
            .map_err(|e| ("Failed to construct mail client", e))?
            .credentials(&config.smtp_username, &config.smtp_password)
            .security_level(lettre::transport::smtp::SecurityLevel::AlwaysEncrypt)
            .build();

        Ok(EmailSender {
            config: config.clone(),
            mail_client: FakeDebug(Mutex::new(mail_client)),
            no_send: false,
        })
    }

    pub fn with_no_send(mut self, no_send: bool) -> Self {
        self.no_send = no_send;
        self
    }
}

impl Sender for EmailSender {
    fn send(&self, logger: &Logger, feed_meta: &FeedMeta, feed_item: &FeedItem) -> Result<(), Error> {

        use lettre::transport::EmailTransport;

        let item_title = feed_item.title.as_ref().map(|x| x.as_str()).unwrap_or(
            "(N/a)",
        );
{'fraction_non_alphanumeric': 0.14442508710801394, 'fraction_numerical': 0.0024390243902439024, 'mean_word_length': 3.293941660433807, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3974435', 'n_tokens_mistral': 1931, 'n_tokens_neox': 1906, 'n_words': 455}