Replaced file logic to put the db in the correct location. Also on conflict replace is exactly what I need.
This commit is contained in:
parent
21b7554af1
commit
86303939de
5 changed files with 47 additions and 325 deletions
|
@ -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
|
||||
|
|
30
src/db.rs
30
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<String>
|
||||
|
@ -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{
|
||||
|
|
13
src/files.rs
Normal file
13
src/files.rs
Normal file
|
@ -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()
|
||||
}
|
153
src/main.rs
153
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<markdown::Item>
|
||||
}
|
||||
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<Channel>,
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
172
src/ui.rs
172
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<Channel>) -> Container<Message> {
|
||||
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<Message> {
|
||||
let title = feed.title();
|
||||
let desc = feed.description();
|
||||
let fig: Image<Handle> =
|
||||
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<Message> {
|
||||
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<Message> {
|
||||
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<Message> {
|
||||
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<Handle>,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue