Skip to content

Commit

Permalink
Re-add importer/exporter logic
Browse files Browse the repository at this point in the history
Readds the importer/exporter logic for local accounts
  • Loading branch information
jocmp committed Jul 2, 2024
1 parent 8e65f29 commit c72ea07
Show file tree
Hide file tree
Showing 28 changed files with 751 additions and 45 deletions.
3 changes: 0 additions & 3 deletions .bundle/config

This file was deleted.

7 changes: 5 additions & 2 deletions .github/workflows/deploy-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ jobs:
- name: Set up Java
uses: actions/[email protected]
with:
distribution: 'zulu'
java-version: '17'
distribution: "zulu"
java-version: "17"
- name: Setup build dependencies
env:
BUNDLE_______DEPLOYMENT: "frozen true"
BUNDLE_DEPLOYMENT: "true"
run: make deps
- name: Configure Git user
run: |
Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
source "https://rubygems.org"

gem "fastlane"
gem "fastlane", "~> 2.221"
32 changes: 16 additions & 16 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ GEM
base64
nkf
rexml
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.909.0)
aws-sdk-core (3.191.6)
aws-partitions (1.950.0)
aws-sdk-core (3.200.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.78.0)
aws-sdk-core (~> 3, >= 3.191.0)
aws-sdk-kms (1.87.0)
aws-sdk-core (~> 3, >= 3.199.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.146.1)
aws-sdk-core (~> 3, >= 3.191.0)
aws-sdk-s3 (1.155.0)
aws-sdk-core (~> 3, >= 3.199.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.0)
Expand Down Expand Up @@ -68,7 +68,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.3.1)
fastlane (2.220.0)
fastlane (2.221.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
Expand Down Expand Up @@ -147,24 +147,24 @@ GEM
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.5)
http-cookie (1.0.6)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.7.2)
jwt (2.8.1)
jwt (2.8.2)
base64
mini_magick (4.12.0)
mini_magick (4.13.1)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.4.0)
multipart-post (2.4.1)
nanaimo (0.3.0)
naturally (2.2.1)
nkf (0.2.0)
optparse (0.4.0)
optparse (0.5.0)
os (1.1.4)
plist (3.7.1)
public_suffix (5.0.5)
public_suffix (6.0.0)
rake (13.2.1)
representable (3.2.0)
declarative (< 0.1.0)
Expand Down Expand Up @@ -214,7 +214,7 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
fastlane
fastlane (~> 2.221)

BUNDLED WITH
2.4.21
54 changes: 54 additions & 0 deletions app/src/main/java/com/jocmp/capyreader/transfers/OPMLExporter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.jocmp.capyreader.transfers

import android.content.Context
import android.content.Intent
import com.jocmp.capy.Account
import com.jocmp.capyreader.R
import com.jocmp.capyreader.common.fileURI
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption

