<artifactId>h2</artifactId>
<version>2.3.232</version>
</dependency>
+
+ <dependency>
+ <groupId>com.zaxxer</groupId>
+ <artifactId>HikariCP</artifactId>
+ <version>7.0.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.rometools</groupId>
+ <artifactId>rome</artifactId>
+ <version>2.1.0</version>
+ </dependency>
</dependencies>
</project>
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()
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)
}
author VARCHAR,
pub_date TIMESTAMP,
guid VARCHAR UNIQUE,
- content CLOB
+ contentText CLOB,
+ contentHTML CLOB
)
""".trimIndent())
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)
}
return items
}
+
+ public fun getAllFeeds() : MutableList<Feed> {
+ val feeds = mutableListOf<Feed>()
+
+ 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<Item>) {
+ 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()
+ }
+ }
+ }
+ }
}
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
package de.unixwork.rssreader
+import java.util.Date
+
class Item(id: Int) {
val id = id
var feedId = -1
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
var result: String? = null
when(col) {
0 -> result = elm.title
- 1 -> result = elm.pubDate
+ 1 -> result = elm.pubDate.toString()
}
result
}
--- /dev/null
+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<Feed>
+
+ 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<Item>()
+ 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