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, } 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() } 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 not null unique on conflict replace, '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, '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') );"; const ITEMS_INDEX_CREATE: &str = "CREATE INDEX IF NOT EXISTS 'items_idx' on 'items'('itemID' ASC);"; const DB_RESET: &str = " drop table feeds; drop table items; "; 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.as_os_str().display()); 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.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_owned(), feed.description, time], ) .unwrap(); conn.close().unwrap(); //need to get the feed_id from the DB and then make sure items are mapped to feed store_items(new_feed); } pub fn store_items(feed: rss::Channel) { let conn = Connection::open(get_db_path()).unwrap(); feed.items.iter().for_each(|i: &rss::Item| { conn.execute( "insert into items(url,title,description,content) values(?1,?2,?3,?4)", [ i.link.clone(), i.title.clone(), i.description.clone(), i.content.clone(), ], ) .unwrap(); }); 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, pub description: Option, pub content: Option //date missing! needed for ordering!!! } pub fn get_feed_items(id: usize) -> Vec{ let conn = get_db(); let mut stmt = conn.prepare("select itemID,title,url,icon,description,content from items").unwrap(); let items:Result> = stmt.query_map([], |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:{}",id)} } } pub struct Feed { pub feed_id: usize, pub title: String, pub description: Option, pub icon: Option, pub url: String, pub subscribed: bool, pub last_updated: Option>, } fn time_string_conversion(str: String) -> Option>{ match DateTime::parse_from_rfc2822(&str) { Ok(dt) => {Some(dt.to_utc())}, Err(_) => {None} } } pub fn get_feeds() -> Vec { 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> = 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() { //get feeds let conn = Connection::open(get_db_path()).unwrap(); let mut stmt = conn.prepare("select url from feeds").unwrap(); let rows = stmt .query_map([], |row| { Ok(ReturnedFeedURLs { url: row.get(0).unwrap(), }) }) .unwrap(); let mut urls: Vec = Vec::new(); for feed in rows { let url = feed.unwrap().url.clone(); urls.push(url); } stmt.finalize().unwrap(); conn.close().unwrap(); for u in urls { store_items(load_rss(&u).unwrap()); } //for each feed // insert items into database //close out }