class OPMLExporter(
private val context: Context,
) {
suspend fun export(account: Account) = withContext(Dispatchers.IO) {
val exports = File(context.filesDir, "transfers")
exports.mkdirs()
val source = File(exports, "source.xml").apply {
writeText(account.opmlDocument())
}
val export = File(exports, "subscriptions.xml")
val target = export.toPath()

if (!source.exists()) {
return@withContext
}

val result = Files.copy(source.toPath(), target, StandardCopyOption.REPLACE_EXISTING)

source.delete()

withContext(Dispatchers.Main) {
try {
val uri = context.fileURI(result.toFile())
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/xml"
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

context.startActivity(
Intent.createChooser(
shareIntent,
context.getString(R.string.transfers_export_subscriptions)
)
)
} catch (e: IllegalArgumentException) {
// no-op
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class AddFeedStateHolder(
withContext(Dispatchers.IO) {
_loading.value = true

val result = account.addFeed(url)
val result = account.addFeed(url = url)
_loading.value = false

when (result) {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,5 @@
<string name="account_source_feedbin">Feedbin</string>
<string name="account_source_local">Local</string>
<string name="add_account_title">Add Account</string>
<string name="transfers_export_subscriptions">Export Subscriptions</string>
</resources>
34 changes: 32 additions & 2 deletions capy/src/main/java/com/jocmp/capy/Account.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import com.jocmp.capy.accounts.FeedbinAccountDelegate
import com.jocmp.capy.accounts.FeedbinOkHttpClient
import com.jocmp.capy.accounts.LocalAccountDelegate
import com.jocmp.capy.accounts.Source
import com.jocmp.capy.accounts.asOPML
import com.jocmp.capy.common.sortedByTitle
import com.jocmp.capy.db.Database
import com.jocmp.capy.persistence.ArticleRecords
import com.jocmp.capy.persistence.FeedRecords
import com.jocmp.feedbinclient.Feedbin
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import java.io.File
import java.net.URI

data class Account(
Expand All @@ -24,6 +27,7 @@ data class Account(
Source.LOCAL -> LocalAccountDelegate(
database = database
)

Source.FEEDBIN -> FeedbinAccountDelegate(
database = database,
feedbin = Feedbin.forAccount(path = path, preferences = preferences)
Expand Down Expand Up @@ -54,8 +58,16 @@ data class Account(
.sortedByTitle()
}

suspend fun addFeed(url: String): AddFeedResult {
return delegate.addFeed(url)
suspend fun addFeed(
url: String,
title: String? = null,
folderTitles: List<String>? = null
): AddFeedResult {
return delegate.addFeed(
url = url,
title = title,
folderTitles = folderTitles
)
}

suspend fun editFeed(form: EditFeedFormEntry): Result<Feed> {
Expand Down Expand Up @@ -140,6 +152,24 @@ data class Account(
suspend fun fetchFullContent(article: Article): Result<String> {
return delegate.fetchFullContent(article)
}

suspend fun opmlDocument(): String {
return OPMLFile(this).opmlDocument()
}

internal suspend fun asOPML(): String {
var opml = ""

feeds.first().forEach { feed ->
opml += feed.asOPML(indentLevel = 2)
}

folders.first().forEach { folder ->
opml += folder.asOPML(indentLevel = 2)
}

return opml
}
}

private fun Feedbin.Companion.forAccount(path: URI, preferences: AccountPreferences) =
Expand Down
12 changes: 11 additions & 1 deletion capy/src/main/java/com/jocmp/capy/AccountDelegate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@ package com.jocmp.capy
import com.jocmp.capy.accounts.AddFeedResult

interface AccountDelegate {
suspend fun addFeed(url: String): AddFeedResult
suspend fun addFeed(
url: String,
title: String?,
folderTitles: List<String>?
): AddFeedResult

suspend fun addStar(articleIDs: List<String>): Result<Unit>

suspend fun refresh(): Result<Unit>

suspend fun removeStar(articleIDs: List<String>): Result<Unit>

suspend fun markRead(articleIDs: List<String>): Result<Unit>

suspend fun markUnread(articleIDs: List<String>): Result<Unit>

suspend fun updateFeed(feed: Feed, title: String, folderTitles: List<String>): Result<Feed>

suspend fun removeFeed(feed: Feed): Result<Unit>
Expand Down
25 changes: 25 additions & 0 deletions capy/src/main/java/com/jocmp/capy/OPMLFile.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.jocmp.capy

class OPMLFile(
val account: Account
) {
suspend fun opmlDocument(): String {
val openingText = """
|<?xml version="1.0" encoding="UTF-8"?>
|<!-- OPML generated by Capy Reader -->
|<opml version="1.1">
| <head>
| <title>Capy Reader Export</title>
| </head>
| <body>
|
""".trimMargin()

val closingText = """
| </body>
|</opml>
""".trimMargin()

return openingText + account.asOPML() + closingText
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,11 @@ internal class FeedbinAccountDelegate(
}
}

override suspend fun addFeed(url: String): AddFeedResult {
override suspend fun addFeed(
url: String,
title: String?,
folderTitles: List<String>?
): AddFeedResult {
return try {
val response = feedbin.createSubscription(CreateSubscriptionRequest(feed_url = url))
val subscription = response.body()
Expand Down
37 changes: 26 additions & 11 deletions capy/src/main/java/com/jocmp/capy/accounts/LocalAccountDelegate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ class LocalAccountDelegate(
return Result.success(Unit)
}

override suspend fun addFeed(url: String): AddFeedResult {
override suspend fun addFeed(
url: String,
title: String?,
folderTitles: List<String>?
): AddFeedResult {
try {
val response = feedFinder.find(url = url)
val feeds = response.getOrDefault(emptyList())
Expand All @@ -46,11 +50,13 @@ class LocalAccountDelegate(
return AddFeedResult.MultipleChoices(choices)
} else if (feeds.size == 1) {
val resultFeed = feeds.first()
upsertFeed(resultFeed)
upsertFeed(resultFeed, title = title)

val feed = feedRecords.findBy(id = resultFeed.feedURL.toString())

return if (feed != null) {
upsertFolders(feed, folderTitles)

AddFeedResult.Success(feed)
} else {
AddFeedResult.Failure(AddFeedResult.AddFeedError.SaveFailure())
Expand All @@ -77,13 +83,7 @@ class LocalAccountDelegate(
excludedTaggingNames = folderTitles
)

folderTitles.forEach { folderTitle ->
taggingRecords.upsert(
id = "${feed.id}:$folderTitle",
feedID = feed.id,
name = folderTitle
)
}
upsertFolders(feed, folderTitles = folderTitles)

taggingRecords.deleteTaggings(taggingIDsToDelete)

Expand Down Expand Up @@ -164,18 +164,33 @@ class LocalAccountDelegate(
}
}

private fun upsertFeed(feed: ParserFeed) {
private fun upsertFeed(
feed: ParserFeed,
title: String?,
) {
val feedURL = feed.feedURL.toString()

database.feedsQueries.upsert(
id = feedURL,
subscription_id = feedURL,
title = feed.name,
title = title ?: feed.name,
feed_url = feedURL,
site_url = feed.siteURL?.toString(),
favicon_url = feed.faviconURL?.toString()
)
}

private fun upsertFolders(feed: Feed, folderTitles: List<String>?) {
folderTitles ?: return

folderTitles.forEach { folderTitle ->
taggingRecords.upsert(
id = "${feed.id}:$folderTitle",
feedID = feed.id,
name = folderTitle
)
}
}
}

private fun cleanSummary(summary: String?): String? {
Expand Down
Loading

0 comments on commit c72ea07

Please sign in to comment.