Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ These values are part of the shipping product. Renames or recreations break upgr
| Feed address repair after the update | Unique work name `feed-address-upgrade-repair` (`UpgradeRepairWorker`); step 4 and sync catch-up record `noBackupFilesDir/upgrade_repair_notifications`; per-show progress in Room `upgrade_repair_shows`; never renamed |
| BuildConfig | Prefer `BOXLORE_*`; Gradle still dual-reads `BOXCAST_*` fallbacks |
| Episode / media IDs | `rss:` prefixes, negative RSS IDs, mediaId prefixes, and `customCacheKey` schemes stay as implemented. PI **episode supplements** also use negative episode IDs (generated under an `rss:` feed namespace) but keep `Episode.podcastId` as the Podcast Index show id — they are not `rss:` library rows. Episode identity (show ID, catalog key), recipe IDs, collisions and aliases follow [`docs/EPISODE_IDS.md`](docs/EPISODE_IDS.md) |
| Renumbered shows | A Podcast Index show ID whose details answer as another show ID is recorded in `boxlore_renumbered_shows`; its follow moves to the new show ID, and no stored episode ID, show namespace or listener row changes ([renumbered shows](docs/EPISODE_IDS.md#renumbered-shows)). `rss:` shows are never renumbered |
| Playback instance | One UI-scoped `PlaybackRepository`; routes and workers must not construct a second one |
| Object graph order | DB → `PodcastRepository` → `QueueRepository` → `PlaybackRepository` → `QueueManager` → `SmartDownloadManager` |
| Smart Queue refill | Owned by `BoxLorePlaybackService` only |
Expand Down Expand Up @@ -255,6 +256,7 @@ Historical FQCNs and preference file names remain reachable so upgrades from old
| — | `boxlore_discovery_last_good` | `PodcastRepository` (`:core:catalog` `RecommendationsLastGood`; last good `/v2` picks per surface and country; do not rename) |
| — | `boxlore_feed_reports` | `AppContainer` (`:core:catalog` `SharedPreferencesFeedReportStamps`; last broken-feed report time per show; do not rename) |
| — | `boxlore_notification_reregistrations` | `AppContainer` (`:core:catalog` `SharedPreferencesPendingNotificationRegistrations`; shows whose notifications wait to be registered again after an address change; do not rename) |
| — | `boxlore_renumbered_shows` | `AppContainer` (`:core:rss` `SharedPreferencesRenumberedShows`; Podcast Index show IDs that now answer as another show ID, old ID → new ID; follows move to the new show and its catalog reads link the old show's listener rows, see [renumbered shows](docs/EPISODE_IDS.md#renumbered-shows); do not rename) |
| `boxcast_api_config` | `boxlore_api_config` | `BoxLoreAppRoot` |
| `boxcast_referrer_prefs` | `boxlore_referrer_prefs` | `InstallReferrerManager` |

Expand Down
1 change: 1 addition & 0 deletions app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ Unacknowledged tablet launches first show an opaque alpha notice with the Home f
- The unique work names `episode-share-registration-<hash of show and catalog key>` (`EpisodeShareRegistrationWorker`), `podcast-notification-registration-<show ID>` (`fcm/PodcastNotificationRegistrationWorker`) and `feed-address-upgrade-repair` (`sync/UpgradeRepairWorker`) are permanent.
- The feed address repair's step 4 record lives in `noBackupFilesDir/upgrade_repair_notifications` (`FileUpgradeNotificationMarker`), so a restore never brings it and the restored phone registers notifications again; stable name.
- SharedPreferences file `boxlore_feed_reports` (`SharedPreferencesFeedReportStamps`, opened by `AppContainer`) holds broken-feed report times; stable name.
- SharedPreferences file `boxlore_renumbered_shows` (`SharedPreferencesRenumberedShows`, opened by `AppContainer`) records Podcast Index shows that now answer as another show ID; stable name. `AppContainer` hands it to `LocalEpisodeCatalogRepository` (catalog reads link a renumbered show's listener rows) and `SubscriptionRepository` (`RenumberingWiring`), and `PodcastRepository(onShowRenumbered = …)` moves a follow whenever a details answer names another show ([docs/EPISODE_IDS.md](../docs/EPISODE_IDS.md#renumbered-shows)).
- Build config reads `BOXLORE_API_BASE_URL`, `BOXLORE_PUBLIC_KEY`, and the public `BOXLORE_CAST_RECEIVER_ID`, with legacy API/key local-property names still accepted for existing developer environments.
- Core modules own Room filenames, DataStore names, SharedPreferences names, ranking identity, RSS IDs, and playback media IDs.
- The last known `feed_first` answer lives in `noBackupFilesDir/feed_first_flag` (`1` or `0`), and the v2 kill switches' answers in `noBackupFilesDir/kill_switch_upgrade_repair_off`, `kill_switch_address_check_off` and `kill_switch_v2_discovery_off`, so a restore or device transfer never brings another phone's answers.
Expand Down
20 changes: 20 additions & 0 deletions app/src/main/java/cx/aswin/boxlore/AppContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import cx.aswin.boxlore.core.catalog.LegacyRssRepairActivation
import cx.aswin.boxlore.core.catalog.LegacyRssRepairCatalog
import cx.aswin.boxlore.core.catalog.LegacyRssRepairRuntime
import cx.aswin.boxlore.core.catalog.PodcastRepository
import cx.aswin.boxlore.core.catalog.RenumberingWiring
import cx.aswin.boxlore.core.catalog.RolloutSwitches
import cx.aswin.boxlore.core.catalog.RoomDiscoveryEpisodeIds
import cx.aswin.boxlore.core.catalog.RoomEpisodeAliasReads
Expand Down Expand Up @@ -68,7 +69,9 @@ import cx.aswin.boxlore.core.ranking.RankingFeedbackRepository
import cx.aswin.boxlore.core.ranking.RankingRuntimeControls
import cx.aswin.boxlore.core.rss.EpisodeSupplementRepository
import cx.aswin.boxlore.core.rss.LocalEpisodeCatalogRepository
import cx.aswin.boxlore.core.rss.RenumberedShows
import cx.aswin.boxlore.core.rss.RssPodcastRepository
import cx.aswin.boxlore.core.rss.SharedPreferencesRenumberedShows
import cx.aswin.boxlore.core.rss.ports.DownloadCacheRelinker
import cx.aswin.boxlore.fcm.PodcastNotificationRegistrationWorker
import cx.aswin.boxlore.share.EpisodeShareRegistrationWorker
Expand Down Expand Up @@ -171,9 +174,19 @@ class AppContainer(
DownloadRepository.relinkDownloadCache(appContext, oldId, newId)
},
onCatalogPersisted = { id -> autoDownloadCoordinator.scanCached(id) },
renumberedShows = renumberedShows,
)
}

/**
* Shows Podcast Index renumbered (an old show ID that now answers as another), kept in
* `boxlore_renumbered_shows`: follows move to the new show and catalog reads link the old
* show's listener rows to its episodes.
*/
val renumberedShows: RenumberedShows by lazy {
SharedPreferencesRenumberedShows(appContext.getSharedPreferences(SharedPreferencesRenumberedShows.FILE_NAME, Context.MODE_PRIVATE))
}

/**
* Shared source-aware catalog for automation, foreground sync and push hydration. Every refresh
* through it follows a feed that moved for good (P4 PR 14), behind `feed-fix-moved-feed-off`.
Expand Down Expand Up @@ -242,6 +255,8 @@ class AppContainer(
telemetry = feedTelemetry,
isFollowed = { id -> database.podcastDao().getPodcast(id)?.isSubscribed == true },
),
// A show Podcast Index renumbered keeps one follow, under the show it now answers as.
onShowRenumbered = { oldId, answer -> subscriptionRepository.renumberedFollows.adoptAnswer(oldId, answer) },
)
}

Expand Down Expand Up @@ -425,6 +440,11 @@ class AppContainer(
appContext.getSharedPreferences(SharedPreferencesPendingNotificationRegistrations.FILE_NAME, Context.MODE_PRIVATE),
),
),
renumbering =
RenumberingWiring(
shows = renumberedShows,
linkEpisodes = { oldId, newId -> localEpisodeCatalogRepository.linkRenumberedShow(oldId, newId) },
),
)
}

Expand Down
Loading
Loading