Skip to main content

news_flash/models/
article.rs

1use crate::models::{ArticleID, FeedID, Url};
2use crate::schema::articles;
3use chrono::{DateTime, Utc};
4use diesel::backend::Backend;
5use diesel::deserialize;
6use diesel::deserialize::FromSql;
7use diesel::serialize::{self, IsNull};
8use diesel::serialize::{Output, ToSql};
9use diesel::sql_types::Integer;
10use diesel::sqlite::Sqlite;
11use serde::{Deserialize, Serialize};
12use std::io::{Error, ErrorKind, Write};
13use std::path::PathBuf;
14
15#[derive(Identifiable, Insertable, Queryable, Clone, Eq, PartialEq, Debug)]
16#[diesel(primary_key(article_id))]
17#[diesel(belongs_to(Feed))]
18#[diesel(treat_none_as_default_value = false)]
19#[diesel(table_name = articles)]
20#[diesel(check_for_backend(SQLite))]
21pub struct Article {
22    pub article_id: ArticleID,
23    pub title: Option<String>,
24    pub author: Option<String>,
25    pub feed_id: FeedID,
26    pub url: Option<Url>,
27    pub date: DateTime<Utc>,
28    pub synced: DateTime<Utc>,
29    pub summary: Option<String>,
30    pub direction: Option<Direction>,
31    pub unread: Read,
32    pub marked: Marked,
33    pub thumbnail_url: Option<String>,
34    pub updated: Option<DateTime<Utc>>,
35}
36
37//------------------------------------------------------------------
38
39#[derive(Identifiable, Insertable, Queryable, Clone, Eq, PartialEq, Debug)]
40#[diesel(primary_key(article_id))]
41#[diesel(belongs_to(Feed))]
42#[diesel(treat_none_as_default_value = false)]
43#[diesel(table_name = articles)]
44#[diesel(check_for_backend(SQLite))]
45pub struct FatArticle {
46    pub article_id: ArticleID,
47    pub title: Option<String>,
48    pub author: Option<String>,
49    pub feed_id: FeedID,
50    pub url: Option<Url>,
51    pub date: DateTime<Utc>,
52    pub synced: DateTime<Utc>,
53    pub html: Option<String>,
54    pub summary: Option<String>,
55    pub direction: Option<Direction>,
56    pub unread: Read,
57    pub marked: Marked,
58    pub scraped_content: Option<String>,
59    pub plain_text: Option<String>,
60    pub thumbnail_url: Option<String>,
61    pub updated: Option<DateTime<Utc>>,
62}
63
64impl FatArticle {
65    pub fn save_html(&self, path: &PathBuf) -> Result<(), Error> {
66        if let Some(ref html) = self.html
67            && let Ok(()) = std::fs::create_dir_all(path)
68        {
69            let mut file_name = match self.title.clone() {
70                Some(file_name) => file_name,
71                None => "Unknown Title".to_owned(),
72            };
73            file_name.push_str(".html");
74            let path = path.join(file_name);
75            let mut html_file = std::fs::File::create(path)?;
76            html_file.write_all(html.as_bytes())?;
77        }
78
79        Err(Error::new(ErrorKind::NotFound, "No HTML for article"))
80    }
81}
82
83//------------------------------------------------------------------
84
85#[derive(Eq, PartialEq, Copy, Clone, Debug, Default, AsExpression, FromSqlRow, Serialize, Deserialize)]
86#[diesel(sql_type = Integer)]
87pub enum Direction {
88    #[default]
89    LeftToRight,
90    RightToLeft,
91}
92
93impl Direction {
94    pub fn to_int(self) -> i32 {
95        self as i32
96    }
97
98    pub fn from_int(int: i32) -> Self {
99        match int {
100            0 => Direction::LeftToRight,
101            1 => Direction::RightToLeft,
102
103            _ => Direction::LeftToRight,
104        }
105    }
106}
107
108impl FromSql<Integer, Sqlite> for Direction {
109    fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
110        let int = i32::from_sql(bytes)?;
111        Ok(Direction::from_int(int))
112    }
113}
114
115impl ToSql<Integer, Sqlite> for Direction {
116    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
117        out.set_value(*self as i32);
118        Ok(IsNull::No)
119    }
120}
121
122//------------------------------------------------------------------
123
124#[derive(Eq, PartialEq, Copy, Clone, Debug, Default, AsExpression, FromSqlRow, Serialize, Deserialize)]
125#[diesel(sql_type = Integer)]
126pub enum Read {
127    Read,
128    #[default]
129    Unread,
130}
131
132impl Read {
133    pub fn to_int(self) -> i32 {
134        self as i32
135    }
136
137    pub fn from_int(int: i32) -> Self {
138        match int {
139            0 => Read::Read,
140            1 => Read::Unread,
141
142            _ => Read::Read,
143        }
144    }
145
146    pub fn invert(&self) -> Read {
147        match self {
148            Read::Read => Read::Unread,
149            Read::Unread => Read::Read,
150        }
151    }
152}
153
154impl FromSql<Integer, Sqlite> for Read {
155    fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
156        let int = i32::from_sql(bytes)?;
157        Ok(Read::from_int(int))
158    }
159}
160
161impl ToSql<Integer, Sqlite> for Read {
162    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
163        out.set_value(*self as i32);
164        Ok(IsNull::No)
165    }
166}
167
168//------------------------------------------------------------------
169
170#[derive(Eq, PartialEq, Copy, Clone, Debug, Default, AsExpression, FromSqlRow, Serialize, Deserialize)]
171#[diesel(sql_type = Integer)]
172pub enum Marked {
173    Marked,
174    #[default]
175    Unmarked,
176}
177
178impl Marked {
179    pub fn to_int(self) -> i32 {
180        self as i32
181    }
182
183    pub fn from_int(int: i32) -> Self {
184        match int {
185            0 => Marked::Marked,
186            1 => Marked::Unmarked,
187
188            _ => Marked::Unmarked,
189        }
190    }
191
192    pub fn invert(&self) -> Marked {
193        match self {
194            Marked::Marked => Marked::Unmarked,
195            Marked::Unmarked => Marked::Marked,
196        }
197    }
198}
199
200impl FromSql<Integer, Sqlite> for Marked {
201    fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
202        let int = i32::from_sql(bytes)?;
203        Ok(Marked::from_int(int))
204    }
205}
206
207impl ToSql<Integer, Sqlite> for Marked {
208    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
209        out.set_value(*self as i32);
210        Ok(IsNull::No)
211    }
212}
213
214//------------------------------------------------------------------
215
216#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)]
217pub enum ArticleOrder {
218    #[default]
219    NewestFirst,
220    OldestFirst,
221}
222
223impl ArticleOrder {
224    pub fn invert(&self) -> ArticleOrder {
225        match self {
226            ArticleOrder::NewestFirst => ArticleOrder::OldestFirst,
227            ArticleOrder::OldestFirst => ArticleOrder::NewestFirst,
228        }
229    }
230}
231
232//------------------------------------------------------------------
233
234#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)]
235pub enum OrderBy {
236    #[default]
237    Updated,
238    Published,
239}