From b3d13dcc003979ee68c6dc1902f0d978869a0980 Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Wed, 27 Aug 2025 17:01:31 +0200 Subject: [PATCH] implement feed sync --- rss-application/pom.xml | 12 +++ .../main/kotlin/de/unixwork/rssreader/App.kt | 15 ++++ .../kotlin/de/unixwork/rssreader/Database.kt | 71 ++++++++++++++++- .../kotlin/de/unixwork/rssreader/FeedList.kt | 18 ++++- .../main/kotlin/de/unixwork/rssreader/Item.kt | 7 +- .../de/unixwork/rssreader/MainWindow.kt | 2 +- .../kotlin/de/unixwork/rssreader/SyncJob.kt | 78 +++++++++++++++++++ 7 files changed, 196 insertions(+), 7 deletions(-) create mode 100644 rss-application/src/main/kotlin/de/unixwork/rssreader/SyncJob.kt diff --git a/rss-application/pom.xml b/rss-application/pom.xml index 5ef160a..38fa064 100644 --- a/rss-application/pom.xml +++ b/rss-application/pom.xml @@ -101,6 +101,18 @@ h2 2.3.232 + + + com.zaxxer + HikariCP + 7.0.2 + + + + com.rometools + rome + 2.1.0 + diff --git a/rss-application/src/main/kotlin/de/unixwork/rssreader/App.kt b/rss-application/src/main/kotlin/de/unixwork/rssreader/App.kt index 744fc91..a1c52ba 100644 --- a/rss-application/src/main/kotlin/de/unixwork/rssreader/App.kt +++ b/rss-application/src/main/kotlin/de/unixwork/rssreader/App.kt @@ -1,9 +1,24 @@ package de.unixwork.rssreader import de.unixwork.ui.Application +import de.unixwork.ui.ToolbarPosition import de.unixwork.ui.Toolkit +import de.unixwork.ui.kotlin.toolbarItem +import de.unixwork.ui.kotlin.addToolbarDefault class App : Application { + init { + initToolbar() + } + + fun initToolbar() { + toolbarItem(name = "reload", icon = "view-refresh") { + SyncJob().sync() + } + + addToolbarDefault("reload", ToolbarPosition.LEFT) + } + override fun startup() { val window = MainWindow() window.show() diff --git a/rss-application/src/main/kotlin/de/unixwork/rssreader/Database.kt b/rss-application/src/main/kotlin/de/unixwork/rssreader/Database.kt index 84894e8..77125e9 100644 --- a/rss-application/src/main/kotlin/de/unixwork/rssreader/Database.kt +++ b/rss-application/src/main/kotlin/de/unixwork/rssreader/Database.kt @@ -1,15 +1,28 @@ package de.unixwork.rssreader +import com.zaxxer.hikari.HikariConfig +import com.zaxxer.hikari.HikariDataSource import de.unixwork.ui.Context +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.IO +import kotlinx.coroutines.launch import java.sql.Connection import java.sql.DriverManager import java.sql.Statement object Database { val connection: Connection + val dataSource: HikariDataSource init { connection = DriverManager.getConnection("jdbc:h2:~/.rssreader/feeds") + + val config = HikariConfig() + config.jdbcUrl = "jdbc:h2:~/.rssreader/feeds" + config.maximumPoolSize = 16 + dataSource = HikariDataSource(config) + ensureSchema(connection) } @@ -69,7 +82,8 @@ object Database { author VARCHAR, pub_date TIMESTAMP, guid VARCHAR UNIQUE, - content CLOB + contentText CLOB, + contentHTML CLOB ) """.trimIndent()) @@ -198,9 +212,10 @@ object Database { item.link = rs.getString("link") item.description = rs.getString("description") item.author = rs.getString("author") - item.pubDate = rs.getString("pub_date") + item.pubDate = rs.getDate("pub_date") item.guid = rs.getString("guid") - item.content = rs.getString("content") + item.contentText = rs.getString("contentText") + item.contentHtml = rs.getString("contentHTML") item.feedName = feedCollection.name item.feedUrl = rs.getString("URL") items.add(item) @@ -209,4 +224,54 @@ object Database { } return items } + + public fun getAllFeeds() : MutableList { + val feeds = mutableListOf() + + connection.prepareStatement(""" + select * from feeds + """.trimIndent()).use { stmt -> + stmt.executeQuery().use { rs -> + while(rs.next()) { + val id = rs.getInt("feed_id") + val feedCollectionId = rs.getInt("feedcollection_id") + val url = rs.getString("url") + val authUser = rs.getString("auth_user") + val authPassword = rs.getString("auth_password") + val certPath = rs.getString("certpath") + val feed = Feed(id, feedCollectionId, url) + feeds.add(feed) + } + } + } + + return feeds + } + + public fun addItems(items: Collection) { + GlobalScope.launch(Dispatchers.IO) { + dataSource.connection.use { conn -> + conn.prepareStatement(""" + insert into items (feed_id, title, link, description, author, pub_date, guid, contentText, contentHTML) + select ?, ?, ?, ?, ?, ?, ?, ?, ? + where ? not in (select guid from items) + """.trimIndent()).use { stmt -> + items.forEach { item -> + stmt.setInt(1, item.feedId) + stmt.setString(2, item.title) + stmt.setString(3, item.link) + stmt.setString(4, item.description) + stmt.setString(5, item.author) + stmt.setTimestamp(6, item.pubDate?.let { java.sql.Timestamp.from(it.toInstant()) }) + stmt.setString(7, item.guid) + stmt.setString(8, item.contentText) + stmt.setString(9, item.contentHtml) + stmt.setString(10, item.guid) + stmt.addBatch() + } + stmt.executeBatch() + } + } + } + } } diff --git a/rss-application/src/main/kotlin/de/unixwork/rssreader/FeedList.kt b/rss-application/src/main/kotlin/de/unixwork/rssreader/FeedList.kt index 1b865fd..af1644e 100644 --- a/rss-application/src/main/kotlin/de/unixwork/rssreader/FeedList.kt +++ b/rss-application/src/main/kotlin/de/unixwork/rssreader/FeedList.kt @@ -27,7 +27,23 @@ class FeedList : Document() { feedName.setString(item.feedName) author.setString(item.author) link.set(item.link, item.link) - webview.loadContent(null, item.content ?: "", "text/html", "utf-8") + var mimeType: String? = null + var content: String? = null + if(item.contentHtml != null) { + content = item.contentHtml + mimeType = "text/html" + } else if(item.contentText != null) { + content = item.contentText + mimeType = "text/plain" + } else if(item.description != null) { + content = item.description + mimeType = "text/html" + } else { + content = "" + mimeType = "text/plain" + } + + webview.loadContent(null, content, mimeType, "utf-8") } } \ No newline at end of file diff --git a/rss-application/src/main/kotlin/de/unixwork/rssreader/Item.kt b/rss-application/src/main/kotlin/de/unixwork/rssreader/Item.kt index 0bd14cd..64c1e18 100644 --- a/rss-application/src/main/kotlin/de/unixwork/rssreader/Item.kt +++ b/rss-application/src/main/kotlin/de/unixwork/rssreader/Item.kt @@ -1,5 +1,7 @@ package de.unixwork.rssreader +import java.util.Date + class Item(id: Int) { val id = id var feedId = -1 @@ -7,9 +9,10 @@ class Item(id: Int) { var link: String? = null var description: String? = null var author: String? = null - var pubDate: String? = null + var pubDate: Date? = null var guid: String? = null - var content: String? = null + var contentText: String? = null + var contentHtml: String? = null var feedName: String? = null var feedUrl: String? = null diff --git a/rss-application/src/main/kotlin/de/unixwork/rssreader/MainWindow.kt b/rss-application/src/main/kotlin/de/unixwork/rssreader/MainWindow.kt index 3860887..83209e0 100644 --- a/rss-application/src/main/kotlin/de/unixwork/rssreader/MainWindow.kt +++ b/rss-application/src/main/kotlin/de/unixwork/rssreader/MainWindow.kt @@ -71,7 +71,7 @@ class MainWindow { var result: String? = null when(col) { 0 -> result = elm.title - 1 -> result = elm.pubDate + 1 -> result = elm.pubDate.toString() } result } diff --git a/rss-application/src/main/kotlin/de/unixwork/rssreader/SyncJob.kt b/rss-application/src/main/kotlin/de/unixwork/rssreader/SyncJob.kt new file mode 100644 index 0000000..f4b5d59 --- /dev/null +++ b/rss-application/src/main/kotlin/de/unixwork/rssreader/SyncJob.kt @@ -0,0 +1,78 @@ +package de.unixwork.rssreader + +import com.rometools.rome.io.SyndFeedInput +import com.rometools.rome.io.XmlReader +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.IO +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.launch +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse + +class SyncJob { + val feeds: List + + init { + feeds = Database.getAllFeeds() + } + + fun sync() { + GlobalScope.launch(Dispatchers.IO) { + val client = HttpClient.newBuilder().build() + val jobs = feeds.map { feed -> + async { + try { + val request = HttpRequest.newBuilder() + .uri(URI(feed.uri)) + .GET() + .build() + + val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) + response.body().use { stream -> + val input = SyndFeedInput() + val syndFeed = input.build(XmlReader(stream)) + val items = mutableListOf() + println("Fetched feed: ${syndFeed.title}") + syndFeed.entries.forEach { entry -> + println(" ${entry.title} - ${entry.link}") + + val item = Item(0) + item.feedId = feed.id + item.title = entry.title + item.link = entry.link + item.description = entry.description?.value + item.author = entry.author + item.pubDate = entry.publishedDate + item.guid = entry.uri + val contents = entry.contents + contents.forEach { content -> + if(content.type == null) { + item.contentText = content.value + } else { + if(content.type.startsWith("text/html")) { + item.contentHtml = content.value + } else if(content.type.startsWith("text/plain")) { + item.contentText = content.value + } + } + } + + items.add(item) + } + Database.addItems(items) + syndFeed + } + } catch (e: Exception) { + println("Failed to fetch ${feed.uri}: ${e.message}") + null + } + } + } + jobs.awaitAll() + } + } +} \ No newline at end of file -- 2.47.3