Swiftgram/submodules/MeshAnimationCache/Sources/MeshAnimationCache.swift
2021-11-16 20:21:38 +04:00

183 lines
7.6 KiB
Swift

import Foundation
import LottieMeshSwift
import Postbox
import SwiftSignalKit
import GZip
import AppBundle
public final class MeshAnimationCache {
private final class Item {
var isPending: Bool = false
let disposable = MetaDisposable()
var readyPath: String?
var animation: MeshAnimation?
}
private let mediaBox: MediaBox
private var items: [String: Item] = [:]
public init(mediaBox: MediaBox) {
self.mediaBox = mediaBox
}
public func get(resource: MediaResource) -> MeshAnimation? {
if let item = self.items[resource.id.stringRepresentation] {
if let animation = item.animation {
return animation
} else if let readyPath = item.readyPath, let data = try? Data(contentsOf: URL(fileURLWithPath: readyPath), options: [.alwaysMapped]) {
let buffer = MeshReadBuffer(data: data)
let animation = MeshAnimation.read(buffer: buffer)
item.animation = animation
return animation
} else {
return nil
}
} else {
let item = Item()
self.items[resource.id.stringRepresentation] = item
let path = self.mediaBox.cachedRepresentationPathForId(resource.id.stringRepresentation, representationId: "mesh-animation", keepDuration: .general)
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) {
let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data))
item.readyPath = path
item.animation = animation
return animation
} else {
self.cache(item: item, resource: resource)
return nil
}
}
}
public func get(bundleName: String) -> MeshAnimation? {
if let item = self.items[bundleName] {
if let animation = item.animation {
return animation
} else if let readyPath = item.readyPath, let data = try? Data(contentsOf: URL(fileURLWithPath: readyPath), options: [.alwaysMapped]) {
let buffer = MeshReadBuffer(data: data)
let animation = MeshAnimation.read(buffer: buffer)
item.animation = animation
return animation
} else {
return nil
}
} else {
let item = Item()
self.items[bundleName] = item
let path = self.mediaBox.cachedRepresentationPathForId(bundleName, representationId: "mesh-animation", keepDuration: .general)
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) {
let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data))
item.readyPath = path
item.animation = animation
return animation
} else {
self.cache(item: item, bundleName: bundleName)
return nil
}
}
}
private func cache(item: Item, resource: MediaResource) {
let mediaBox = self.mediaBox
item.isPending = true
item.disposable.set((self.mediaBox.resourceData(resource)
|> filter { data in
return data.complete
}
|> take(1)
|> mapToSignal { data -> Signal<(MeshAnimation, String)?, NoError> in
return Signal { subscriber in
guard let zippedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.alwaysMapped]) else {
subscriber.putNext(nil)
subscriber.putCompletion()
return EmptyDisposable
}
let jsonData = TGGUnzipData(zippedData, 1 * 1024 * 1024) ?? zippedData
if let tempFile = generateMeshAnimation(data: jsonData) {
mediaBox.storeCachedResourceRepresentation(resource.id.stringRepresentation, representationId: "mesh-animation", keepDuration: .general, tempFile: tempFile, completion: { path in
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) {
let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data))
subscriber.putNext((animation, path))
subscriber.putCompletion()
} else {
subscriber.putNext(nil)
subscriber.putCompletion()
}
})
} else {
subscriber.putNext(nil)
subscriber.putCompletion()
return EmptyDisposable
}
return EmptyDisposable
}
|> runOn(Queue.concurrentBackgroundQueue())
}
|> deliverOnMainQueue).start(next: { [weak self] animationAndPath in
guard let strongSelf = self else {
return
}
if let animationAndPath = animationAndPath {
if let item = strongSelf.items[resource.id.stringRepresentation] {
item.isPending = false
item.animation = animationAndPath.0
item.readyPath = animationAndPath.1
}
}
}))
}
private func cache(item: Item, bundleName: String) {
let mediaBox = self.mediaBox
item.isPending = true
item.disposable.set((Signal<(MeshAnimation, String)?, NoError> { subscriber in
guard let path = getAppBundle().path(forResource: bundleName, ofType: "tgs") else {
subscriber.putNext(nil)
subscriber.putCompletion()
return EmptyDisposable
}
guard let zippedData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) else {
subscriber.putNext(nil)
subscriber.putCompletion()
return EmptyDisposable
}
let jsonData = TGGUnzipData(zippedData, 1 * 1024 * 1024) ?? zippedData
if let tempFile = generateMeshAnimation(data: jsonData) {
mediaBox.storeCachedResourceRepresentation(bundleName, representationId: "mesh-animation", keepDuration: .general, tempFile: tempFile, completion: { path in
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) {
let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data))
subscriber.putNext((animation, path))
subscriber.putCompletion()
} else {
subscriber.putNext(nil)
subscriber.putCompletion()
}
})
} else {
subscriber.putNext(nil)
subscriber.putCompletion()
return EmptyDisposable
}
return EmptyDisposable
}
|> runOn(Queue.concurrentDefaultQueue())
|> deliverOnMainQueue).start(next: { [weak self] animationAndPath in
guard let strongSelf = self else {
return
}
if let animationAndPath = animationAndPath {
if let item = strongSelf.items[bundleName] {
item.isPending = false
item.animation = animationAndPath.0
item.readyPath = animationAndPath.1
}
}
}))
}
}