241 lines
6.9 KiB
Rust
241 lines
6.9 KiB
Rust
use std::path::PathBuf;
|
||
use chrono::FixedOffset;
|
||
use rusqlite::{ Connection, Result};
|
||
use super::files::*;
|
||
use super::net::*;
|
||
use chrono::Utc; //Maybe use a different time?
|
||
use chrono::DateTime;
|
||
|
||
/*
|
||
Cache is the in-memory database
|
||
Any changes/updates are written to file database
|
||
*/
|
||
|
||
struct Item {
|
||
title: String,
|
||
content: Option<String>,
|
||
}
|
||
|
||
const DB_LOCATION: &str = "rsscar.db";
|
||
|
||
fn get_db_path() -> PathBuf {
|
||
get_data_directory().join(DB_LOCATION)
|
||
}
|
||
|
||
fn get_db() -> Connection{
|
||
Connection::open(get_db_path()).unwrap()
|
||
}
|
||
//url needs to be from the feed URL NOT the url in the channel itself!!
|
||
const FEEDS_TABLE_CREATE: &str = "CREATE TABLE IF NOT EXISTS 'feeds' (
|
||
'feedID' INTEGER NOT NULL,
|
||
'title' TEXT NOT NULL,
|
||
'description' TEXT,
|
||
'icon' BLOB,
|
||
'url' text,
|
||
'subscribed' INTEGER NOT NULL default 0,
|
||
'last_updated' TEXT ,
|
||
PRIMARY KEY('feedID')
|
||
);";
|
||
|
||
const FEEDS_INDEX_CREATE: &str = "CREATE INDEX IF NOT EXISTS 'subscribed_feeds_idx' ON 'feeds' (
|
||
'feedID' ASC
|
||
) WHERE 'subscribed' = 1;";
|
||
|
||
/* */
|
||
const ITEMS_TABLE_CREATE: &str = "CREATE TABLE IF NOT EXISTS 'items' (
|
||
'itemID' INTEGER NOT NULL,
|
||
'feedID' INTEGER NOT NULL,
|
||
'title' TEXT NOT NULL,
|
||
'icon' BLOB,
|
||
'url' text not null unique on conflict replace,
|
||
'description' TEXT,
|
||
'content' TEXT,
|
||
'read' INTEGER DEFAULT 0,
|
||
PRIMARY KEY('itemID'),
|
||
FOREIGN KEY('feedID') REFERENCES 'feeds'('feedID')
|
||
);";
|
||
const ITEMS_INDEX_CREATE: &str = "CREATE INDEX IF NOT EXISTS 'items_idx' on 'items'('itemID' ASC);";
|
||
|
||
const ITEMS_FEED_INDEX_CREATE: &str = "CREATE INDEX IF NOT EXISTS 'item_feed_idx' on 'items'('feedID' ASC);";
|
||
|
||
const DB_RESET: &str = "
|
||
drop table items;
|
||
drop table feeds;
|
||
";
|
||
|
||
pub fn reset(){
|
||
println!("⚠️WARNING⚠️\nResetting Database");
|
||
let conn = get_db();
|
||
match conn.execute_batch(DB_RESET) {
|
||
Ok(_) => {println!("Database successfully wiped.")}
|
||
Err(e) => {panic!("Error erasing database.\nError: {0}",e)}
|
||
}
|
||
conn.close().unwrap();
|
||
initialize();
|
||
|
||
}
|
||
|
||
|
||
pub fn initialize() {
|
||
let path = get_db_path();
|
||
println!("Database at {} initialized", path.to_string_lossy());
|
||
let conn = get_db();
|
||
conn.execute(FEEDS_TABLE_CREATE, []).unwrap();
|
||
conn.execute(FEEDS_INDEX_CREATE, []).unwrap();
|
||
conn.execute(ITEMS_TABLE_CREATE, []).unwrap();
|
||
conn.execute(ITEMS_INDEX_CREATE, []).unwrap();
|
||
conn.execute(ITEMS_FEED_INDEX_CREATE,[]).unwrap();
|
||
conn.close().unwrap();
|
||
println!("Database Initialized.")
|
||
}
|
||
|
||
|
||
pub fn add_feed(url: &str) {
|
||
let conn = Connection::open(get_db_path()).unwrap();
|
||
let feed = load_rss(url).unwrap();
|
||
let new_feed = feed.clone();
|
||
let time = Utc::now().to_rfc2822();
|
||
conn.execute(
|
||
"insert into feeds(title,url,description,last_updated) values(?1,?2,?3,?4)",
|
||
[feed.title, url.to_string(), feed.description, time],
|
||
)
|
||
.unwrap();
|
||
let mut stmt = conn.prepare("select feedID from feeds where url=?1").unwrap();
|
||
let id: usize = stmt.query_row([url],|row| {
|
||
row.get(0)
|
||
}).unwrap();
|
||
//need to get the feed_id from the DB and then make sure items are mapped to feed
|
||
store_items(new_feed,id);
|
||
}
|
||
|
||
pub fn store_items(feed: rss::Channel,feed_id: usize) {
|
||
let conn = Connection::open(get_db_path()).unwrap();
|
||
feed.items.into_iter().for_each(|i: rss::Item| {
|
||
conn.execute(
|
||
"insert into items(url,title,description,content,feedID)
|
||
values(?1,?2,?3,?4,?5)",
|
||
[
|
||
i.link,
|
||
i.title,
|
||
i.description,
|
||
i.content,
|
||
Some(feed_id.to_string())
|
||
],
|
||
)
|
||
.ok();
|
||
});
|
||
conn.close().unwrap();
|
||
}
|
||
|
||
pub fn return_item() -> String {
|
||
let conn = Connection::open(get_db_path()).unwrap();
|
||
let item = conn
|
||
.query_row(
|
||
"select title,content from items where rowid=?1",
|
||
[],
|
||
|row| {
|
||
Ok(Item {
|
||
title: row.get(0).unwrap(),
|
||
content: row.get(1).unwrap(),
|
||
})
|
||
},
|
||
)
|
||
.unwrap();
|
||
match item.content {
|
||
Some(content) => content,
|
||
None => panic!(),
|
||
}
|
||
}
|
||
|
||
pub struct FeedItem {
|
||
pub item_id: usize,
|
||
pub title: String,
|
||
pub url: String,
|
||
pub icon: Option<String>,
|
||
pub description: Option<String>,
|
||
pub content: Option<String>
|
||
//date missing! needed for ordering!!!
|
||
|
||
}
|
||
|
||
pub fn get_feed_items(feed_id: usize) -> Vec<FeedItem>{
|
||
let conn = get_db();
|
||
let mut stmt = conn.prepare("select itemID,title,url,icon,description,content from items where feedID = ?1").unwrap();
|
||
let items:Result<Vec<FeedItem>> = stmt.query_map([feed_id], |row| {
|
||
Ok(FeedItem{
|
||
item_id: row.get(0).unwrap(),
|
||
title: row.get(1).unwrap(),
|
||
url: row.get(2).unwrap(),
|
||
icon: row.get(3).unwrap(),
|
||
description: row.get(4).unwrap(),
|
||
content: row.get(5).unwrap()
|
||
})
|
||
}).unwrap().collect();
|
||
match items {
|
||
Ok(i) => {i},
|
||
Err(_) => {panic!("No items for this feed\nFeedID:{}",feed_id)}
|
||
}
|
||
}
|
||
|
||
pub fn get_item(item_id: usize) -> FeedItem {
|
||
let conn = get_db();
|
||
let mut stmt = conn.prepare("select itemID,title,url,icon,description,content from items where itemID = ?1").unwrap();
|
||
let item:FeedItem = stmt.query_one([item_id],|row| {
|
||
Ok(
|
||
FeedItem{
|
||
item_id: row.get(0).unwrap(),
|
||
title: row.get(1).unwrap(),
|
||
url: row.get(2).unwrap(),
|
||
icon: row.get(3).unwrap(),
|
||
description: row.get(4).unwrap(),
|
||
content: row.get(5).unwrap(),
|
||
})
|
||
}).unwrap();
|
||
item
|
||
}
|
||
|
||
pub struct Feed {
|
||
pub feed_id: usize,
|
||
pub title: String,
|
||
pub description: Option<String>,
|
||
pub icon: Option<String>,
|
||
pub url: String,
|
||
pub subscribed: bool,
|
||
pub last_updated: Option<DateTime<Utc>>,
|
||
}
|
||
|
||
fn time_string_conversion(str: String) -> Option<DateTime<Utc>>{
|
||
match DateTime::parse_from_rfc2822(&str) {
|
||
Ok(dt) => {Some(dt.to_utc())},
|
||
Err(_) => {None}
|
||
}
|
||
}
|
||
|
||
pub fn get_feeds() -> Vec<Feed> {
|
||
let conn = get_db();
|
||
let mut stmt = conn.prepare("select feedID,title,description,icon,url,subscribed,last_updated from feeds order by last_updated desc").unwrap();
|
||
let rows: Result<Vec<Feed>> = stmt
|
||
.query_map([], |row| {
|
||
Ok(Feed {
|
||
feed_id: row.get(0).unwrap(),
|
||
title: row.get(1).unwrap(),
|
||
description: row.get(2).unwrap(),
|
||
icon: row.get(3).unwrap(),
|
||
url: row.get(4).unwrap(),
|
||
subscribed: row.get::<_,bool>(5).unwrap(),
|
||
last_updated:time_string_conversion(row.get(6).unwrap()),
|
||
})
|
||
}).unwrap().collect();
|
||
match rows {
|
||
Ok(r) => {
|
||
r
|
||
}
|
||
Err(e) => {panic!("No idea what causes this")}
|
||
}
|
||
}
|
||
struct ReturnedFeedURLs {
|
||
url: String,
|
||
}
|
||
pub fn update_feeds() {
|
||
todo!()
|
||
}
|