mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
214 lines
10 KiB
Swift
214 lines
10 KiB
Swift
import Foundation
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
|
|
final class AutomaticCacheEvictionContext {
|
|
private final class Impl {
|
|
private struct CombinedSettings: Equatable {
|
|
var categoryStorageTimeout: [CacheStorageSettings.PeerStorageCategory: Int32]
|
|
var exceptions: [AccountSpecificCacheStorageSettings.Value]
|
|
}
|
|
|
|
let queue: Queue
|
|
let processingQueue: Queue
|
|
let accountManager: AccountManager<TelegramAccountManagerTypes>
|
|
let postbox: Postbox
|
|
|
|
var settingsDisposable: Disposable?
|
|
var processDisposable: Disposable?
|
|
|
|
init(queue: Queue, accountManager: AccountManager<TelegramAccountManagerTypes>, postbox: Postbox) {
|
|
self.queue = queue
|
|
self.processingQueue = Queue(name: "AutomaticCacheEviction-Processing", qos: .background)
|
|
self.accountManager = accountManager
|
|
self.postbox = postbox
|
|
|
|
self.start()
|
|
}
|
|
|
|
deinit {
|
|
self.settingsDisposable?.dispose()
|
|
self.processDisposable?.dispose()
|
|
}
|
|
|
|
func start() {
|
|
self.settingsDisposable?.dispose()
|
|
self.processDisposable?.dispose()
|
|
|
|
let cacheSettings = self.accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings])
|
|
|> map { sharedData -> CacheStorageSettings in
|
|
let cacheSettings: CacheStorageSettings
|
|
if let value = sharedData.entries[SharedDataKeys.cacheStorageSettings]?.get(CacheStorageSettings.self) {
|
|
cacheSettings = value
|
|
} else {
|
|
cacheSettings = CacheStorageSettings.defaultSettings
|
|
}
|
|
|
|
return cacheSettings
|
|
}
|
|
|
|
let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.accountSpecificCacheStorageSettings]))
|
|
let accountSpecificSettings = self.postbox.combinedView(keys: [viewKey])
|
|
|> map { views -> AccountSpecificCacheStorageSettings in
|
|
let cacheSettings: AccountSpecificCacheStorageSettings
|
|
if let view = views.views[viewKey] as? PreferencesView, let value = view.values[PreferencesKeys.accountSpecificCacheStorageSettings]?.get(AccountSpecificCacheStorageSettings.self) {
|
|
cacheSettings = value
|
|
} else {
|
|
cacheSettings = AccountSpecificCacheStorageSettings.defaultSettings
|
|
}
|
|
|
|
return cacheSettings
|
|
}
|
|
|
|
self.settingsDisposable = (combineLatest(queue: self.queue,
|
|
cacheSettings,
|
|
accountSpecificSettings
|
|
)
|
|
|> map { cacheSettings, accountSpecificSettings -> CombinedSettings in
|
|
return CombinedSettings(
|
|
categoryStorageTimeout: cacheSettings.categoryStorageTimeout,
|
|
exceptions: accountSpecificSettings.peerStorageTimeoutExceptions
|
|
)
|
|
}
|
|
|> distinctUntilChanged
|
|
|> deliverOn(self.queue)).start(next: { [weak self] combinedSettings in
|
|
self?.restart(settings: combinedSettings)
|
|
})
|
|
}
|
|
|
|
private func restart(settings: CombinedSettings) {
|
|
self.processDisposable?.dispose()
|
|
|
|
let processingQueue = self.processingQueue
|
|
let postbox = self.postbox
|
|
let mediaBox = self.postbox.mediaBox
|
|
|
|
let _ = processingQueue
|
|
let _ = mediaBox
|
|
|
|
self.processDisposable = (self.postbox.mediaBox.storageBox.allPeerIds()
|
|
|> mapToSignal { peerIds -> Signal<Never, NoError> in
|
|
return postbox.transaction { transaction -> [PeerId: CacheStorageSettings.PeerStorageCategory] in
|
|
var channelCategoryMapping: [PeerId: CacheStorageSettings.PeerStorageCategory] = [:]
|
|
for peerId in peerIds {
|
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
|
var category: CacheStorageSettings.PeerStorageCategory = .channels
|
|
if let peer = transaction.getPeer(peerId) as? TelegramChannel, case .group = peer.info {
|
|
category = .groups
|
|
}
|
|
channelCategoryMapping[peerId] = category
|
|
}
|
|
}
|
|
|
|
return channelCategoryMapping
|
|
}
|
|
|> mapToSignal { channelCategoryMapping -> Signal<Never, NoError> in
|
|
var signals: Signal<Never, NoError> = .complete()
|
|
|
|
let listSignal = Signal<PeerId, NoError> { subscriber in
|
|
for peerId in peerIds {
|
|
subscriber.putNext(peerId)
|
|
}
|
|
|
|
subscriber.putCompletion()
|
|
|
|
return EmptyDisposable
|
|
}
|
|
|
|
signals = listSignal |> mapToQueue { peerId -> Signal<Never, NoError> in
|
|
let timeout: Int32
|
|
if let value = settings.exceptions.first(where: { $0.key == peerId }) {
|
|
timeout = value.value
|
|
} else {
|
|
switch peerId.namespace {
|
|
case Namespaces.Peer.CloudUser, Namespaces.Peer.SecretChat:
|
|
timeout = settings.categoryStorageTimeout[.privateChats] ?? Int32.max
|
|
case Namespaces.Peer.CloudGroup:
|
|
timeout = settings.categoryStorageTimeout[.groups] ?? Int32.max
|
|
default:
|
|
if let category = channelCategoryMapping[peerId], case .groups = category {
|
|
timeout = settings.categoryStorageTimeout[.groups] ?? Int32.max
|
|
} else {
|
|
timeout = settings.categoryStorageTimeout[.channels] ?? Int32.max
|
|
}
|
|
}
|
|
}
|
|
|
|
if timeout == Int32.max {
|
|
return .complete()
|
|
}
|
|
|
|
let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - timeout
|
|
//let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
|
|
|
return mediaBox.storageBox.all(peerId: peerId)
|
|
|> mapToSignal { peerResourceIds -> Signal<Never, NoError> in
|
|
return Signal { subscriber in
|
|
var isCancelled = false
|
|
|
|
processingQueue.justDispatch {
|
|
var removeIds: [MediaResourceId] = []
|
|
var removeRawIds: [Data] = []
|
|
var localCounter = 0
|
|
for resourceId in peerResourceIds {
|
|
localCounter += 1
|
|
if localCounter % 100 == 0 {
|
|
if isCancelled {
|
|
subscriber.putCompletion()
|
|
return
|
|
}
|
|
}
|
|
|
|
removeRawIds.append(resourceId)
|
|
let id = MediaResourceId(String(data: resourceId, encoding: .utf8)!)
|
|
let resourceTimestamp = mediaBox.resourceUsageWithInfo(id: id)
|
|
if resourceTimestamp != 0 && resourceTimestamp < minPeerTimestamp {
|
|
removeIds.append(id)
|
|
}
|
|
}
|
|
|
|
if !removeIds.isEmpty {
|
|
Logger.shared.log("AutomaticCacheEviction", "peer \(peerId): cleaning \(removeIds.count) resources")
|
|
|
|
let _ = mediaBox.removeCachedResourcesWithResult(removeIds).start(next: { actualIds in
|
|
var actualRawIds: [Data] = []
|
|
for id in actualIds {
|
|
if let data = id.stringRepresentation.data(using: .utf8) {
|
|
actualRawIds.append(data)
|
|
}
|
|
}
|
|
|
|
mediaBox.storageBox.remove(ids: actualRawIds)
|
|
|
|
subscriber.putCompletion()
|
|
})
|
|
} else {
|
|
subscriber.putCompletion()
|
|
}
|
|
}
|
|
|
|
return ActionDisposable {
|
|
isCancelled = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return signals
|
|
}
|
|
}).start()
|
|
}
|
|
}
|
|
|
|
private let queue: Queue
|
|
private let impl: QueueLocalObject<Impl>
|
|
|
|
init(postbox: Postbox, accountManager: AccountManager<TelegramAccountManagerTypes>) {
|
|
let queue = Queue(name: "AutomaticCacheEviction")
|
|
self.queue = queue
|
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
|
return Impl(queue: queue, accountManager: accountManager, postbox: postbox)
|
|
})
|
|
}
|
|
}
|