Add write opml test
This commit is contained in:
parent
5fe10a848c
commit
ab86bcbcc2
@ -54,6 +54,7 @@ dependencies {
|
|||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
|
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
|
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
|
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<opml version="2.0">
|
<opml version="2.0">
|
||||||
<body>
|
<body>
|
||||||
<outline text="Folder 1" title="Folder 1">
|
<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="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 title="TechCrunch" xmlUrl='https://techcrunch.com/feed/' htmlUrl="https://techcrunch.com/" />
|
||||||
<outline xmlUrl='http://feeds.mashable.com/Mashable' />
|
<outline xmlUrl='http://feeds.mashable.com/Mashable' />
|
||||||
|
@ -1,22 +1,36 @@
|
|||||||
package com.readrops.api
|
package com.readrops.api
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Environment
|
||||||
|
import android.util.Log
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.rule.GrantPermissionRule
|
||||||
import com.readrops.api.opml.OPMLParser
|
import com.readrops.api.opml.OPMLParser
|
||||||
import com.readrops.api.utils.ParseException
|
import com.readrops.api.utils.ParseException
|
||||||
import com.readrops.db.entities.Feed
|
import com.readrops.db.entities.Feed
|
||||||
import com.readrops.db.entities.Folder
|
import com.readrops.db.entities.Folder
|
||||||
|
import io.reactivex.CompletableObserver
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import junit.framework.TestCase.assertEquals
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import junit.framework.TestCase.assertTrue
|
||||||
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class OPMLParserTest {
|
class OPMLParserTest {
|
||||||
|
|
||||||
private val context: Context = InstrumentationRegistry.getInstrumentation().context
|
private val context: Context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun readOpmlTest() {
|
fun readOpmlTest() {
|
||||||
val stream = context.resources.assets.open("subscriptions.opml")
|
val stream = context.resources.assets.open("subscriptions.opml")
|
||||||
@ -53,6 +67,34 @@ class OPMLParserTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun writeOpmlTest() {
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
9
api/src/debug/AndroidManifest.xml
Normal file
9
api/src/debug/AndroidManifest.xml
Normal file
@ -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)
|
val opml: OPML = serializer.read(OPML::class.java, fileString)
|
||||||
|
|
||||||
emitter.onSuccess(opmltoFoldersAndFeeds(opml))
|
emitter.onSuccess(opmlToFoldersAndFeeds(opml))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(TAG, e.message, e)
|
Log.d(TAG, e.message, e)
|
||||||
emitter.onError(e)
|
emitter.onError(e)
|
||||||
@ -49,7 +49,7 @@ object OPMLParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@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 ->
|
return Completable.create { emitter ->
|
||||||
val serializer: Serializer = Persister()
|
val serializer: Serializer = Persister()
|
||||||
serializer.write(foldersAndFeedsToOPML(foldersAndFeeds), outputStream)
|
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")
|
if (opml.version != "2.0")
|
||||||
throw ParseException("Only 2.0 OPML specification is supported")
|
throw ParseException("Only 2.0 OPML specification is supported")
|
||||||
|
|
||||||
@ -84,7 +84,9 @@ object OPMLParser {
|
|||||||
|
|
||||||
// The outline is a folder/category
|
// The outline is a folder/category
|
||||||
if ((outline.outlines != null && !outline.outlines?.isEmpty()!!) || outline.xmlUrl.isNullOrEmpty()) {
|
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 {
|
outline.outlines?.forEach {
|
||||||
val recursiveFeedsFolders = parseOutline(it)
|
val recursiveFeedsFolders = parseOutline(it)
|
||||||
@ -100,7 +102,7 @@ object OPMLParser {
|
|||||||
} else { // the outline is a feed
|
} else { // the outline is a feed
|
||||||
if (!outline.xmlUrl.isNullOrEmpty()) {
|
if (!outline.xmlUrl.isNullOrEmpty()) {
|
||||||
val feed = Feed().apply {
|
val feed = Feed().apply {
|
||||||
name = outline.title
|
name = outline.name
|
||||||
url = outline.xmlUrl
|
url = outline.xmlUrl
|
||||||
siteUrl = outline.htmlUrl
|
siteUrl = outline.htmlUrl
|
||||||
}
|
}
|
||||||
@ -112,20 +114,27 @@ object OPMLParser {
|
|||||||
return foldersAndFeeds
|
return foldersAndFeeds
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun foldersAndFeedsToOPML(foldersAndFeeds: Map<Folder, List<Feed>>): OPML {
|
private fun foldersAndFeedsToOPML(foldersAndFeeds: Map<Folder?, List<Feed>>): OPML {
|
||||||
val outlines = arrayListOf<Outline>()
|
val outlines = arrayListOf<Outline>()
|
||||||
|
|
||||||
for (folderAndFeeds in foldersAndFeeds) {
|
for (folderAndFeeds in foldersAndFeeds) {
|
||||||
val outline = Outline(folderAndFeeds.key.name)
|
if (folderAndFeeds.key != null) {
|
||||||
|
val outline = Outline(folderAndFeeds.key?.name)
|
||||||
|
|
||||||
val feedOutlines = arrayListOf<Outline>()
|
val feedOutlines = arrayListOf<Outline>()
|
||||||
folderAndFeeds.value.forEach { feed ->
|
for (feed in folderAndFeeds.value) {
|
||||||
val feedOutline = Outline(feed.name, feed.url, feed.siteUrl)
|
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")
|
val head = Head("Subscriptions")
|
||||||
|
@ -5,8 +5,8 @@ import org.simpleframework.xml.ElementList
|
|||||||
import org.simpleframework.xml.Root
|
import org.simpleframework.xml.Root
|
||||||
|
|
||||||
@Root(name = "outline", strict = false)
|
@Root(name = "outline", strict = false)
|
||||||
data class Outline(@field:Attribute(required = false) var title: String?,
|
data class Outline(@field:Attribute(required = false) private var title: String?,
|
||||||
@field:Attribute(required = false) var text: String?,
|
@field:Attribute(required = false) private var text: String?,
|
||||||
@field:Attribute(required = false) var type: String?,
|
@field:Attribute(required = false) var type: String?,
|
||||||
@field:Attribute(required = false) var xmlUrl: String?,
|
@field:Attribute(required = false) var xmlUrl: String?,
|
||||||
@field:Attribute(required = false) var htmlUrl: String?,
|
@field:Attribute(required = false) var htmlUrl: String?,
|
||||||
@ -23,7 +23,10 @@ data class Outline(@field:Attribute(required = false) var title: String?,
|
|||||||
null,
|
null,
|
||||||
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…
x
Reference in New Issue
Block a user