Skip to content

fix(greader): stop sync from undoing mark all as read#1272

Closed
amerkay wants to merge 1 commit intoReadYouApp:mainfrom
amerkay:read-unread-sync-bug-fix
Closed

fix(greader): stop sync from undoing mark all as read#1272
amerkay wants to merge 1 commit intoReadYouApp:mainfrom
amerkay:read-unread-sync-bug-fix

Conversation

@amerkay
Copy link
Copy Markdown

@amerkay amerkay commented May 2, 2026

Summary

  • Fixes "Mark all as read" silently leaving items unread on the server, observed against a Miniflux backend over the Google Reader API.
  • Switches the bulk path in GoogleReaderRssService.markAsRead from per-ID edit-tag calls to the server's mark-all-as-read stream endpoint.

Why

The old flow pushed only IDs the local database knew about. Articles unread on the server but missing locally (pagination, archive, mid-sync) stayed unread, and the next sync flipped them back to unread on the client - producing the 515 → 0 → 1796 → 0 → 515 cycle described in #1271 and mentioned/related to #1157 (comment)

Scope

  • Bulk mark-as-read (with optional before timestamp): now uses googleReaderAPI.markAllAsRead(streamId, ts) against AllItems / Feed / Category.
  • Single-article toggle and mark-as-unread: unchanged - still per-ID editTag.

The endpoint is part of the standard Google Reader API. Verified against Miniflux only; behavior against other Google Reader / FreshRSS backends has not been tested but should be no worse than the current per-ID approach.

Closes #1271

Tested

  • Miniflux account: mark all as read → app shows 0, web UI shows 0, pull-to-refresh keeps it at 0.
  • Mark a single article unread → still works.
  • Mark all in a single feed → only that feed clears.
  • Mark all in a group → only that group clears.
  • Mark as read with the "1 day / 3 days / 7 days" filter → only items older than the cutoff are marked.

markAsRead pushed only locally-known IDs via edit-tag, so unread items only on the server stayed unread and got flipped back on the next sync. Use the bulk mark-all-as-read endpoint instead.
@amerkay
Copy link
Copy Markdown
Author

amerkay commented May 3, 2026

This broke it when you use the "Mark all as read" feature when offline.

Stack trace when offline

Version: 0.16.1

Device: Google Pixel 6a

System: Android 16 (API 36)

Stack trace:

java.net.UnknownHostException: Unable to resolve host "miniflux.example.com": No address associated with hostname
	at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:124)
	at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
	at java.net.InetAddress.getAllByName(InetAddress.java:1152)
	at okhttp3.Dns$Companion$DnsSystem.lookup(Dns.kt:49)
	at okhttp3.internal.connection.RouteSelector.resetNextInetSocketAddress(RouteSelector.kt:169)
	at okhttp3.internal.connection.RouteSelector.nextProxy(RouteSelector.kt:132)
	at okhttp3.internal.connection.RouteSelector.next(RouteSelector.kt:74)
	at okhttp3.internal.connection.RealRoutePlanner.planConnect(RealRoutePlanner.kt:157)
	at okhttp3.internal.connection.RealRoutePlanner.plan(RealRoutePlanner.kt:69)
	at okhttp3.internal.connection.FastFallbackExchangeFinder.launchTcpConnect(FastFallbackExchangeFinder.kt:118)
	at okhttp3.internal.connection.FastFallbackExchangeFinder.find(FastFallbackExchangeFinder.kt:62)
	at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:268)
	at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
	at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
	at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:84)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
	at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:65)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
	at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:205)
	at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:543)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1154)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:652)
	at java.lang.Thread.run(Thread.java:1563)
	Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@a628baa, Dispatchers.IO]

I see there was an attempt to fix:

// launch {
// val toBeReadRemote = localReadIds.intersect(remoteUnreadIds.await())
// if (toBeReadRemote.isNotEmpty()) {
// googleReaderAPI.editTag(
// itemIds = toBeReadRemote.toList(),
// mark = GoogleReaderAPI.Stream.Read.tag,
// )
// }
// }

I will investigate and report back with a better pull request.

@amerkay amerkay closed this May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GReader "mark all as read" leaves items unread on the server (Google Reader on Miniflux)

1 participant