mastodon-ios/Mastodon/State/AppContext.swift

212 lines
8.2 KiB
Swift
Raw Normal View History

2021-01-27 07:50:13 +01:00
//
// AppContext.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-1-27.
//
import os.log
import UIKit
import Combine
import CoreData
import CoreDataStack
2021-06-01 08:07:44 +02:00
import AlamofireImage
import Kingfisher
2021-01-27 07:50:13 +01:00
class AppContext: ObservableObject {
var disposeBag = Set<AnyCancellable>()
@Published var viewStateStore = ViewStateStore()
let coreDataStack: CoreDataStack
let managedObjectContext: NSManagedObjectContext
let backgroundManagedObjectContext: NSManagedObjectContext
2021-01-28 09:10:30 +01:00
let apiService: APIService
2021-02-03 09:01:08 +01:00
let authenticationService: AuthenticationService
2021-03-15 07:40:10 +01:00
let emojiService: EmojiService
let audioPlaybackService = AudioPlaybackService()
2021-03-15 07:40:10 +01:00
let videoPlaybackService = VideoPlaybackService()
2021-03-10 12:12:53 +01:00
let statusPrefetchingService: StatusPrefetchingService
let statusPublishService = StatusPublishService()
2021-04-25 06:48:29 +02:00
let notificationService: NotificationService
let settingService: SettingService
let blockDomainService: BlockDomainService
let photoLibraryService = PhotoLibraryService()
let placeholderImageCacheService = PlaceholderImageCacheService()
let blurhashImageCacheService = BlurhashImageCacheService()
let statusContentCacheService = StatusContentCacheService()
2021-01-27 07:50:13 +01:00
let documentStore: DocumentStore
private var documentStoreSubscription: AnyCancellable!
let overrideTraitCollection = CurrentValueSubject<UITraitCollection?, Never>(nil)
init() {
let _coreDataStack = CoreDataStack()
let _managedObjectContext = _coreDataStack.persistentContainer.viewContext
let _backgroundManagedObjectContext = _coreDataStack.persistentContainer.newBackgroundContext()
coreDataStack = _coreDataStack
managedObjectContext = _managedObjectContext
backgroundManagedObjectContext = _backgroundManagedObjectContext
2021-01-28 09:10:30 +01:00
let _apiService = APIService(backgroundManagedObjectContext: _backgroundManagedObjectContext)
apiService = _apiService
2021-04-25 06:48:29 +02:00
let _authenticationService = AuthenticationService(
2021-02-03 09:01:08 +01:00
managedObjectContext: _managedObjectContext,
backgroundManagedObjectContext: _backgroundManagedObjectContext,
apiService: _apiService
)
2021-04-25 06:48:29 +02:00
authenticationService = _authenticationService
2021-01-28 09:10:30 +01:00
2021-03-15 07:40:10 +01:00
emojiService = EmojiService(
apiService: apiService
)
2021-03-10 12:12:53 +01:00
statusPrefetchingService = StatusPrefetchingService(
apiService: _apiService
)
let _notificationService = NotificationService(
2021-04-27 11:27:03 +02:00
apiService: _apiService,
2021-04-25 06:48:29 +02:00
authenticationService: _authenticationService
)
notificationService = _notificationService
settingService = SettingService(
apiService: _apiService,
authenticationService: _authenticationService,
notificationService: _notificationService
)
2021-03-10 12:12:53 +01:00
2021-04-30 08:55:02 +02:00
blockDomainService = BlockDomainService(
backgroundManagedObjectContext: _backgroundManagedObjectContext,
authenticationService: _authenticationService
)
2021-01-27 07:50:13 +01:00
documentStore = DocumentStore()
documentStoreSubscription = documentStore.objectWillChange
.receive(on: DispatchQueue.main)
.sink { [unowned self] in
self.objectWillChange.send()
}
backgroundManagedObjectContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave, object: backgroundManagedObjectContext)
.sink { [weak self] notification in
guard let self = self else { return }
self.managedObjectContext.perform {
self.managedObjectContext.mergeChanges(fromContextDidSave: notification)
}
}
.store(in: &disposeBag)
}
}
2021-06-01 08:07:44 +02:00
extension AppContext {
typealias ByteCount = Int
static let byteCountFormatter: ByteCountFormatter = {
let formatter = ByteCountFormatter()
return formatter
}()
private static let purgeCacheWorkingQueue = DispatchQueue(label: "org.joinmastodon.app.AppContext.purgeCacheWorkingQueue")
2021-06-01 08:07:44 +02:00
func purgeCache() -> AnyPublisher<ByteCount, Never> {
Publishers.MergeMany([
AppContext.purgeAlamofireImageCache(),
AppContext.purgeKingfisherCache(),
AppContext.purgeTemporaryDirectory(),
])
.reduce(0, +)
.eraseToAnyPublisher()
}
private static func purgeAlamofireImageCache() -> AnyPublisher<ByteCount, Never> {
Future<ByteCount, Never> { promise in
AppContext.purgeCacheWorkingQueue.async {
// clean image cache for AlamofireImage
let diskBytes = ImageDownloader.defaultURLCache().currentDiskUsage
ImageDownloader.defaultURLCache().removeAllCachedResponses()
let currentDiskBytes = ImageDownloader.defaultURLCache().currentDiskUsage
let purgedDiskBytes = max(0, diskBytes - currentDiskBytes)
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: purge AlamofireImage cache bytes: %ld -> %ld (%ld)", ((#file as NSString).lastPathComponent), #line, #function, diskBytes, currentDiskBytes, purgedDiskBytes)
promise(.success(purgedDiskBytes))
}
}
.eraseToAnyPublisher()
}
private static func purgeKingfisherCache() -> AnyPublisher<ByteCount, Never> {
Future<ByteCount, Never> { promise in
KingfisherManager.shared.cache.calculateDiskStorageSize { result in
switch result {
case .success(let diskBytes):
KingfisherManager.shared.cache.clearCache()
KingfisherManager.shared.cache.calculateDiskStorageSize { currentResult in
switch currentResult {
case .success(let currentDiskBytes):
let purgedDiskBytes = max(0, Int(diskBytes) - Int(currentDiskBytes))
promise(.success(purgedDiskBytes))
case .failure:
promise(.success(0))
}
}
case .failure:
promise(.success(0))
}
}
}
.eraseToAnyPublisher()
}
private static func purgeTemporaryDirectory() -> AnyPublisher<ByteCount, Never> {
Future<ByteCount, Never> { promise in
AppContext.purgeCacheWorkingQueue.async {
let fileManager = FileManager.default
let temporaryDirectoryURL = fileManager.temporaryDirectory
let resourceKeys = Set<URLResourceKey>([.fileSizeKey, .isDirectoryKey])
guard let directoryEnumerator = fileManager.enumerator(
at: temporaryDirectoryURL,
includingPropertiesForKeys: Array(resourceKeys),
options: .skipsHiddenFiles
) else {
promise(.success(0))
return
}
var fileURLs: [URL] = []
var totalFileSizeInBytes = 0
for case let fileURL as URL in directoryEnumerator {
guard let resourceValues = try? fileURL.resourceValues(forKeys: resourceKeys),
let isDirectory = resourceValues.isDirectory else {
continue
}
guard !isDirectory else {
continue
}
fileURLs.append(fileURL)
totalFileSizeInBytes += resourceValues.fileSize ?? 0
}
for fileURL in fileURLs {
try? fileManager.removeItem(at: fileURL)
}
promise(.success(totalFileSizeInBytes))
}
}
.eraseToAnyPublisher()
}
//
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: purge temporary directory success", ((#file as NSString).lastPathComponent), #line, #function)
// }
}