Skip to content

Commit

Permalink
Slow progress, get XMLFeed to return valid
Browse files Browse the repository at this point in the history
  • Loading branch information
jocmp committed Dec 5, 2023
1 parent bb18fc0 commit 87c6cf8
Show file tree
Hide file tree
Showing 30 changed files with 292 additions and 152 deletions.
8 changes: 4 additions & 4 deletions feedbinclient/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ java {

dependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.moshi:moshi-kotlin:1.14.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
implementation(libs.moshi.kotlin)
implementation(libs.kotlinx.coroutines.core)
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
ksp("com.squareup.moshi:moshi-kotlin-codegen:1.14.0")
ksp(libs.moshi.kotlin.codegen)
testImplementation(kotlin("test"))
testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.12.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1")
testImplementation(libs.kotlinx.coroutines.test)
}
8 changes: 6 additions & 2 deletions feedfinder/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id("java-library")
id("org.jetbrains.kotlin.jvm")
id("com.google.devtools.ksp") version "1.9.20-1.0.14"
}

java {
Expand All @@ -10,8 +11,11 @@ java {

dependencies {
implementation("org.jsoup:jsoup:1.17.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("com.prof18.rssparser:rssparser:6.0.4")
implementation(libs.moshi.kotlin)
implementation(libs.kotlinx.coroutines.core)
ksp(libs.moshi.kotlin.codegen)
testImplementation(kotlin("test"))
testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
testImplementation(libs.kotlinx.coroutines.test)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.jocmp.feedfinder

import java.net.URL

internal class DefaultRequest: Request {
override suspend fun fetch(url: URL): Response {
TODO("Not yet implemented")
}
}
5 changes: 0 additions & 5 deletions feedfinder/src/main/java/com/jocmp/feedfinder/Feed.kt

This file was deleted.

77 changes: 40 additions & 37 deletions feedfinder/src/main/java/com/jocmp/feedfinder/FeedFinder.kt
Original file line number Diff line number Diff line change
@@ -1,59 +1,62 @@
package com.jocmp.feedfinder

import com.jocmp.feedfinder.source.BaseSource
import com.jocmp.feedfinder.source.MetaLink
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import java.io.IOException
import com.jocmp.feedfinder.parser.Feed
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.MalformedURLException
import java.net.URI
import java.net.URL

class FeedFinder(val url: String) {
// Parser
// - XMLFeed
// - JSONFeed
// - HTML
// - Meta Links
// - Body Links
// - XMLFeedGuess (second pass)

// XML can be parsed directly if XML feed
// HTML takes response body

class FeedFinder(
val url: String,
private val request: Request = DefaultRequest()
) {
// Convert URL to HTTPS if missing
// iterate through sources
// return best matching XML
suspend fun find(): Result {
// 1. Download the request using a Java HTTP connection
// 2. If the response is an XML Feed itself, return
// 3. If the response is HTML and th
internal suspend fun find(): Result = withContext(Dispatchers.IO) {
try {
val parsedURL = URI(url)
// TODO:
// normalize URL via
// https://github.com/Ranchero-Software/RSCore/blob/a2f711d64af8f1baefdf0092f57a7f0df7f0e5e8/Sources/RSCore/Shared/String+RSCore.swift#L114
val parsedURL = URI(url).toURL()
// val response = request.fetch(url = parsedURL).parse()

// val urls = listOf(parsedURL) + variations.map { variation ->
// parsedURL.resolve(variation)
// }
//
// val documents = coroutineScope {
// urls.map { async { fetchDocument(it) } }
// .awaitAll()
// .filterNotNull()
// }

// return find(documents = documents)
// XMLFeed.parse()
// val rssChannel = RssParser().parse(response.body)
// val feeds = XML(source = BaseSource(response)).find()

val feeds = MetaLink(BaseSource(url = parsedURL)).find()

return Result.Success(title = "", feedURL = URL(""))
// if (feeds.isNotEmpty()) {
// return@withContext Result.Success(feeds.first())
// }
//
Result.Failure(error = FeedError.IO_FAILURE)
} catch (e: MalformedURLException) {
return Result.Failure(error = FeedError.IO_FAILURE)
Result.Failure(error = FeedError.IO_FAILURE)
}
}

private fun fetchDocument(url: URI): Document? {
return try {
Jsoup.connect(url.toString()).get()
} catch (e: IOException) {
return null
}
sealed class Result {
class Success(val feed: Feed) : Result()

class Failure(val error: FeedError) : Result()
}

companion object {
suspend fun find(feedURL: String): Result {
return FeedFinder(url = feedURL).find()
}


private val variations = listOf(
"feed",
"rss"
)
}
}
7 changes: 7 additions & 0 deletions feedfinder/src/main/java/com/jocmp/feedfinder/Request.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.jocmp.feedfinder

import java.net.URL

interface Request {
suspend fun fetch(url: URL): Response
}
10 changes: 10 additions & 0 deletions feedfinder/src/main/java/com/jocmp/feedfinder/Response.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jocmp.feedfinder

import com.jocmp.feedfinder.parser.FakeFeed
import com.jocmp.feedfinder.parser.Feed

class Response(val body: String?) {
suspend fun parse(): Feed {
return FakeFeed()
}
}
9 changes: 0 additions & 9 deletions feedfinder/src/main/java/com/jocmp/feedfinder/Result.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.jocmp.feedfinder.parser

class FakeFeed: Feed {
override fun isValid(): Boolean {
return false
}
}
5 changes: 5 additions & 0 deletions feedfinder/src/main/java/com/jocmp/feedfinder/parser/Feed.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.jocmp.feedfinder.parser

interface Feed {
fun isValid(): Boolean
}
35 changes: 35 additions & 0 deletions feedfinder/src/main/java/com/jocmp/feedfinder/parser/Parser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.jocmp.feedfinder.parser

internal object Parser {
class NoFeedFoundError: Throwable()

// Parse as XML
// return result if feed is valid
// if result is not valid, attempt to detect encoding
// if encoding is present and encoding detection confidence is high,
// reparse XML
// return result if feed is valid
// if result is not present, parse as JSON
// return result if feed is valid
// if no result, raise a NotFeed error

// Parser
// - XMLFeed
// - JSONFeed
// - HTML
suspend fun parse(body: String): Feed {
val xmlFeed = XMLFeed.from(body)

if (xmlFeed.isValid()) {
return xmlFeed
}

throw NoFeedFoundError()
}

// sealed class Document {
// class XMLDocument
// class HTMLDocument
// class JSONDocument
// }
}
22 changes: 22 additions & 0 deletions feedfinder/src/main/java/com/jocmp/feedfinder/parser/XMLFeed.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.jocmp.feedfinder.parser

import com.prof18.rssparser.RssParser
import com.prof18.rssparser.model.RssChannel

internal class XMLFeed(private val channel: RssChannel) : Feed {
override fun isValid(): Boolean {
return !channel.link.isNullOrBlank() &&
!channel.title.isNullOrBlank() &&
hasEntries()
}

private fun hasEntries(): Boolean {
return channel.items.isNotEmpty()
}

companion object {
suspend fun from(body: String): XMLFeed {
return XMLFeed(RssParser().parse(body))
}
}
}

This file was deleted.

This file was deleted.

26 changes: 0 additions & 26 deletions feedfinder/src/main/java/com/jocmp/feedfinder/source/Source.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.jocmp.feedfinder.sources

import com.jocmp.feedfinder.parser.Feed

//internal class BodyLink(source: Source): Source by source {
// override fun find(): List<Feed> {
// return emptyList()
// }
//}
18 changes: 18 additions & 0 deletions feedfinder/src/main/java/com/jocmp/feedfinder/sources/Guess.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.jocmp.feedfinder.sources

// val urls = listOf(parsedURL) + variations.map { variation ->
// parsedURL.resolve(variation)
// }
//
// val documents = coroutineScope {
// urls.map { async { fetchDocument(it) } }
// .awaitAll()
// .filterNotNull()
// }

// return find(documents = documents)

private val variations = listOf(
"feed",
"rss"
)
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package com.jocmp.feedfinder.source
package com.jocmp.feedfinder.sources

import com.jocmp.feedfinder.Feed
import com.jocmp.feedfinder.parser.Feed
import org.jsoup.nodes.Element
import java.net.URL

internal class MetaLink(source: Source) : Source by source {
override fun find(): List<Feed> {
if (document == null) {
return emptyList()
}

return document.select("link[rel~=alternate]")
.filter { element -> isValidLink(element) }
.map { Feed(feedURL = URL(it.attr("href"))) }
}
// override fun find(): List<Feed> {
// if (document == null) {
// return emptyList()
// }
// return emptyList()
//
// return document.select("link[rel~=alternate]")
// .filter { element -> isValidLink(element) }
// .map { XMLFeed(url = URL(it.attr("href"))) }
// }

private fun isValidLink(element: Element): Boolean {
val type = element.attr("type").lowercase()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.jocmp.feedfinder.sources

//import com.jocmp.feedfinder.Response
//
//internal class ResponseSource(response: Response): Source {
// fun createFromRequest()
//}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.jocmp.feedfinder.sources

import com.jocmp.feedfinder.parser.Feed

sealed interface Source {
suspend fun find(): List<Feed>
}
4 changes: 4 additions & 0 deletions feedfinder/src/main/java/com/jocmp/feedfinder/sources/XML.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.jocmp.feedfinder.sources

//internal class XML(source: BaseSource): Source by source {
//}
9 changes: 9 additions & 0 deletions feedfinder/src/test/java/com/jocmp/feedfinder/TestRequest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.jocmp.feedfinder

import java.net.URL

class TestRequest(private val response: Response): Request {
override suspend fun fetch(url: URL): Response {
return response
}
}
Loading

0 comments on commit 87c6cf8

Please sign in to comment.