Skip to content

Commit

Permalink
[FreshRSS] Mark read or starred (#483)
Browse files Browse the repository at this point in the history
* Reorder account delegate methods

* Add edit-tag method

* Add 'read' and 'starred' methods
  • Loading branch information
jocmp authored Nov 8, 2024
1 parent 31549bd commit 3325685
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 115 deletions.
20 changes: 10 additions & 10 deletions capy/src/main/java/com/jocmp/capy/AccountDelegate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ import com.jocmp.capy.accounts.AddFeedResult
import java.time.ZonedDateTime

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

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

suspend fun refresh(cutoffDate: ZonedDateTime? = null): 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 addStar(articleIDs: List<String>): Result<Unit>

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

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

suspend fun updateFeed(
feed: Feed,
title: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ internal class FeedbinAccountDelegate(
private val feedRecords = FeedRecords(database)
private val taggingRecords = TaggingRecords(database)

override suspend fun refresh(cutoffDate: ZonedDateTime?): Result<Unit> {
return try {
val since = articleRecords.maxUpdatedAt().toString()

refreshFeeds()
refreshTaggings()
refreshArticles(since = since)

Result.success(Unit)
} catch (exception: IOException) {
Result.failure(exception)
} catch (e: UnauthorizedError) {
Result.failure(e)
}
}

override suspend fun markRead(articleIDs: List<String>): Result<Unit> {
val entryIDs = articleIDs.map { it.toLong() }

Expand All @@ -62,74 +78,6 @@ internal class FeedbinAccountDelegate(
}
}

override suspend fun updateFeed(
feed: Feed,
title: String,
folderTitles: List<String>,
): Result<Feed> = withErrorHandling {
if (title != feed.title) {
feedbin.updateSubscription(
subscriptionID = feed.subscriptionID,
body = UpdateSubscriptionRequest(title = title)
)

feedRecords.update(
feedID = feed.id,
title = title,
)
}

val taggingIDsToDelete = taggingRecords.findFeedTaggingsToDelete(
feed = feed,
excludedTaggingNames = folderTitles
)

folderTitles.forEach { folderTitle ->
val request = CreateTaggingRequest(feed_id = feed.id, name = folderTitle)

withResult(feedbin.createTagging(request)) { tagging ->
taggingRecords.upsert(
id = tagging.id.toString(),
feedID = tagging.feed_id.toString(),
name = tagging.name
)
}
}

taggingIDsToDelete.forEach { taggingID ->
val result = feedbin.deleteTagging(taggingID = taggingID)

if (result.isSuccessful) {
taggingRecords.deleteTagging(taggingID = taggingID)
}
}

feedRecords.findBy(feed.id)
}

override suspend fun removeFeed(feed: Feed): Result<Unit> = withErrorHandling {
feedbin.deleteSubscription(subscriptionID = feed.subscriptionID)

Unit
}

override suspend fun fetchFullContent(article: Article): Result<String> {
return try {
val url = article.extractedContentURL!!

val result = feedbin.fetchExtractedContent(url = url.toString())
val responseBody = result.body()

if (result.isSuccessful && responseBody != null) {
return Result.success(responseBody.content)
} else {
return Result.failure(Throwable("Error extracting article"))
}
} catch (e: Exception) {
Result.failure(e)
}
}

override suspend fun addStar(articleIDs: List<String>): Result<Unit> {
val entryIDs = articleIDs.map { it.toLong() }

Expand Down Expand Up @@ -191,18 +139,70 @@ internal class FeedbinAccountDelegate(
}
}

override suspend fun refresh(cutoffDate: ZonedDateTime?): Result<Unit> {
override suspend fun updateFeed(
feed: Feed,
title: String,
folderTitles: List<String>,
): Result<Feed> = withErrorHandling {
if (title != feed.title) {
feedbin.updateSubscription(
subscriptionID = feed.subscriptionID,
body = UpdateSubscriptionRequest(title = title)
)

feedRecords.update(
feedID = feed.id,
title = title,
)
}

val taggingIDsToDelete = taggingRecords.findFeedTaggingsToDelete(
feed = feed,
excludedTaggingNames = folderTitles
)

folderTitles.forEach { folderTitle ->
val request = CreateTaggingRequest(feed_id = feed.id, name = folderTitle)

withResult(feedbin.createTagging(request)) { tagging ->
taggingRecords.upsert(
id = tagging.id.toString(),
feedID = tagging.feed_id.toString(),
name = tagging.name
)
}
}

taggingIDsToDelete.forEach { taggingID ->
val result = feedbin.deleteTagging(taggingID = taggingID)

if (result.isSuccessful) {
taggingRecords.deleteTagging(taggingID = taggingID)
}
}

feedRecords.findBy(feed.id)
}

override suspend fun removeFeed(feed: Feed): Result<Unit> = withErrorHandling {
feedbin.deleteSubscription(subscriptionID = feed.subscriptionID)

Unit
}

override suspend fun fetchFullContent(article: Article): Result<String> {
return try {
val since = articleRecords.maxUpdatedAt().toString()
val url = article.extractedContentURL!!

refreshFeeds()
refreshTaggings()
refreshArticles(since = since)
val result = feedbin.fetchExtractedContent(url = url.toString())
val responseBody = result.body()

Result.success(Unit)
} catch (exception: IOException) {
Result.failure(exception)
} catch (e: UnauthorizedError) {
if (result.isSuccessful && responseBody != null) {
return Result.success(responseBody.content)
} else {
return Result.failure(Throwable("Error extracting article"))
}
} catch (e: Exception) {
Result.failure(e)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import com.jocmp.capy.AccountDelegate
import com.jocmp.capy.Article
import com.jocmp.capy.Feed
import com.jocmp.capy.accounts.AddFeedResult
import com.jocmp.capy.accounts.feedbin.FeedbinAccountDelegate.Companion.MAX_CREATE_UNREAD_LIMIT
import com.jocmp.capy.accounts.withErrorHandling
import com.jocmp.capy.articles.ArticleContent
import com.jocmp.capy.common.TimeHelpers
import com.jocmp.capy.common.UnauthorizedError
Expand Down Expand Up @@ -36,18 +38,6 @@ internal class ReaderAccountDelegate(
private val articleContent = ArticleContent(httpClient)
private val articleRecords = ArticleRecords(database)

override suspend fun addFeed(
url: String,
title: String?,
folderTitles: List<String>?
): AddFeedResult {
return AddFeedResult.Failure(error = AddFeedResult.AddFeedError.NetworkError())
}

override suspend fun addStar(articleIDs: List<String>): Result<Unit> {
return Result.failure(Throwable(""))
}

override suspend fun refresh(cutoffDate: ZonedDateTime?): Result<Unit> {
return try {
val since = articleRecords.maxUpdatedAt().toEpochSecond()
Expand All @@ -63,16 +53,33 @@ internal class ReaderAccountDelegate(
}
}

override suspend fun removeStar(articleIDs: List<String>): Result<Unit> {
return Result.failure(Throwable(""))
}

override suspend fun markRead(articleIDs: List<String>): Result<Unit> {
return Result.failure(Throwable(""))
return withErrorHandling {
articleIDs.chunked(MAX_CREATE_UNREAD_LIMIT).map { batchIDs ->
editTag(ids = batchIDs, addTag = Stream.READ)
}
Unit
}
}

override suspend fun markUnread(articleIDs: List<String>): Result<Unit> {
return Result.failure(Throwable(""))
return editTag(ids = articleIDs, removeTag = Stream.READ)
}

override suspend fun addStar(articleIDs: List<String>): Result<Unit> {
return editTag(ids = articleIDs, addTag = Stream.STARRED)
}

override suspend fun removeStar(articleIDs: List<String>): Result<Unit> {
return editTag(ids = articleIDs, removeTag = Stream.STARRED)
}

override suspend fun addFeed(
url: String,
title: String?,
folderTitles: List<String>?
): AddFeedResult {
return AddFeedResult.Failure(error = AddFeedResult.AddFeedError.NetworkError())
}

override suspend fun updateFeed(
Expand Down Expand Up @@ -206,7 +213,11 @@ internal class ReaderAccountDelegate(
count = MAX_PAGINATED_ITEM_LIMIT,
)

val result = response.body() ?: return
val result = response.body()

if (result == null || result.itemRefs.isEmpty()) {
return
}

coroutineScope {
launch {
Expand Down Expand Up @@ -263,7 +274,30 @@ internal class ReaderAccountDelegate(
}
}

private suspend fun editTag(
ids: List<String>,
addTag: Stream? = null,
removeTag: Stream? = null,
): Result<Unit> {
return withErrorHandling {
withPostToken {
googleReader.editTag(
ids,
postToken = postToken.get(),
addTag = addTag?.id,
removeTag = removeTag?.id
)
}

Unit
}
}

private suspend fun <T> withPostToken(handler: suspend () -> Response<T>): Response<T> {
if (postToken.get() == null) {
fetchToken()
}

val response = handler()

val isBadToken = response
Expand All @@ -276,20 +310,23 @@ internal class ReaderAccountDelegate(
return response
}

fetchToken()

return handler()
}

private suspend fun fetchToken() {
try {
postToken.set(googleReader.token().body())

return handler()
} catch (exception: IOException) {
return response
// continue
}
}

private fun taggingID(subscription: Subscription, category: Category): String {
return "${subscription.id}:${category.id}"
}


companion object {
const val MAX_PAGINATED_ITEM_LIMIT = 100
}
Expand Down
Loading

0 comments on commit 3325685

Please sign in to comment.