1 // dbqueries.rs
2 //
3 // Copyright 2017 Jordan Petridis <jpetridis@gnome.org>
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 //
18 // SPDX-License-Identifier: GPL-3.0-or-later
19 
20 //! Random CRUD helper functions.
21 
22 use chrono::prelude::*;
23 use diesel::prelude::*;
24 
25 use diesel::dsl::exists;
26 use diesel::select;
27 
28 use crate::database::connection;
29 use crate::errors::DataError;
30 use crate::models::*;
31 
get_sources() -> Result<Vec<Source>, DataError>32 pub fn get_sources() -> Result<Vec<Source>, DataError> {
33     use crate::schema::source::dsl::*;
34     let db = connection();
35     let con = db.get()?;
36 
37     source
38         .order((http_etag.asc(), last_modified.asc()))
39         .load::<Source>(&con)
40         .map_err(From::from)
41 }
42 
get_podcasts() -> Result<Vec<Show>, DataError>43 pub fn get_podcasts() -> Result<Vec<Show>, DataError> {
44     use crate::schema::shows::dsl::*;
45     let db = connection();
46     let con = db.get()?;
47 
48     shows
49         .order(title.asc())
50         .load::<Show>(&con)
51         .map_err(From::from)
52 }
53 
get_podcasts_filter(filter_ids: &[i32]) -> Result<Vec<Show>, DataError>54 pub fn get_podcasts_filter(filter_ids: &[i32]) -> Result<Vec<Show>, DataError> {
55     use crate::schema::shows::dsl::*;
56     let db = connection();
57     let con = db.get()?;
58 
59     shows
60         .order(title.asc())
61         .filter(id.ne_all(filter_ids))
62         .load::<Show>(&con)
63         .map_err(From::from)
64 }
65 
get_episodes() -> Result<Vec<Episode>, DataError>66 pub fn get_episodes() -> Result<Vec<Episode>, DataError> {
67     use crate::schema::episodes::dsl::*;
68     let db = connection();
69     let con = db.get()?;
70 
71     episodes
72         .order(epoch.desc())
73         .load::<Episode>(&con)
74         .map_err(From::from)
75 }
76 
get_downloaded_episodes() -> Result<Vec<EpisodeCleanerModel>, DataError>77 pub(crate) fn get_downloaded_episodes() -> Result<Vec<EpisodeCleanerModel>, DataError> {
78     use crate::schema::episodes::dsl::*;
79     let db = connection();
80     let con = db.get()?;
81 
82     episodes
83         .select((rowid, local_uri, played))
84         .filter(local_uri.is_not_null())
85         .load::<EpisodeCleanerModel>(&con)
86         .map_err(From::from)
87 }
88 
89 // pub(crate) fn get_played_episodes() -> Result<Vec<Episode>, DataError> {
90 //     use schema::episodes::dsl::*;
91 
92 //     let db = connection();
93 //     let con = db.get()?;
94 //     episodes
95 //         .filter(played.is_not_null())
96 //         .load::<Episode>(&con)
97 //         .map_err(From::from)
98 // }
99 
get_played_cleaner_episodes() -> Result<Vec<EpisodeCleanerModel>, DataError>100 pub(crate) fn get_played_cleaner_episodes() -> Result<Vec<EpisodeCleanerModel>, DataError> {
101     use crate::schema::episodes::dsl::*;
102     let db = connection();
103     let con = db.get()?;
104 
105     episodes
106         .select((rowid, local_uri, played))
107         .filter(played.is_not_null())
108         .load::<EpisodeCleanerModel>(&con)
109         .map_err(From::from)
110 }
111 
get_episode_from_rowid(ep_id: i32) -> Result<Episode, DataError>112 pub fn get_episode_from_rowid(ep_id: i32) -> Result<Episode, DataError> {
113     use crate::schema::episodes::dsl::*;
114     let db = connection();
115     let con = db.get()?;
116 
117     episodes
118         .filter(rowid.eq(ep_id))
119         .get_result::<Episode>(&con)
120         .map_err(From::from)
121 }
122 
get_episode_widget_from_rowid(ep_id: i32) -> Result<EpisodeWidgetModel, DataError>123 pub fn get_episode_widget_from_rowid(ep_id: i32) -> Result<EpisodeWidgetModel, DataError> {
124     use crate::schema::episodes::dsl::*;
125     let db = connection();
126     let con = db.get()?;
127 
128     episodes
129         .select((
130             rowid,
131             title,
132             uri,
133             local_uri,
134             epoch,
135             length,
136             duration,
137             played,
138             play_position,
139             show_id,
140         ))
141         .filter(rowid.eq(ep_id))
142         .get_result::<EpisodeWidgetModel>(&con)
143         .map_err(From::from)
144 }
145 
get_episode_local_uri_from_id(ep_id: i32) -> Result<Option<String>, DataError>146 pub fn get_episode_local_uri_from_id(ep_id: i32) -> Result<Option<String>, DataError> {
147     use crate::schema::episodes::dsl::*;
148     let db = connection();
149     let con = db.get()?;
150 
151     episodes
152         .filter(rowid.eq(ep_id))
153         .select(local_uri)
154         .get_result::<Option<String>>(&con)
155         .map_err(From::from)
156 }
157 
get_episodes_widgets_filter_limit( filter_ids: &[i32], limit: u32, ) -> Result<Vec<EpisodeWidgetModel>, DataError>158 pub fn get_episodes_widgets_filter_limit(
159     filter_ids: &[i32],
160     limit: u32,
161 ) -> Result<Vec<EpisodeWidgetModel>, DataError> {
162     use crate::schema::episodes::dsl::*;
163     let db = connection();
164     let con = db.get()?;
165     let columns = (
166         rowid,
167         title,
168         uri,
169         local_uri,
170         epoch,
171         length,
172         duration,
173         played,
174         play_position,
175         show_id,
176     );
177 
178     episodes
179         .select(columns)
180         .order(epoch.desc())
181         .filter(show_id.ne_all(filter_ids))
182         .limit(i64::from(limit))
183         .load::<EpisodeWidgetModel>(&con)
184         .map_err(From::from)
185 }
186 
get_podcast_from_id(pid: i32) -> Result<Show, DataError>187 pub fn get_podcast_from_id(pid: i32) -> Result<Show, DataError> {
188     use crate::schema::shows::dsl::*;
189     let db = connection();
190     let con = db.get()?;
191 
192     shows
193         .filter(id.eq(pid))
194         .get_result::<Show>(&con)
195         .map_err(From::from)
196 }
197 
get_podcast_cover_from_id(pid: i32) -> Result<ShowCoverModel, DataError>198 pub fn get_podcast_cover_from_id(pid: i32) -> Result<ShowCoverModel, DataError> {
199     use crate::schema::shows::dsl::*;
200     let db = connection();
201     let con = db.get()?;
202 
203     shows
204         .select((id, title, image_uri, image_uri_hash, image_cached))
205         .filter(id.eq(pid))
206         .get_result::<ShowCoverModel>(&con)
207         .map_err(From::from)
208 }
209 
get_pd_episodes(parent: &Show) -> Result<Vec<Episode>, DataError>210 pub fn get_pd_episodes(parent: &Show) -> Result<Vec<Episode>, DataError> {
211     use crate::schema::episodes::dsl::*;
212     let db = connection();
213     let con = db.get()?;
214 
215     Episode::belonging_to(parent)
216         .order(epoch.desc())
217         .load::<Episode>(&con)
218         .map_err(From::from)
219 }
220 
get_pd_episodes_count(parent: &Show) -> Result<i64, DataError>221 pub fn get_pd_episodes_count(parent: &Show) -> Result<i64, DataError> {
222     let db = connection();
223     let con = db.get()?;
224 
225     Episode::belonging_to(parent)
226         .count()
227         .get_result(&con)
228         .map_err(From::from)
229 }
230 
get_pd_episodeswidgets(parent: &Show) -> Result<Vec<EpisodeWidgetModel>, DataError>231 pub fn get_pd_episodeswidgets(parent: &Show) -> Result<Vec<EpisodeWidgetModel>, DataError> {
232     use crate::schema::episodes::dsl::*;
233     let db = connection();
234     let con = db.get()?;
235     let columns = (
236         rowid,
237         title,
238         uri,
239         local_uri,
240         epoch,
241         length,
242         duration,
243         played,
244         play_position,
245         show_id,
246     );
247 
248     episodes
249         .select(columns)
250         .filter(show_id.eq(parent.id()))
251         .order(epoch.desc())
252         .load::<EpisodeWidgetModel>(&con)
253         .map_err(From::from)
254 }
255 
get_pd_unplayed_episodes(parent: &Show) -> Result<Vec<Episode>, DataError>256 pub fn get_pd_unplayed_episodes(parent: &Show) -> Result<Vec<Episode>, DataError> {
257     use crate::schema::episodes::dsl::*;
258     let db = connection();
259     let con = db.get()?;
260 
261     Episode::belonging_to(parent)
262         .filter(played.is_null())
263         .order(epoch.desc())
264         .load::<Episode>(&con)
265         .map_err(From::from)
266 }
267 
268 // pub(crate) fn get_pd_episodes_limit(parent: &Show, limit: u32) ->
269 // Result<Vec<Episode>, DataError> {     use schema::episodes::dsl::*;
270 
271 //     let db = connection();
272 //     let con = db.get()?;
273 
274 //     Episode::belonging_to(parent)
275 //         .order(epoch.desc())
276 //         .limit(i64::from(limit))
277 //         .load::<Episode>(&con)
278 //         .map_err(From::from)
279 // }
280 
get_source_from_uri(uri_: &str) -> Result<Source, DataError>281 pub fn get_source_from_uri(uri_: &str) -> Result<Source, DataError> {
282     use crate::schema::source::dsl::*;
283     let db = connection();
284     let con = db.get()?;
285 
286     source
287         .filter(uri.eq(uri_))
288         .get_result::<Source>(&con)
289         .map_err(From::from)
290 }
291 
get_source_from_id(id_: i32) -> Result<Source, DataError>292 pub fn get_source_from_id(id_: i32) -> Result<Source, DataError> {
293     use crate::schema::source::dsl::*;
294     let db = connection();
295     let con = db.get()?;
296 
297     source
298         .filter(id.eq(id_))
299         .get_result::<Source>(&con)
300         .map_err(From::from)
301 }
302 
get_podcast_from_source_id(sid: i32) -> Result<Show, DataError>303 pub fn get_podcast_from_source_id(sid: i32) -> Result<Show, DataError> {
304     use crate::schema::shows::dsl::*;
305     let db = connection();
306     let con = db.get()?;
307 
308     shows
309         .filter(source_id.eq(sid))
310         .get_result::<Show>(&con)
311         .map_err(From::from)
312 }
313 
get_episode_from_pk(title_: &str, pid: i32) -> Result<Episode, DataError>314 pub fn get_episode_from_pk(title_: &str, pid: i32) -> Result<Episode, DataError> {
315     use crate::schema::episodes::dsl::*;
316     let db = connection();
317     let con = db.get()?;
318 
319     episodes
320         .filter(title.eq(title_))
321         .filter(show_id.eq(pid))
322         .get_result::<Episode>(&con)
323         .map_err(From::from)
324 }
325 
get_episode_minimal_from_pk( title_: &str, pid: i32, ) -> Result<EpisodeMinimal, DataError>326 pub(crate) fn get_episode_minimal_from_pk(
327     title_: &str,
328     pid: i32,
329 ) -> Result<EpisodeMinimal, DataError> {
330     use crate::schema::episodes::dsl::*;
331     let db = connection();
332     let con = db.get()?;
333 
334     episodes
335         .select((
336             rowid,
337             title,
338             uri,
339             epoch,
340             length,
341             duration,
342             play_position,
343             guid,
344             show_id,
345         ))
346         .filter(title.eq(title_))
347         .filter(show_id.eq(pid))
348         .get_result::<EpisodeMinimal>(&con)
349         .map_err(From::from)
350 }
351 
352 #[cfg(test)]
get_episode_cleaner_from_pk( title_: &str, pid: i32, ) -> Result<EpisodeCleanerModel, DataError>353 pub(crate) fn get_episode_cleaner_from_pk(
354     title_: &str,
355     pid: i32,
356 ) -> Result<EpisodeCleanerModel, DataError> {
357     use crate::schema::episodes::dsl::*;
358     let db = connection();
359     let con = db.get()?;
360 
361     episodes
362         .select((rowid, local_uri, played))
363         .filter(title.eq(title_))
364         .filter(show_id.eq(pid))
365         .get_result::<EpisodeCleanerModel>(&con)
366         .map_err(From::from)
367 }
368 
remove_feed(pd: &Show) -> Result<(), DataError>369 pub(crate) fn remove_feed(pd: &Show) -> Result<(), DataError> {
370     let db = connection();
371     let con = db.get()?;
372 
373     con.transaction(|| {
374         delete_source(&con, pd.source_id())?;
375         delete_podcast(&con, pd.id())?;
376         delete_podcast_episodes(&con, pd.id())?;
377         info!("Feed removed from the Database.");
378         Ok(())
379     })
380 }
381 
delete_source(con: &SqliteConnection, source_id: i32) -> QueryResult<usize>382 fn delete_source(con: &SqliteConnection, source_id: i32) -> QueryResult<usize> {
383     use crate::schema::source::dsl::*;
384 
385     diesel::delete(source.filter(id.eq(source_id))).execute(con)
386 }
387 
delete_podcast(con: &SqliteConnection, show_id: i32) -> QueryResult<usize>388 fn delete_podcast(con: &SqliteConnection, show_id: i32) -> QueryResult<usize> {
389     use crate::schema::shows::dsl::*;
390 
391     diesel::delete(shows.filter(id.eq(show_id))).execute(con)
392 }
393 
delete_podcast_episodes(con: &SqliteConnection, parent_id: i32) -> QueryResult<usize>394 fn delete_podcast_episodes(con: &SqliteConnection, parent_id: i32) -> QueryResult<usize> {
395     use crate::schema::episodes::dsl::*;
396 
397     diesel::delete(episodes.filter(show_id.eq(parent_id))).execute(con)
398 }
399 
source_exists(url: &str) -> Result<bool, DataError>400 pub fn source_exists(url: &str) -> Result<bool, DataError> {
401     use crate::schema::source::dsl::*;
402 
403     let db = connection();
404     let con = db.get()?;
405 
406     select(exists(source.filter(uri.eq(url))))
407         .get_result(&con)
408         .map_err(From::from)
409 }
410 
podcast_exists(source_id_: i32) -> Result<bool, DataError>411 pub(crate) fn podcast_exists(source_id_: i32) -> Result<bool, DataError> {
412     use crate::schema::shows::dsl::*;
413 
414     let db = connection();
415     let con = db.get()?;
416 
417     select(exists(shows.filter(source_id.eq(source_id_))))
418         .get_result(&con)
419         .map_err(From::from)
420 }
421 
episode_exists(title_: &str, show_id_: i32) -> Result<bool, DataError>422 pub(crate) fn episode_exists(title_: &str, show_id_: i32) -> Result<bool, DataError> {
423     use crate::schema::episodes::dsl::*;
424 
425     let db = connection();
426     let con = db.get()?;
427 
428     select(exists(
429         episodes
430             .filter(show_id.eq(show_id_))
431             .filter(title.eq(title_)),
432     ))
433     .get_result(&con)
434     .map_err(From::from)
435 }
436 
437 /// Check if the `episodes table contains any rows
438 ///
439 /// Return true if `episodes` table is populated.
is_episodes_populated(filter_show_ids: &[i32]) -> Result<bool, DataError>440 pub fn is_episodes_populated(filter_show_ids: &[i32]) -> Result<bool, DataError> {
441     use crate::schema::episodes::dsl::*;
442 
443     let db = connection();
444     let con = db.get()?;
445 
446     select(exists(episodes.filter(show_id.ne_all(filter_show_ids))))
447         .get_result(&con)
448         .map_err(From::from)
449 }
450 
451 /// Check if the `shows` table contains any rows
452 ///
453 /// Return true if `shows` table is populated.
is_podcasts_populated(filter_ids: &[i32]) -> Result<bool, DataError>454 pub fn is_podcasts_populated(filter_ids: &[i32]) -> Result<bool, DataError> {
455     use crate::schema::shows::dsl::*;
456 
457     let db = connection();
458     let con = db.get()?;
459 
460     select(exists(shows.filter(id.ne_all(filter_ids))))
461         .get_result(&con)
462         .map_err(From::from)
463 }
464 
465 /// Check if the `source` table contains any rows
466 ///
467 /// Return true if `source` table is populated.
is_source_populated(filter_ids: &[i32]) -> Result<bool, DataError>468 pub fn is_source_populated(filter_ids: &[i32]) -> Result<bool, DataError> {
469     use crate::schema::source::dsl::*;
470 
471     let db = connection();
472     let con = db.get()?;
473 
474     select(exists(source.filter(id.ne_all(filter_ids))))
475         .get_result(&con)
476         .map_err(From::from)
477 }
478 
index_new_episodes(eps: &[NewEpisode]) -> Result<(), DataError>479 pub(crate) fn index_new_episodes(eps: &[NewEpisode]) -> Result<(), DataError> {
480     use crate::schema::episodes::dsl::*;
481     let db = connection();
482     let con = db.get()?;
483 
484     diesel::insert_into(episodes)
485         .values(eps)
486         .execute(&*con)
487         .map_err(From::from)
488         .map(|_| ())
489 }
490 
update_none_to_played_now(parent: &Show) -> Result<usize, DataError>491 pub fn update_none_to_played_now(parent: &Show) -> Result<usize, DataError> {
492     use crate::schema::episodes::dsl::*;
493     let db = connection();
494     let con = db.get()?;
495 
496     let epoch_now = Utc::now().timestamp() as i32;
497     con.transaction(|| {
498         diesel::update(Episode::belonging_to(parent).filter(played.is_null()))
499             .set(played.eq(Some(epoch_now)))
500             .execute(&con)
501             .map_err(From::from)
502     })
503 }
504 
505 #[cfg(test)]
506 mod tests {
507     use super::*;
508     use crate::database::*;
509     use crate::pipeline::pipeline;
510     use anyhow::Result;
511 
512     #[test]
test_update_none_to_played_now() -> Result<()>513     fn test_update_none_to_played_now() -> Result<()> {
514         truncate_db()?;
515 
516         let url = "https://web.archive.org/web/20180120083840if_/https://feeds.feedburner.\
517                    com/InterceptedWithJeremyScahill";
518         let source = Source::from_url(url)?;
519         let id = source.id();
520         let rt = tokio::runtime::Runtime::new()?;
521         rt.block_on(pipeline(vec![source]));
522         let pd = get_podcast_from_source_id(id)?;
523 
524         let eps_num = get_pd_unplayed_episodes(&pd)?.len();
525         assert_ne!(eps_num, 0);
526 
527         update_none_to_played_now(&pd)?;
528         let eps_num2 = get_pd_unplayed_episodes(&pd)?.len();
529         assert_eq!(eps_num2, 0);
530         Ok(())
531     }
532 }
533