Successfully loaded a feed and items into the db, able to parse and display as markdown... kinda
This commit is contained in:
parent
7f95347330
commit
ca139525a2
7 changed files with 234 additions and 14 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
target
|
target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
*.db
|
11
Cargo.toml
11
Cargo.toml
|
@ -8,4 +8,13 @@ iced = {git = "https://github.com/iced-rs/iced", branch = "master", features = [
|
||||||
reqwest = { version = "0.12", features= ["blocking"]}
|
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"
|
mdka = "1.2.10" #Not Sure if this is useful...
|
||||||
|
rusqlite = {version="0.32",features=['bundled']}
|
||||||
|
html2md = "0.2"
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
122
src/db.rs
Normal file
122
src/db.rs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
use rusqlite::{params, Connection, Result};
|
||||||
|
use super::net::{*};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Cache is the in-memory database
|
||||||
|
Any changes/updates are written to file database
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct Item{
|
||||||
|
title: String,
|
||||||
|
content: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
const DB_LOCATION: &str = "rsscar.db";
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
'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);";
|
||||||
|
|
||||||
|
pub fn initialize() {
|
||||||
|
let conn = Connection::open(DB_LOCATION).unwrap();
|
||||||
|
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();
|
||||||
|
println!("Database Initialized.")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_feed(url: &str) {
|
||||||
|
let conn = Connection::open(DB_LOCATION).unwrap();
|
||||||
|
let feed = load_rss(url).unwrap();
|
||||||
|
let new_feed = feed.clone();
|
||||||
|
conn.execute("insert into feeds(title,url,description) values(?1,?2,?3)",
|
||||||
|
[feed.title,url.to_owned(),feed.description]).unwrap();
|
||||||
|
conn.close();
|
||||||
|
store_items(new_feed);
|
||||||
|
//defaulting to Libre Solutions
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn store_items(feed: rss::Channel) {
|
||||||
|
let conn = Connection::open(DB_LOCATION).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();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn return_item() -> String{
|
||||||
|
let conn = Connection::open(DB_LOCATION).unwrap();
|
||||||
|
let item = conn.query_row("select title,content from items where rowid=?1",[1],|row|{
|
||||||
|
Ok(
|
||||||
|
Item { title: row.get(0).unwrap(), content: row.get(1).unwrap() }
|
||||||
|
)
|
||||||
|
}).unwrap();
|
||||||
|
match item.content {
|
||||||
|
Some(content) => content,
|
||||||
|
None => panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ReturnedFeedURLs{
|
||||||
|
url: String
|
||||||
|
}
|
||||||
|
pub fn update_feeds() {
|
||||||
|
//get feeds
|
||||||
|
let conn = Connection::open(DB_LOCATION).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<String> = Vec::new();
|
||||||
|
for feed in rows{
|
||||||
|
let url = feed.unwrap().url.clone();
|
||||||
|
urls.push(url);
|
||||||
|
}
|
||||||
|
stmt.finalize();
|
||||||
|
conn.close();
|
||||||
|
|
||||||
|
for u in urls {
|
||||||
|
store_items(load_rss(&u).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//for each feed
|
||||||
|
// insert items into database
|
||||||
|
|
||||||
|
//close out
|
||||||
|
}
|
17
src/html.rs
17
src/html.rs
|
@ -1,3 +1,16 @@
|
||||||
pub fn process_html(content: String) -> String {
|
use html2md;
|
||||||
content
|
use regex::Regex;
|
||||||
|
|
||||||
|
|
||||||
|
/* this works, but...
|
||||||
|
- images are omitted
|
||||||
|
- Blockquotes don't display
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub fn process(content: String) -> String {
|
||||||
|
let re = Regex::new(r"(?s)<iframe[^>]*>.*?</iframe>").unwrap();
|
||||||
|
let cleaned = re.replace_all(&content,"");
|
||||||
|
html2md::parse_html(&cleaned)
|
||||||
}
|
}
|
59
src/main.rs
59
src/main.rs
|
@ -1,16 +1,68 @@
|
||||||
mod net;
|
mod net;
|
||||||
use iced::{application, widget::{self, button, column, row, text}, Settings, Theme};
|
use db::initialize;
|
||||||
|
use iced::{application, widget::{self, button, column, markdown, row, text}, Settings, Theme};
|
||||||
use net::load_rss;
|
use net::load_rss;
|
||||||
use rss::Channel;
|
use rss::Channel;
|
||||||
mod ui;
|
mod ui;
|
||||||
mod html;
|
mod html;
|
||||||
|
mod db;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
|
||||||
|
|
||||||
|
/*pub fn main() -> iced::Result {
|
||||||
|
db::initialize();
|
||||||
iced::application("Really Sweet Stuff",State::update,State::view)
|
iced::application("Really Sweet Stuff",State::update,State::view)
|
||||||
.theme(theme)
|
.theme(theme)
|
||||||
.run()
|
.run()
|
||||||
|
}*/
|
||||||
|
pub fn main() -> iced::Result{
|
||||||
|
db::initialize();
|
||||||
|
db::add_feed("https://libresolutions.network/archive/index.xml");
|
||||||
|
iced::application("Really Sweet Stuff",Viewer::update,Viewer::view)
|
||||||
|
.run()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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::default(),
|
||||||
|
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)]
|
#[derive(Clone,Debug)]
|
||||||
struct State {
|
struct State {
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
|
@ -52,9 +104,8 @@ impl Default for State {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let main = net::load_rss("https://libresolutions.network/rss").unwrap();
|
let main = net::load_rss("https://libresolutions.network/rss").unwrap();
|
||||||
let small = net::load_rss("https://libresolutions.network/about/index.xml").unwrap();
|
let small = net::load_rss("https://libresolutions.network/about/index.xml").unwrap();
|
||||||
let test = net::load_rss("http://localhost:1313/about/index.xml").unwrap();
|
|
||||||
let gabefeed = net::load_rss("https://gabe.rocks/rss").unwrap();
|
let gabefeed = net::load_rss("https://gabe.rocks/rss").unwrap();
|
||||||
let channels = vec![test];
|
let channels = vec![small];
|
||||||
Self {
|
Self {
|
||||||
scene: Scene::ItemView,
|
scene: Scene::ItemView,
|
||||||
channels,
|
channels,
|
||||||
|
|
23
src/net.rs
23
src/net.rs
|
@ -23,10 +23,29 @@ pub fn load_rss(url: &str) -> Option<Channel>{
|
||||||
Err(e) => { panic!("Empty response")}
|
Err(e) => { panic!("Empty response")}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(err) => {panic!("Error loading feed.")}
|
Err(err) => {panic!("Error loading feed.:{}",err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn download_image(url: &str) -> Option<iced::widget::image::Handle>{
|
pub fn download_image(url: &str) -> Option<iced::widget::image::Handle>{
|
||||||
match reqwest::blocking::get(url) {
|
match reqwest::blocking::get(url) {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
|
|
13
src/ui.rs
13
src/ui.rs
|
@ -1,7 +1,7 @@
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
|
||||||
use crate::net::download_image;
|
use crate::net::download_image;
|
||||||
|
use super::html;
|
||||||
use super::net;
|
use super::net;
|
||||||
use iced::{
|
use iced::{
|
||||||
self,
|
self,
|
||||||
|
@ -115,7 +115,7 @@ pub fn item_view(item: &rss::Item) -> Container<Message> {
|
||||||
text(title).size(50),
|
text(title).size(50),
|
||||||
text(date).size(25),
|
text(date).size(25),
|
||||||
text(desc).size(35),
|
text(desc).size(35),
|
||||||
iced::widget::scrollable(text(super::html::process_html(content)).size(25)),
|
iced::widget::scrollable(text(html::process(content)).size(25)),
|
||||||
]
|
]
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.align_x(iced::Alignment::Start);
|
.align_x(iced::Alignment::Start);
|
||||||
|
@ -124,6 +124,11 @@ pub fn item_view(item: &rss::Item) -> Container<Message> {
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Display Story
|
||||||
|
pub fn content_view<'a>(content: String) -> Container<'a, Message>{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fancy_button<'a>(
|
pub fn fancy_button<'a>(
|
||||||
icon: iced::widget::Image<Handle>,
|
icon: iced::widget::Image<Handle>,
|
||||||
title: &'a str,
|
title: &'a str,
|
||||||
|
@ -136,7 +141,7 @@ pub fn fancy_button<'a>(
|
||||||
.align_x(iced::Alignment::Start)
|
.align_x(iced::Alignment::Start)
|
||||||
.spacing(5)
|
.spacing(5)
|
||||||
]
|
]
|
||||||
.spacing(5)
|
.spacing(15)
|
||||||
.align_y(iced::Alignment::Center),
|
.align_y(iced::Alignment::Center),
|
||||||
)
|
)
|
||||||
.align_x(iced::alignment::Horizontal::Center);
|
.align_x(iced::alignment::Horizontal::Center);
|
||||||
|
@ -145,7 +150,7 @@ pub fn fancy_button<'a>(
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_item_image(item: &rss::Item) -> Option<&str> {
|
pub fn get_item_image(item: &rss::Item) -> Option<&str> {
|
||||||
// Only bother with itunes:image
|
// Only bother with itunes:image
|
||||||
print!("{} \n", item.title().unwrap());
|
print!("{} \n", item.title().unwrap());
|
||||||
match item.itunes_ext() {
|
match item.itunes_ext() {
|
||||||
|
|
Loading…
Reference in a new issue