Implement caching for home timeline items (IOS-176)
This commit is contained in:
parent
837d8dd329
commit
5532324f83
@ -60,6 +60,7 @@
|
||||
2AE202AC297FE19100F66E55 /* FollowersCountIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE202AB297FE19100F66E55 /* FollowersCountIntentHandler.swift */; };
|
||||
2AE202AD297FE1CD00F66E55 /* WidgetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A72813E297EC762004138C5 /* WidgetExtension.swift */; };
|
||||
2AE244482927831100BDBF7C /* UIImage+SFSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE244472927831100BDBF7C /* UIImage+SFSymbols.swift */; };
|
||||
2AF2E7BF2B19DC6E00D98917 /* FileManager+HomeTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF2E7BE2B19DC6E00D98917 /* FileManager+HomeTimeline.swift */; };
|
||||
2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198642261BF09500F0B013 /* SearchResultItem.swift */; };
|
||||
2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198648261C0B8500F0B013 /* SearchResultSection.swift */; };
|
||||
2D206B8625F5FB0900143C56 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B8525F5FB0900143C56 /* Double.swift */; };
|
||||
@ -696,6 +697,7 @@
|
||||
2AE202A9297FDDF500F66E55 /* WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetExtension.entitlements; sourceTree = "<group>"; };
|
||||
2AE202AB297FE19100F66E55 /* FollowersCountIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersCountIntentHandler.swift; sourceTree = "<group>"; };
|
||||
2AE244472927831100BDBF7C /* UIImage+SFSymbols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+SFSymbols.swift"; sourceTree = "<group>"; };
|
||||
2AF2E7BE2B19DC6E00D98917 /* FileManager+HomeTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+HomeTimeline.swift"; sourceTree = "<group>"; };
|
||||
2D198642261BF09500F0B013 /* SearchResultItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultItem.swift; sourceTree = "<group>"; };
|
||||
2D198648261C0B8500F0B013 /* SearchResultSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultSection.swift; sourceTree = "<group>"; };
|
||||
2D206B8525F5FB0900143C56 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = "<group>"; };
|
||||
@ -1898,6 +1900,7 @@
|
||||
children = (
|
||||
D8AC98772B0F62230045EC2B /* Model */,
|
||||
D8AC98752B0F61680045EC2B /* FileManager+SearchHistory.swift */,
|
||||
2AF2E7BE2B19DC6E00D98917 /* FileManager+HomeTimeline.swift */,
|
||||
);
|
||||
path = Persistence;
|
||||
sourceTree = "<group>";
|
||||
@ -3978,6 +3981,7 @@
|
||||
DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */,
|
||||
D8318A802A4466D300C0FB73 /* SettingsCoordinator.swift in Sources */,
|
||||
DB3E6FF12806D96900B035AE /* DiscoveryNewsViewModel+Diffable.swift in Sources */,
|
||||
2AF2E7BF2B19DC6E00D98917 /* FileManager+HomeTimeline.swift in Sources */,
|
||||
DB3E6FF82807C45300B035AE /* DiscoveryForYouViewModel.swift in Sources */,
|
||||
DB0F9D56283EB46200379AF8 /* ProfileHeaderView+Configuration.swift in Sources */,
|
||||
DB6746F0278F463B008A6B94 /* AutoGenerateProtocolDelegate.swift in Sources */,
|
||||
|
@ -626,7 +626,7 @@ extension SceneCoordinator: SettingsCoordinatorDelegate {
|
||||
try await self.appContext.authenticationService.signOutMastodonUser(
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
FileManager.default.invalidateHomeTimelineCache(for: authContext.mastodonAuthenticationBox.userID)
|
||||
self.setup()
|
||||
}
|
||||
|
||||
|
53
Mastodon/Persistence/FileManager+HomeTimeline.swift
Normal file
53
Mastodon/Persistence/FileManager+HomeTimeline.swift
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
extension FileManager {
|
||||
private static let cacheHomeItemsLimit: Int = 100 // max number of items to cache
|
||||
|
||||
func cachedHomeTimeline(for userId: String) throws -> [MastodonStatus] {
|
||||
guard let cachesDirectory else { return [] }
|
||||
|
||||
let filePath = Persistence.homeTimeline(userId).filepath(baseURL: cachesDirectory)
|
||||
|
||||
guard let data = try? Data(contentsOf: filePath) else { return [] }
|
||||
|
||||
do {
|
||||
let items = try JSONDecoder().decode([Mastodon.Entity.Status].self, from: data)
|
||||
|
||||
return items.map(MastodonStatus.fromEntity)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func cacheHomeTimeline(items: [MastodonStatus], for userId: String) {
|
||||
guard let cachesDirectory else { return }
|
||||
|
||||
let processableItems: [MastodonStatus]
|
||||
if items.count > Self.cacheHomeItemsLimit {
|
||||
processableItems = items.dropLast(items.count - Self.cacheHomeItemsLimit)
|
||||
} else {
|
||||
processableItems = items
|
||||
}
|
||||
|
||||
do {
|
||||
let data = try JSONEncoder().encode(processableItems.map { $0.entity })
|
||||
|
||||
let filePath = Persistence.homeTimeline(userId).filepath(baseURL: cachesDirectory)
|
||||
try data.write(to: filePath)
|
||||
} catch {
|
||||
debugPrint(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func invalidateHomeTimelineCache(for userId: String) {
|
||||
guard let cachesDirectory else { return }
|
||||
|
||||
let filePath = Persistence.homeTimeline(userId).filepath(baseURL: cachesDirectory)
|
||||
|
||||
try? removeItem(at: filePath)
|
||||
}
|
||||
}
|
@ -68,8 +68,12 @@ extension FileManager {
|
||||
}
|
||||
}
|
||||
|
||||
extension FileManager {
|
||||
public var documentsDirectory: URL? {
|
||||
return self.urls(for: .documentDirectory, in: .userDomainMask).first
|
||||
public extension FileManager {
|
||||
var documentsDirectory: URL? {
|
||||
urls(for: .documentDirectory, in: .userDomainMask).first
|
||||
}
|
||||
|
||||
var cachesDirectory: URL? {
|
||||
urls(for: .cachesDirectory, in: .userDomainMask).first
|
||||
}
|
||||
}
|
||||
|
@ -386,6 +386,7 @@ extension HomeTimelineViewController {
|
||||
@objc func signOutAction(_ sender: UIAction) {
|
||||
Task { @MainActor in
|
||||
try await context.authenticationService.signOutMastodonUser(authenticationBox: viewModel.authContext.mastodonAuthenticationBox)
|
||||
FileManager.default.invalidateHomeTimelineCache(for: viewModel.authContext.mastodonAuthenticationBox.userID)
|
||||
self.coordinator.setup()
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import GameplayKit
|
||||
import AlamofireImage
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonSDK
|
||||
|
||||
final class HomeTimelineViewModel: NSObject {
|
||||
|
||||
@ -83,6 +84,10 @@ final class HomeTimelineViewModel: NSObject {
|
||||
self.fetchedResultsController = FeedFetchedResultsController(context: context, authContext: authContext)
|
||||
self.homeTimelineNavigationBarTitleViewModel = HomeTimelineNavigationBarTitleViewModel(context: context)
|
||||
super.init()
|
||||
self.fetchedResultsController.records = (try? FileManager.default.cachedHomeTimeline(for: authContext.mastodonAuthenticationBox.userID).map {
|
||||
MastodonFeed.fromStatus($0, kind: .home)
|
||||
}) ?? []
|
||||
|
||||
homeTimelineNeedRefresh
|
||||
.sink { [weak self] _ in
|
||||
self?.loadLatestStateMachine.enter(LoadLatestState.Loading.self)
|
||||
@ -98,6 +103,18 @@ final class HomeTimelineViewModel: NSObject {
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
self.fetchedResultsController.$records
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { feeds in
|
||||
let items: [MastodonStatus] = feeds.compactMap { feed -> MastodonStatus? in
|
||||
guard let status = feed.status else { return nil }
|
||||
return status
|
||||
}
|
||||
FileManager.default.cacheHomeTimeline(items: items, for: authContext.mastodonAuthenticationBox.userID)
|
||||
})
|
||||
.store(in: &disposeBag)
|
||||
|
||||
self.fetchedResultsController.loadInitial(kind: .home)
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,14 @@ import Foundation
|
||||
|
||||
public enum Persistence {
|
||||
case searchHistory
|
||||
case homeTimeline(String)
|
||||
|
||||
private var filename: String {
|
||||
switch self {
|
||||
case .searchHistory:
|
||||
return "search_history"
|
||||
case let .homeTimeline(userId):
|
||||
return "home_timeline_\(userId)"
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user