mirror of https://github.com/readrops/Readrops.git
Add write opml test
This commit is contained in:
parent
5fe10a848c
commit
ab86bcbcc2
|
@ -54,6 +54,7 @@ dependencies {
|
|||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<opml version="2.0">
|
||||
<body>
|
||||
<outline text="Folder 1" title="Folder 1">
|
||||
<outline text="Subfolder 1" title="SubFolder 1">
|
||||
<outline text="Subfolder 1" title="Subfolder 1">
|
||||
<outline title="The Verge" xmlUrl='http://www.theverge.com/rss/index.xml' htmlUrl="http://www.theverge.com" />
|
||||
<outline title="TechCrunch" xmlUrl='https://techcrunch.com/feed/' htmlUrl="https://techcrunch.com/" />
|
||||
<outline xmlUrl='http://feeds.mashable.com/Mashable' />
|
||||
|
|
|
@ -1,22 +1,36 @@
|
|||
package com.readrops.api
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.readrops.api.opml.OPMLParser
|
||||
import com.readrops.api.utils.ParseException
|
||||
import com.readrops.db.entities.Feed
|
||||
import com.readrops.db.entities.Folder
|
||||
import io.reactivex.CompletableObserver
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class OPMLParserTest {
|
||||
|
||||
private val context: Context = InstrumentationRegistry.getInstrumentation().context
|
||||
|
||||
@get:Rule
|
||||
val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
|
||||
@Test
|
||||
fun readOpmlTest() {
|
||||
val stream = context.resources.assets.open("subscriptions.opml")
|
||||
|
@ -53,6 +67,34 @@ class OPMLParserTest {
|
|||
|
||||
@Test
|
||||
fun writeOpmlTest() {
|
||||
val filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath
|
||||
val file = File(filePath, "subscriptions.opml")
|
||||
|
||||
val outputStream: OutputStream = FileOutputStream(file)
|
||||
val foldersAndFeeds: Map<Folder?, List<Feed>> = HashMap<Folder?, List<Feed>>().apply {
|
||||
put(null, listOf(Feed("Feed1", "", "https://feed1.com"),
|
||||
Feed("Feed2", "", "https://feed2.com")))
|
||||
put(Folder("Folder1"), listOf())
|
||||
put(Folder("Folder2"), listOf(Feed("Feed3", "", "https://feed3.com"),
|
||||
Feed("Feed4", "", "https://feed4.com")))
|
||||
}
|
||||
|
||||
OPMLParser.write(foldersAndFeeds, outputStream)
|
||||
.subscribeOn(Schedulers.trampoline())
|
||||
.subscribe()
|
||||
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
||||
val inputStream = file.inputStream()
|
||||
var foldersAndFeeds2: Map<Folder?, List<Feed>>? = null
|
||||
OPMLParser.read(inputStream).subscribe { result -> foldersAndFeeds2 = result }
|
||||
|
||||
assertEquals(foldersAndFeeds.size, foldersAndFeeds2?.size)
|
||||
assertEquals(foldersAndFeeds[Folder("Folder1")]?.size, foldersAndFeeds2?.get(Folder("Folder1"))?.size)
|
||||
assertEquals(foldersAndFeeds[Folder("Folder2")]?.size, foldersAndFeeds2?.get(Folder("Folder2"))?.size)
|
||||
assertEquals(foldersAndFeeds[null]?.size, foldersAndFeeds2?.get(null)?.size)
|
||||
|
||||
inputStream.close()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.readrops.api">
|
||||
|
||||
<!-- for tests only -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application android:requestLegacyExternalStorage="true" />
|
||||
|
||||
</manifest>
|
|
@ -40,7 +40,7 @@ object OPMLParser {
|
|||
|
||||
val opml: OPML = serializer.read(OPML::class.java, fileString)
|
||||
|
||||
emitter.onSuccess(opmltoFoldersAndFeeds(opml))
|
||||
emitter.onSuccess(opmlToFoldersAndFeeds(opml))
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, e.message, e)
|
||||
emitter.onError(e)
|
||||
|
@ -49,7 +49,7 @@ object OPMLParser {
|
|||
}
|
||||
|
||||
@JvmStatic
|
||||
fun write(foldersAndFeeds: Map<Folder, List<Feed>>, outputStream: OutputStream): Completable {
|
||||
fun write(foldersAndFeeds: Map<Folder?, List<Feed>>, outputStream: OutputStream): Completable {
|
||||
return Completable.create { emitter ->
|
||||
val serializer: Serializer = Persister()
|
||||
serializer.write(foldersAndFeedsToOPML(foldersAndFeeds), outputStream)
|
||||
|
@ -58,7 +58,7 @@ object OPMLParser {
|
|||
}
|
||||
}
|
||||
|
||||
private fun opmltoFoldersAndFeeds(opml: OPML): Map<Folder?, List<Feed>> {
|
||||
private fun opmlToFoldersAndFeeds(opml: OPML): Map<Folder?, List<Feed>> {
|
||||
if (opml.version != "2.0")
|
||||
throw ParseException("Only 2.0 OPML specification is supported")
|
||||
|
||||
|
@ -84,7 +84,9 @@ object OPMLParser {
|
|||
|
||||
// The outline is a folder/category
|
||||
if ((outline.outlines != null && !outline.outlines?.isEmpty()!!) || outline.xmlUrl.isNullOrEmpty()) {
|
||||
val folder = Folder(outline.text)
|
||||
// if the outline doesn't have text or title value but contains sub outlines,
|
||||
// those sub outlines will be considered as not belonging to any folder and join the others at the top level of the hierarchy
|
||||
val folder = if (outline.name != null) Folder(outline.name) else null
|
||||
|
||||
outline.outlines?.forEach {
|
||||
val recursiveFeedsFolders = parseOutline(it)
|
||||
|
@ -100,7 +102,7 @@ object OPMLParser {
|
|||
} else { // the outline is a feed
|
||||
if (!outline.xmlUrl.isNullOrEmpty()) {
|
||||
val feed = Feed().apply {
|
||||
name = outline.title
|
||||
name = outline.name
|
||||
url = outline.xmlUrl
|
||||
siteUrl = outline.htmlUrl
|
||||
}
|
||||
|
@ -112,20 +114,27 @@ object OPMLParser {
|
|||
return foldersAndFeeds
|
||||
}
|
||||
|
||||
private fun foldersAndFeedsToOPML(foldersAndFeeds: Map<Folder, List<Feed>>): OPML {
|
||||
private fun foldersAndFeedsToOPML(foldersAndFeeds: Map<Folder?, List<Feed>>): OPML {
|
||||
val outlines = arrayListOf<Outline>()
|
||||
|
||||
for (folderAndFeeds in foldersAndFeeds) {
|
||||
val outline = Outline(folderAndFeeds.key.name)
|
||||
if (folderAndFeeds.key != null) {
|
||||
val outline = Outline(folderAndFeeds.key?.name)
|
||||
|
||||
val feedOutlines = arrayListOf<Outline>()
|
||||
folderAndFeeds.value.forEach { feed ->
|
||||
val feedOutline = Outline(feed.name, feed.url, feed.siteUrl)
|
||||
val feedOutlines = arrayListOf<Outline>()
|
||||
for (feed in folderAndFeeds.value) {
|
||||
val feedOutline = Outline(feed.name, feed.url, feed.siteUrl)
|
||||
|
||||
feedOutlines += feedOutline
|
||||
feedOutlines += feedOutline
|
||||
}
|
||||
|
||||
outline.outlines = feedOutlines
|
||||
outlines += outline
|
||||
} else {
|
||||
for (feed in folderAndFeeds.value) {
|
||||
outlines += Outline(feed.name, feed.url, feed.siteUrl)
|
||||
}
|
||||
}
|
||||
|
||||
outline.outlines = feedOutlines
|
||||
outlines += outline
|
||||
}
|
||||
|
||||
val head = Head("Subscriptions")
|
||||
|
|
|
@ -5,8 +5,8 @@ import org.simpleframework.xml.ElementList
|
|||
import org.simpleframework.xml.Root
|
||||
|
||||
@Root(name = "outline", strict = false)
|
||||
data class Outline(@field:Attribute(required = false) var title: String?,
|
||||
@field:Attribute(required = false) var text: String?,
|
||||
data class Outline(@field:Attribute(required = false) private var title: String?,
|
||||
@field:Attribute(required = false) private var text: String?,
|
||||
@field:Attribute(required = false) var type: String?,
|
||||
@field:Attribute(required = false) var xmlUrl: String?,
|
||||
@field:Attribute(required = false) var htmlUrl: String?,
|
||||
|
@ -23,7 +23,10 @@ data class Outline(@field:Attribute(required = false) var title: String?,
|
|||
null,
|
||||
null)
|
||||
|
||||
constructor(title: String) : this(title, null, null, null, null, null)
|
||||
constructor(title: String?) : this(title, title, null, null, null, null)
|
||||
|
||||
constructor(title: String, xmlUrl: String, htmlUrl: String) : this(title, title, "rss", xmlUrl, htmlUrl, null)
|
||||
constructor(title: String?, xmlUrl: String, htmlUrl: String?) : this(title, title, "rss", xmlUrl, htmlUrl, null)
|
||||
|
||||
val name: String?
|
||||
get() = title ?: text
|
||||
}
|
Loading…
Reference in New Issue