use crate::models::{ArticleID, FeedID, Url};
use crate::schema::articles;
use chrono::{DateTime, Utc};
use diesel::backend::Backend;
use diesel::deserialize;
use diesel::deserialize::FromSql;
use diesel::serialize::{self, IsNull};
use diesel::serialize::{Output, ToSql};
use diesel::sql_types::Integer;
use diesel::sqlite::Sqlite;
use serde::{Deserialize, Serialize};
use std::io::{Error, ErrorKind, Write};
use std::path::PathBuf;
#[derive(Identifiable, Insertable, Queryable, Clone, Eq, PartialEq, Debug)]
#[diesel(primary_key(article_id))]
#[diesel(belongs_to(Feed))]
#[diesel(treat_none_as_default_value = false)]
#[diesel(table_name = articles)]
#[diesel(check_for_backend(SQLite))]
pub struct Article {
pub article_id: ArticleID,
pub title: Option<String>,
pub author: Option<String>,
pub feed_id: FeedID,
pub url: Option<Url>,
pub date: DateTime<Utc>,
pub synced: DateTime<Utc>,
pub summary: Option<String>,
pub direction: Option<Direction>,
pub unread: Read,
pub marked: Marked,
pub thumbnail_url: Option<String>,
pub updated: Option<DateTime<Utc>>,
}
#[derive(Identifiable, Insertable, Queryable, Clone, Eq, PartialEq, Debug)]
#[diesel(primary_key(article_id))]
#[diesel(belongs_to(Feed))]
#[diesel(treat_none_as_default_value = false)]
#[diesel(table_name = articles)]
#[diesel(check_for_backend(SQLite))]
pub struct FatArticle {
pub article_id: ArticleID,
pub title: Option<String>,
pub author: Option<String>,
pub feed_id: FeedID,
pub url: Option<Url>,
pub date: DateTime<Utc>,
pub synced: DateTime<Utc>,
pub html: Option<String>,
pub summary: Option<String>,
pub direction: Option<Direction>,
pub unread: Read,
pub marked: Marked,
pub scraped_content: Option<String>,
pub plain_text: Option<String>,
pub thumbnail_url: Option<String>,
pub updated: Option<DateTime<Utc>>,
}
impl FatArticle {
pub fn save_html(&self, path: &PathBuf) -> Result<(), Error> {
if let Some(ref html) = self.html
&& let Ok(()) = std::fs::create_dir_all(path)
{
let mut file_name = match self.title.clone() {
Some(file_name) => file_name,
None => "Unknown Title".to_owned(),
};
file_name.push_str(".html");
let path = path.join(file_name);
let mut html_file = std::fs::File::create(path)?;
html_file.write_all(html.as_bytes())?;
}
Err(Error::new(ErrorKind::NotFound, "No HTML for article"))
}
}
#[derive(Eq, PartialEq, Copy, Clone, Debug, Default, AsExpression, FromSqlRow, Serialize, Deserialize)]
#[diesel(sql_type = Integer)]
pub enum Direction {
#[default]
LeftToRight,
RightToLeft,
}
impl Direction {
pub fn to_int(self) -> i32 {
self as i32
}
pub fn from_int(int: i32) -> Self {
match int {
0 => Direction::LeftToRight,
1 => Direction::RightToLeft,
_ => Direction::LeftToRight,
}
}
}
impl FromSql<Integer, Sqlite> for Direction {
fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let int = i32::from_sql(bytes)?;
Ok(Direction::from_int(int))
}
}
impl ToSql<Integer, Sqlite> for Direction {
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
out.set_value(*self as i32);
Ok(IsNull::No)
}
}
#[derive(Eq, PartialEq, Copy, Clone, Debug, Default, AsExpression, FromSqlRow, Serialize, Deserialize)]
#[diesel(sql_type = Integer)]
pub enum Read {
Read,
#[default]
Unread,
}
impl Read {
pub fn to_int(self) -> i32 {
self as i32
}
pub fn from_int(int: i32) -> Self {
match int {
0 => Read::Read,
1 => Read::Unread,
_ => Read::Read,
}
}
pub fn invert(&self) -> Read {
match self {
Read::Read => Read::Unread,
Read::Unread => Read::Read,
}
}
}
impl FromSql<Integer, Sqlite> for Read {
fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let int = i32::from_sql(bytes)?;
Ok(Read::from_int(int))
}
}
impl ToSql<Integer, Sqlite> for Read {
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
out.set_value(*self as i32);
Ok(IsNull::No)
}
}
#[derive(Eq, PartialEq, Copy, Clone, Debug, Default, AsExpression, FromSqlRow, Serialize, Deserialize)]
#[diesel(sql_type = Integer)]
pub enum Marked {
Marked,
#[default]
Unmarked,
}
impl Marked {
pub fn to_int(self) -> i32 {
self as i32
}
pub fn from_int(int: i32) -> Self {
match int {
0 => Marked::Marked,
1 => Marked::Unmarked,
_ => Marked::Unmarked,
}
}
pub fn invert(&self) -> Marked {
match self {
Marked::Marked => Marked::Unmarked,
Marked::Unmarked => Marked::Marked,
}
}
}
impl FromSql<Integer, Sqlite> for Marked {
fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let int = i32::from_sql(bytes)?;
Ok(Marked::from_int(int))
}
}
impl ToSql<Integer, Sqlite> for Marked {
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
out.set_value(*self as i32);
Ok(IsNull::No)
}
}
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum ArticleOrder {
#[default]
NewestFirst,
OldestFirst,
}
impl ArticleOrder {
pub fn invert(&self) -> ArticleOrder {
match self {
ArticleOrder::NewestFirst => ArticleOrder::OldestFirst,
ArticleOrder::OldestFirst => ArticleOrder::NewestFirst,
}
}
}
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum OrderBy {
#[default]
Updated,
Published,
}