diff --git a/Cargo.toml b/Cargo.toml index f8b1bc0..9fedd5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,10 @@ reqwest = { version = "0.12", features= ["blocking"]} rss = "2.0" web-sys = "0.3.70" mdka = "1.2.10" #Not Sure if this is useful... -rusqlite = {version=">=0.36",features=['bundled']} +rusqlite = {version=">=0.34",features=['bundled']} html2md = "0.2" regex = "1" -#exemplar = ">=0.35.0" +directories-next = "2.0.0" [profile.dev] opt-level = 3 diff --git a/src/db.rs b/src/db.rs index 1c324ad..b384b80 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,5 +1,10 @@ +use std::path::PathBuf; + use rusqlite::{params, Connection, Result}; +use crate::files; + use super::net::{*}; +use super::files::{*}; /* @@ -7,6 +12,7 @@ Cache is the in-memory database Any changes/updates are written to file database */ + struct Item{ title: String, content: Option @@ -14,17 +20,16 @@ struct Item{ const DB_LOCATION: &str = "rsscar.db"; -/* - "on conflict ignore" HAS to be replaced. - What needs to happen is that when a story is already stored the existing item has to be updated. -*/ +fn get_db_path() -> PathBuf{ + get_data_directory().join(DB_LOCATION) +} 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 ignore, + 'url' text not null unique on conflict replace, 'subscribed' INTEGER, 'last_updated' TEXT , PRIMARY KEY('feedID') @@ -39,7 +44,7 @@ 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 ignore, + 'url' text not null unique on conflict replace, 'description' TEXT, 'content' TEXT, 'read' INTEGER DEFAULT 0, @@ -48,7 +53,8 @@ const items_table_create: &str = "CREATE TABLE IF NOT EXISTS 'items' ( const items_index_create: &str = "CREATE INDEX IF NOT EXISTS 'items_idx' on 'items'('itemID' ASC);"; pub fn initialize() { - let conn = Connection::open(DB_LOCATION).unwrap(); + + let conn = Connection::open(get_db_path()).unwrap(); conn.execute(feeds_table_create,[]).unwrap(); conn.execute(feeds_index_create,[]).unwrap(); conn.execute(items_table_create,[]).unwrap(); @@ -58,8 +64,8 @@ pub fn initialize() { } -pub fn add_feed(url: &str) { - let conn = Connection::open(DB_LOCATION).unwrap(); +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(); conn.execute("insert into feeds(title,url,description) values(?1,?2,?3)", @@ -70,7 +76,7 @@ pub fn add_feed(url: &str) { pub fn store_items(feed: rss::Channel) { - let conn = Connection::open(DB_LOCATION).unwrap(); + 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(), @@ -83,7 +89,7 @@ pub fn store_items(feed: rss::Channel) { } pub fn return_item() -> String{ - let conn = Connection::open(DB_LOCATION).unwrap(); + let conn = Connection::open(get_db_path()).unwrap(); let item = conn.query_row("select title,content from items where rowid=?1",[488],|row|{ Ok( Item { title: row.get(0).unwrap(), content: row.get(1).unwrap() } @@ -100,7 +106,7 @@ struct ReturnedFeedURLs{ } pub fn update_feeds() { //get feeds - let conn = Connection::open(DB_LOCATION).unwrap(); + 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{ diff --git a/src/files.rs b/src/files.rs new file mode 100644 index 0000000..97b4b22 --- /dev/null +++ b/src/files.rs @@ -0,0 +1,13 @@ +use std::{fs,io}; + +use directories_next::ProjectDirs; + +pub fn get_data_directory() -> std::path::PathBuf { + let dirs = ProjectDirs::from("rocks","gabe","RSSCar").expect("Failed to get paths"); + match fs::create_dir(dirs.data_dir()){ + Ok(_) => {} + Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {} + Err(e) => {println!("Unable to create data directory")} + }; + dirs.data_dir().to_owned() +} diff --git a/src/main.rs b/src/main.rs index 552c0f2..d5e4ec9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,150 +6,25 @@ use rss::Channel; mod ui; mod html; mod db; +mod files; -/*pub fn main() -> iced::Result { +//build CLI first, then create GUI + +/* +TO DO: + 1) Create basic CLI interactions + 2) Create comprehensive Schema + 3) Add/remove subscriptions + 4) + +*/ + +pub fn main() { db::initialize(); -iced::application("Really Sweet Stuff",State::update,State::view) - .theme(theme) - .run() -}*/ -pub fn main() -> iced::Result{ - db::initialize(); - //db::add_feed("https://gabe.rocks/rss"); - iced::run(Viewer::update,Viewer::view) + db::add_feed("https://gabe.rocks/rss"); } -#[derive(Clone,Debug)] -struct Viewer { - content: Vec -} -impl Default for Viewer{ - fn default() -> Self { - Self { content: markdown::parse(&html::process(db::return_item())).collect(), - } - } - -} - -#[derive(Clone,Debug)] -enum Changes{ - Nullchange(markdown::Url) -} - -impl Viewer{ - fn update(&mut self, _mes: Changes) { - - } - fn view(&self) -> iced::Element<'_, Changes> { - widget::container( - widget::scrollable( - markdown::view( - &self.content, - markdown::Settings::with_style(markdown::Style::from_palette(iced::Theme::Dark.palette())), - - ).map(Changes::Nullchange)) - ) - .align_x(iced::alignment::Horizontal::Center) - .align_y(iced::alignment::Vertical::Center) - .width(iced::Length::Fill) - .height(iced::Length::Fill) - .padding(5) - .into() - - - } -} -#[derive(Clone,Debug)] -struct State { - scene: Scene, - channels: Vec, - current_channel: usize, - current_item: usize, - item_open: bool, - play_media: bool, -} - -fn theme(state: &State) -> Theme { - iced::Theme::Nord -} - -#[derive(Clone, Copy, Debug)] -pub enum Scene { - Feeds, - Items, - ItemView, -} - -impl Default for Scene { - fn default() -> Self { - Scene::Feeds - } -} - -#[derive(Debug, Clone)] -pub enum Message { - SetScene(Scene), - SetChannel(usize), - SetItem(usize), - OpenItem, - ToggleMedia, - AddFeed(String), -} - -impl Default for State { - fn default() -> Self { - let main = net::load_rss("https://libresolutions.network/rss").unwrap(); - let small = net::load_rss("https://libresolutions.network/about/index.xml").unwrap(); - let gabefeed = net::load_rss("https://gabe.rocks/rss").unwrap(); - let channels = vec![small]; - Self { - scene: Scene::ItemView, - channels, - current_channel: 0, - current_item: 0, - item_open: true, - play_media: false, - } - } -} - -impl State { - - fn update(&mut self, mes: Message) { - match mes { - Message::SetScene (scene) => self.scene = scene, - Message::SetChannel(c) => { - self.current_channel = c; - self.scene = Scene::Items; - }, - Message::SetItem(i) => { - self.current_item = i; - self.scene = Scene::ItemView; - }, - Message::OpenItem => self.item_open = !self.item_open, - Message::ToggleMedia => self.play_media = !self.play_media, - Message::AddFeed(feed) => {self.channels.push(load_rss(&feed).unwrap())} - } - } - - fn view(&self) -> iced::Element<'_, Message> { - match self.scene { - Scene::Feeds => { - ui::channel_view(&self.channels) - }, - Scene::Items => { - ui::item_list_view(&self.channels[self.current_channel]) - }, - Scene::ItemView => { - let item = &self.channels[self.current_channel].items()[self.current_item]; - ui::item_view(item) - } - }.into() - } - -} - diff --git a/src/ui.rs b/src/ui.rs index fd26b61..e69de29 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,172 +0,0 @@ -use std::cell::Cell; - -use crate::net::download_image; -use super::html; -use super::net; -use iced::{ - self, - alignment::Vertical, - theme::Palette, - widget::{ - button, column, container, - image::{self, Handle}, - row, scrollable, text, Button, Column, Container, Image, Row, - }, - Color, Element, - Length::Fill, -}; -use rss::{self, Channel, Item}; - -use super::Message; -use super::Scene; - -pub fn channel_view(feeds: &Vec) -> Container { - container( - column( - feeds - .iter() - .map(|c: &Channel| { - let title = c.title(); - let index = feeds.iter().position(|i| i.title() == title).unwrap(); - channel_preview(c, index) - }) - .map(Element::from), - ) - .align_x(iced::Alignment::Start) - .spacing(5) - .padding(15), - ) - .height(Fill) - .width(Fill) -} - -pub fn channel_preview(feed: &rss::Channel, index: usize) -> Button { - let title = feed.title(); - let desc = feed.description(); - let fig: Image = - iced::widget::Image::new(download_image(feed.image().unwrap().url()).unwrap()); - //image needs to be downloaded... - fancy_button(fig, title, desc).on_press(Message::SetChannel(index)) -} - -pub fn item_list_view(feed: &Channel) -> Container { - println!("Loading items..\n"); - let rw = row![button("Feeds").on_press(Message::SetScene(Scene::Feeds))] - .spacing(10) - .align_y(iced::Alignment::Start); - let item_list = column( - feed.items - .iter() - .map(|i: &Item| { - let title = i.title(); - let index = feed.items.iter().position(|n| n.title() == title).unwrap(); - item_preview(i, index).width(Fill) - }) - .map(Element::from), - ) - .width(Fill) - .align_x(iced::Alignment::Start) - .width(Fill) - .spacing(5); - let scroll = scrollable(item_list).width(iced::Length::Fill).height(Fill); - - container(column![rw, scroll]) - .align_x(iced::alignment::Horizontal::Center) - .align_y(iced::alignment::Vertical::Center) - .width(Fill) - .height(Fill) - .padding(5) -} - -pub fn item_preview(item: &rss::Item, index: usize) -> Button { - let title = match item.title() { - Some(t) => t, - None => "Missing title", - }; - let date = match item.pub_date() { - Some(d) => d, - None => "Missing Date", - }; - let desc = item.description().unwrap(); - let fig = iced::widget::image(match get_item_image(item) { - Some(img) => net::download_image(img).unwrap(), - None => Handle::from("rss.png"), - }); - fancy_button(fig, title, desc).on_press(Message::SetItem((index))) -} - -pub fn item_view(item: &rss::Item) -> Container { - let title = item.title().unwrap(); - let desc: &str = item.description().unwrap(); - let date = match item.pub_date() { - Some(dt) => dt, - None => "", - }; - let content = item.content.clone().unwrap(); - let desc = item.description().unwrap(); - let rw = row![ - button("Feeds").on_press(Message::SetScene(Scene::Feeds)), - button("Items").on_press(Message::SetScene(Scene::Items)) - ] - .spacing(15) - .padding(5) - .align_y(iced::Alignment::Start); - let list = column![ - text(title).size(50), - text(date).size(25), - text(desc).size(35), - iced::widget::scrollable(text(html::process(content)).size(25)), - ] - .spacing(10) - .align_x(iced::Alignment::Start); - container(column![rw, list].width(Fill)) - .width(Fill) - .height(Fill) -} - -//Display Story -pub fn content_view<'a>(content: String) -> Container<'a, Message>{ - todo!() -} - -pub fn fancy_button<'a>( - icon: iced::widget::Image, - title: &'a str, - description: &'a str, -) -> Button<'a, Message> { - let c = container( - row![ - icon.width(120), - column![text(title).size(40), text(description).size(25),] - .align_x(iced::Alignment::Start) - .spacing(5) - ] - .spacing(15) - .align_y(iced::Alignment::Center), - ) - .align_x(iced::alignment::Horizontal::Center); - button(c) - .padding(5) - .width(Fill) -} - -pub fn get_item_image(item: &rss::Item) -> Option<&str> { - // Only bother with itunes:image - print!("{} \n", item.title().unwrap()); - match item.itunes_ext() { - Some(e) => match e.image() { - Some(img) => { - println!("Image found: {}", img); - Some(img) - } - None => { - println!("Itunes extension found, but image was not.."); - None - } - }, - None => { - println!("found no extensions"); - None - } - } -}