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"
|
rss = "2.0"
|
||||||
web-sys = "0.3.70"
|
web-sys = "0.3.70"
|
||||||
mdka = "1.2.10" #Not Sure if this is useful...
|
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"
|
html2md = "0.2"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
#exemplar = ">=0.35.0"
|
directories-next = "2.0.0"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 3
|
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 rusqlite::{params, Connection, Result};
|
||||||
|
use crate::files;
|
||||||
|
|
||||||
use super::net::{*};
|
use super::net::{*};
|
||||||
|
use super::files::{*};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -7,6 +12,7 @@ Cache is the in-memory database
|
||||||
Any changes/updates are written to file database
|
Any changes/updates are written to file database
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
struct Item{
|
struct Item{
|
||||||
title: String,
|
title: String,
|
||||||
content: Option<String>
|
content: Option<String>
|
||||||
|
@ -14,17 +20,16 @@ struct Item{
|
||||||
|
|
||||||
const DB_LOCATION: &str = "rsscar.db";
|
const DB_LOCATION: &str = "rsscar.db";
|
||||||
|
|
||||||
/*
|
fn get_db_path() -> PathBuf{
|
||||||
"on conflict ignore" HAS to be replaced.
|
get_data_directory().join(DB_LOCATION)
|
||||||
What needs to happen is that when a story is already stored the existing item has to be updated.
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
const feeds_table_create: &str = "CREATE TABLE IF NOT EXISTS 'feeds' (
|
const feeds_table_create: &str = "CREATE TABLE IF NOT EXISTS 'feeds' (
|
||||||
'feedID' INTEGER NOT NULL,
|
'feedID' INTEGER NOT NULL,
|
||||||
'title' TEXT NOT NULL,
|
'title' TEXT NOT NULL,
|
||||||
'description' TEXT,
|
'description' TEXT,
|
||||||
'icon' BLOB,
|
'icon' BLOB,
|
||||||
'url' text not null unique on conflict ignore,
|
'url' text not null unique on conflict replace,
|
||||||
'subscribed' INTEGER,
|
'subscribed' INTEGER,
|
||||||
'last_updated' TEXT ,
|
'last_updated' TEXT ,
|
||||||
PRIMARY KEY('feedID')
|
PRIMARY KEY('feedID')
|
||||||
|
@ -39,7 +44,7 @@ const items_table_create: &str = "CREATE TABLE IF NOT EXISTS 'items' (
|
||||||
'itemID' INTEGER NOT NULL,
|
'itemID' INTEGER NOT NULL,
|
||||||
'title' TEXT NOT NULL,
|
'title' TEXT NOT NULL,
|
||||||
'icon' BLOB,
|
'icon' BLOB,
|
||||||
'url' text not null unique on conflict ignore,
|
'url' text not null unique on conflict replace,
|
||||||
'description' TEXT,
|
'description' TEXT,
|
||||||
'content' TEXT,
|
'content' TEXT,
|
||||||
'read' INTEGER DEFAULT 0,
|
'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);";
|
const items_index_create: &str = "CREATE INDEX IF NOT EXISTS 'items_idx' on 'items'('itemID' ASC);";
|
||||||
|
|
||||||
pub fn initialize() {
|
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_table_create,[]).unwrap();
|
||||||
conn.execute(feeds_index_create,[]).unwrap();
|
conn.execute(feeds_index_create,[]).unwrap();
|
||||||
conn.execute(items_table_create,[]).unwrap();
|
conn.execute(items_table_create,[]).unwrap();
|
||||||
|
@ -58,8 +64,8 @@ pub fn initialize() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_feed(url: &str) {
|
pub fn add_feed(url: &str) {
|
||||||
let conn = Connection::open(DB_LOCATION).unwrap();
|
let conn = Connection::open(get_db_path()).unwrap();
|
||||||
let feed = load_rss(url).unwrap();
|
let feed = load_rss(url).unwrap();
|
||||||
let new_feed = feed.clone();
|
let new_feed = feed.clone();
|
||||||
conn.execute("insert into feeds(title,url,description) values(?1,?2,?3)",
|
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) {
|
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|{
|
feed.items.iter().for_each(|i: &rss::Item|{
|
||||||
conn.execute("insert into items(url,title,description,content) values(?1,?2,?3,?4)",[
|
conn.execute("insert into items(url,title,description,content) values(?1,?2,?3,?4)",[
|
||||||
i.link.clone(),
|
i.link.clone(),
|
||||||
|
@ -83,7 +89,7 @@ pub fn store_items(feed: rss::Channel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn return_item() -> String{
|
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|{
|
let item = conn.query_row("select title,content from items where rowid=?1",[488],|row|{
|
||||||
Ok(
|
Ok(
|
||||||
Item { title: row.get(0).unwrap(), content: row.get(1).unwrap() }
|
Item { title: row.get(0).unwrap(), content: row.get(1).unwrap() }
|
||||||
|
@ -100,7 +106,7 @@ struct ReturnedFeedURLs{
|
||||||
}
|
}
|
||||||
pub fn update_feeds() {
|
pub fn update_feeds() {
|
||||||
//get 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 mut stmt = conn.prepare("select url from feeds").unwrap();
|
||||||
let rows = stmt.query_map([],|row| {
|
let rows = stmt.query_map([],|row| {
|
||||||
Ok(ReturnedFeedURLs{
|
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 ui;
|
||||||
mod html;
|
mod html;
|
||||||
mod db;
|
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();
|
db::initialize();
|
||||||
iced::application("Really Sweet Stuff",State::update,State::view)
|
db::add_feed("https://gabe.rocks/rss");
|
||||||
.theme(theme)
|
|
||||||
.run()
|
|
||||||
}*/
|
|
||||||
pub fn main() -> iced::Result{
|
|
||||||
db::initialize();
|
|
||||||
//db::add_feed("https://gabe.rocks/rss");
|
|
||||||
iced::run(Viewer::update,Viewer::view)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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