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:
Gabriel 2025-05-29 12:07:36 -04:00
parent 21b7554af1
commit 86303939de
5 changed files with 47 additions and 325 deletions

View file

@ -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

View file

@ -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
View 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()
}

View file

@ -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
View file

@ -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
}
}
}