From a06e186a392180814ae0c0bffa32d26365f4dea1 Mon Sep 17 00:00:00 2001 From: uttiya10 <56562649+uttiya10@users.noreply.github.com> Date: Tue, 4 May 2021 22:22:02 -0400 Subject: [PATCH 1/4] Bundle multiple garbage collection requests for TrackingContext Previously, garbage collection would trigger on every access for a tracking context. Now, multiple requests will be bundled into 1 request for faster performance --- NineAnimator/Models/NineAnimator.swift | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/NineAnimator/Models/NineAnimator.swift b/NineAnimator/Models/NineAnimator.swift index d0334409..6dc74519 100644 --- a/NineAnimator/Models/NineAnimator.swift +++ b/NineAnimator/Models/NineAnimator.swift @@ -103,6 +103,8 @@ class NineAnimator: Alamofire.SessionDelegate { /// Container for the cached references to tracking contexts fileprivate var trackingContextReferences = [AnimeLink: WeakRef]() + fileprivate var trackingContextGarbageCollectionTimer: Timer? + /// An in-memory cache of all the loaded anime @AtomicProperty fileprivate var cachedAnimeMap = [AnimeLink: (Date, Anime)]() @@ -216,9 +218,9 @@ extension NineAnimator { /// Retrieve the tracking context for the anime func trackingContext(for anime: AnimeLink) -> TrackingContext { - // Remove dead contexts - collectGarbage() - // Return the context dirctly if it has been created + // Schedule garbage collection + scheduleGarbageCollection() + // Return the context directly if it has been created if let context: TrackingContext = NineAnimator.globalConfigurationQueue.sync(execute: { trackingContextReferences[anime]?.object }) { return context } @@ -263,7 +265,22 @@ extension NineAnimator { register(service: Simkl(self)) } - /// Remove all expired weak references + /// Schedules garbage collection to remove all expired weak `TrackingContext` references + func scheduleGarbageCollection() { + DispatchQueue.main.async { + guard self.trackingContextGarbageCollectionTimer == nil else { return } + // Trigger collection after 15 seconds to bundle multiple collection requests into one + self.trackingContextGarbageCollectionTimer = Timer.scheduledTimer( + withTimeInterval: TimeInterval(15), + repeats: false + ) { _ in + self.collectGarbage() + self.trackingContextGarbageCollectionTimer = nil + } + } + } + + /// Removes all expired weak `TrackingContext` references private func collectGarbage() { NineAnimator.globalConfigurationQueue.sync(flags: [ .barrier ]) { let before = self.trackingContextReferences.count From 0df57c78907b0c6e872ff76e46efb742d1ca84ec Mon Sep 17 00:00:00 2001 From: uttiya10 <56562649+uttiya10@users.noreply.github.com> Date: Tue, 4 May 2021 22:24:13 -0400 Subject: [PATCH 2/4] Use a single TrackingContext reference for every EpisodeTableViewCell --- .../Controllers/Player Scene/AnimeViewController.swift | 3 ++- .../Anime Scene/DetailedEpisodeTableViewCell.swift | 9 +++++++-- .../Views/Anime Scene/EpisodeTableViewCell.swift | 10 +++++++--- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/NineAnimator/Controllers/Player Scene/AnimeViewController.swift b/NineAnimator/Controllers/Player Scene/AnimeViewController.swift index 800add9f..d4ed1c3c 100644 --- a/NineAnimator/Controllers/Player Scene/AnimeViewController.swift +++ b/NineAnimator/Controllers/Player Scene/AnimeViewController.swift @@ -378,6 +378,7 @@ extension AnimeViewController { cell.makeThemable() cell.setPresenting( episode, + trackingContext: anime!.trackingContext, additionalInformation: detailedEpisodeInfo, parent: self ) { [weak self] _ in @@ -389,7 +390,7 @@ extension AnimeViewController { } else { let cell = tableView.dequeueReusableCell(withIdentifier: "anime.episode", for: indexPath) as! EpisodeTableViewCell cell.makeThemable() - cell.setPresenting(episode, parent: self) + cell.setPresenting(episode, trackingContext: anime!.trackingContext, parent: self) return cell } } diff --git a/NineAnimator/Views/Anime Scene/DetailedEpisodeTableViewCell.swift b/NineAnimator/Views/Anime Scene/DetailedEpisodeTableViewCell.swift index f6d6152f..74cd8ef0 100644 --- a/NineAnimator/Views/Anime Scene/DetailedEpisodeTableViewCell.swift +++ b/NineAnimator/Views/Anime Scene/DetailedEpisodeTableViewCell.swift @@ -40,6 +40,8 @@ class DetailedEpisodeTableViewCell: UITableViewCell { private(set) var episodeLink: EpisodeLink? + private(set) var trackingContext: TrackingContext? + private(set) var episodeInformation: Anime.AdditionalEpisodeLinkInformation? private var progress: Float { @@ -66,10 +68,12 @@ class DetailedEpisodeTableViewCell: UITableViewCell { /// Initialize this cell func setPresenting(_ episodeLink: EpisodeLink, + trackingContext: TrackingContext, additionalInformation info: Anime.AdditionalEpisodeLinkInformation, parent: AnimeViewController, didResizeCell: @escaping (DetailedEpisodeTableViewCell) -> Void) { self.episodeLink = episodeLink + self.trackingContext = trackingContext self.offlineAccessButton.setPresenting(episodeLink, delegate: parent) self.offlineAccessButton.delegate = parent self.onStateChange = didResizeCell @@ -120,9 +124,10 @@ class DetailedEpisodeTableViewCell: UITableViewCell { } @objc private func onProgressUpdate() { - guard let episodeLink = episodeLink else { return } + guard let episodeLink = episodeLink, + let trackingContext = trackingContext else { return } - let currentProgress = Float(episodeLink.playbackProgress) + let currentProgress = Float(trackingContext.playbackProgress(for: episodeLink)) DispatchQueue.main.async { [weak self] in diff --git a/NineAnimator/Views/Anime Scene/EpisodeTableViewCell.swift b/NineAnimator/Views/Anime Scene/EpisodeTableViewCell.swift index caf34c88..0e863238 100644 --- a/NineAnimator/Views/Anime Scene/EpisodeTableViewCell.swift +++ b/NineAnimator/Views/Anime Scene/EpisodeTableViewCell.swift @@ -22,6 +22,8 @@ import UIKit class EpisodeTableViewCell: UITableViewCell { private(set) var episodeLink: EpisodeLink? + private(set) var trackingContext: TrackingContext? + var onStateChange: ((EpisodeTableViewCell) -> Void)? @IBOutlet private weak var titleLabel: UILabel! @@ -60,7 +62,7 @@ class EpisodeTableViewCell: UITableViewCell { } /// Initialize the current cell - func setPresenting(_ episodeLink: EpisodeLink, parent: AnimeViewController) { + func setPresenting(_ episodeLink: EpisodeLink, trackingContext: TrackingContext, parent: AnimeViewController) { self.episodeLink = episodeLink self.offlineAccessButton.setPresenting(episodeLink, delegate: parent) @@ -81,10 +83,12 @@ class EpisodeTableViewCell: UITableViewCell { } @objc private func onProgressUpdate() { - guard let link = episodeLink else { return } + guard let trackingContext = trackingContext, + let episodeLink = episodeLink else { return } - let currentProgress = Float(link.playbackProgress) + let currentProgress = Float(trackingContext.playbackProgress(for: episodeLink)) + print(episodeLink.playbackProgress) DispatchQueue.main.async { [weak self] in guard let self = self else { return } From 85c5299d18fa5223f5069df1ead84896c736a14b Mon Sep 17 00:00:00 2001 From: uttiya10 <56562649+uttiya10@users.noreply.github.com> Date: Wed, 5 May 2021 21:24:36 -0400 Subject: [PATCH 3/4] Automatically update metadata for animelinks in the user's subscriptions --- .../Player Scene/AnimeViewController.swift | 8 +++++ .../Models/User/User+Subscriptions.swift | 31 ++++++++++++++++++- .../Utilities/StatesSerialization.swift | 4 +-- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/NineAnimator/Controllers/Player Scene/AnimeViewController.swift b/NineAnimator/Controllers/Player Scene/AnimeViewController.swift index d4ed1c3c..f1ddd24f 100644 --- a/NineAnimator/Controllers/Player Scene/AnimeViewController.swift +++ b/NineAnimator/Controllers/Player Scene/AnimeViewController.swift @@ -268,6 +268,14 @@ extension AnimeViewController { NineAnimator.default.user.entering(anime: anime.link) NineAnimator.default.user.push() + // Update metadata for the link in the users subscription + // This is important in cases such as where Anime Sources may have initially + // provided incorrect data (ex. parsed a broken artworkURL), but later provides + // correct information (ex. user has updated the app to fix parser) + if NineAnimator.default.user.isSubscribing(anime) { + NineAnimator.default.user.updateMetadata(for: anime.link) + } + // Setup userActivity self.prepareContinuity() } diff --git a/NineAnimator/Models/User/User+Subscriptions.swift b/NineAnimator/Models/User/User+Subscriptions.swift index 5f26125e..3fb1f3c8 100644 --- a/NineAnimator/Models/User/User+Subscriptions.swift +++ b/NineAnimator/Models/User/User+Subscriptions.swift @@ -24,7 +24,7 @@ extension NineAnimatorUser { /** Returns the list of anime currently set to be notified for updates */ - var subscribedAnimes: [AnimeLink] { + private(set) var subscribedAnimes: [AnimeLink] { get { decodeIfPresent([AnimeLink].self, from: _freezer.value(forKey: Keys.subscribedAnimeList)) ?? [] } set { guard let data = encodeIfPresent(data: newValue) else { @@ -65,6 +65,12 @@ extension NineAnimatorUser { UserNotificationManager.default.lazyPersist(link) } + /// Replace the user's subscription with a new list + func replaceAllSubscriptions(with newSubscriptions: [AnimeLink]) { + Log.info("[User+Subscriptions] Replacing user subscription list") + subscribedAnimes = newSubscriptions + } + /** Move AnimeLink from one index to another index in the user's watch list */ @@ -92,6 +98,29 @@ extension NineAnimatorUser { */ func unsubscribe(anime: Anime) { unsubscribe(anime: anime.link) } + /// Updates the metadata (title, image, etc) of an animelink stored in the user's subscriptions + /// - Parameter linkWithNewMetaData: The animelink that contains the updated metadata + /// - Note: The AnimeLink's URL cannot be updated as it is used as a unique identifier + func updateMetadata(for linkWithNewMetaData: AnimeLink) { + // Find the original link in the users subscription + // This uses the animelink's link property since its used as a unique ID + guard let originalLinkIndex = subscribedAnimes.firstIndex(of: linkWithNewMetaData) else { + return Log.error("[User+Subscription] Tried updating the metadata of an animelink that does not exist in the user's subscribed anime") + } + + // Only update subscription list if metadata has changed + guard !isMetadataSame(subscribedAnimes[originalLinkIndex], linkWithNewMetaData) else { return } + + // Replace the old link + subscribedAnimes[originalLinkIndex] = linkWithNewMetaData + } + + /// Helper function to compare metadata between two animelinks + private func isMetadataSame(_ firstLink: AnimeLink, _ secondLink: AnimeLink) -> Bool { + firstLink.image == secondLink.image + && firstLink.title == secondLink.title + } + /** Remove the anime from the watch list */ diff --git a/NineAnimator/Utilities/StatesSerialization.swift b/NineAnimator/Utilities/StatesSerialization.swift index 61843890..0e698728 100644 --- a/NineAnimator/Utilities/StatesSerialization.swift +++ b/NineAnimator/Utilities/StatesSerialization.swift @@ -107,7 +107,7 @@ func merge(_ configuration: NineAnimatorUser, with fileUrl: URL, policy: NineAni var finalSubscriptionsSet = Set() configuration.subscribedAnimes.forEach { finalSubscriptionsSet.insert($0) } backupSubscriptions.forEach { finalSubscriptionsSet.insert($0) } - configuration.subscribedAnimes = finalSubscriptionsSet.map { $0 } + configuration.replaceAllSubscriptions(with: Array(finalSubscriptionsSet)) } } @@ -128,7 +128,7 @@ func replace(_ configuration: NineAnimatorUser, with fileUrl: URL) throws { // Restoring subscription list if let subscriptions = preservedStates.subscriptions { - configuration.subscribedAnimes = subscriptions + configuration.replaceAllSubscriptions(with: subscriptions) } // Restoring the tracking data From 06868d5e95ddd42c8eb47c97f6b03a4e217b6c98 Mon Sep 17 00:00:00 2001 From: uttiya10 <56562649+uttiya10@users.noreply.github.com> Date: Wed, 5 May 2021 22:04:28 -0400 Subject: [PATCH 4/4] Add Tolerance to Garbage Collection Timer --- NineAnimator/Models/NineAnimator.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/NineAnimator/Models/NineAnimator.swift b/NineAnimator/Models/NineAnimator.swift index 6dc74519..f105960f 100644 --- a/NineAnimator/Models/NineAnimator.swift +++ b/NineAnimator/Models/NineAnimator.swift @@ -277,6 +277,7 @@ extension NineAnimator { self.collectGarbage() self.trackingContextGarbageCollectionTimer = nil } + self.trackingContextGarbageCollectionTimer?.tolerance = 5 } }