Skip to content

Commit

Permalink
example
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzhirkevich committed Jul 30, 2024
1 parent c835ee6 commit c972dbf
Show file tree
Hide file tree
Showing 26 changed files with 2,658 additions and 643 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,53 +49,51 @@ private class DotLottieCompositionSpec(
override val key: String = "zip_${archive.contentHashCode()}_${animationId.orEmpty()}"

@OptIn(InternalCompottieApi::class)
override suspend fun load(cacheKey : Any?): LottieComposition {
override suspend fun load(): LottieComposition {
return withContext(ioDispatcher()) {
LottieComposition.getOrCreate(cacheKey) {
val fileSystem = FakeFileSystem()
val path = "lottie".toPath()
val fileSystem = FakeFileSystem()
val path = "lottie".toPath()

fileSystem.write(path) {
write(archive)
}
fileSystem.write(path) {
write(archive)
}

val entries = fileSystem.listZipEntries(path)
val entries = fileSystem.listZipEntries(path)

val zipSystem = ZipFileSystem(fileSystem, entries, path)
val zipSystem = ZipFileSystem(fileSystem, entries, path)

val manifestPath = entries.keys.firstOrNull { it.name == "manifest.json" }
val manifestPath = entries.keys.firstOrNull { it.name == "manifest.json" }

if (manifestPath != null) {
if (manifestPath != null) {

val manifest = DotLottieJson.decodeFromString<DotLottieManifest>(
zipSystem.read(manifestPath).decodeToString()
)
val manifest = DotLottieJson.decodeFromString<DotLottieManifest>(
zipSystem.read(manifestPath).decodeToString()
)

val animation = checkNotNull(manifest.animations.firstOrNull()){
"dotLottie animation folder is empty"
}
val animation = checkNotNull(manifest.animations.firstOrNull()) {
"dotLottie animation folder is empty"
}

val anim = zipSystem.read("animations/${animationId ?: animation.id}.json".toPath())
val anim = zipSystem.read("animations/${animationId ?: animation.id}.json".toPath())

LottieComposition.parse(anim.decodeToString()).apply {
speed = animation.speed
if (animation.loop) {
iterations = Compottie.IterateForever
}
prepareAssets(DotLottieAssetsManager(zipSystem, manifestPath.parent))
LottieComposition.parse(anim.decodeToString()).apply {
speed = animation.speed
if (animation.loop) {
iterations = Compottie.IterateForever
}
} else {
val animPath = entries.keys.first { it.name.endsWith(".json", true) }
val anim = zipSystem.read(animPath)

LottieComposition.parse(anim.decodeToString()).apply {
prepareAssets(
DotLottieAssetsManager(
zipFileSystem = zipSystem,
root = animPath.parent
)
prepareAssets(DotLottieAssetsManager(zipSystem, manifestPath.parent))
}
} else {
val animPath = entries.keys.first { it.name.endsWith(".json", true) }
val anim = zipSystem.read(animPath)

LottieComposition.parse(anim.decodeToString()).apply {
prepareAssets(
DotLottieAssetsManager(
zipFileSystem = zipSystem,
root = animPath.parent
)
}
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package io.github.alexzhirkevich.compottie

import androidx.compose.ui.util.fastForEach
import io.github.alexzhirkevich.compottie.DiskLruCache.Editor
import kotlinx.atomicfu.atomic
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized
import kotlinx.coroutines.CoroutineDispatcher
Expand Down Expand Up @@ -133,7 +132,7 @@ internal class DiskLruCache(
private var operationsSinceRewrite = 0
private var journalWriter: BufferedSink? = null
private var hasJournalErrors = false
private val initialized = atomic(false)
private var initialized = false
private var closed = false
private var mostRecentTrimFailed = false
private var mostRecentRebuildFailed = false
Expand All @@ -146,8 +145,8 @@ internal class DiskLruCache(
}
}

fun initialize() {
if (initialized.compareAndSet(false, true)) {
private fun initialize() {
if (!initialized) {

// If a temporary file exists, delete it.
fileSystem.delete(journalFileTmp)
Expand All @@ -167,7 +166,7 @@ internal class DiskLruCache(
try {
readJournal()
processJournal()
initialized.value = true
initialized = true
return
} catch (_: IOException) {
// The journal is corrupt.
Expand All @@ -184,7 +183,7 @@ internal class DiskLruCache(
}

writeJournal()
initialized.value = true
initialized = true
}
}

Expand Down Expand Up @@ -345,7 +344,7 @@ internal class DiskLruCache(
* Returns a snapshot of the entry named [key], or null if it doesn't exist or is not currently
* readable. If a value is returned, it is moved to the head of the LRU queue.
*/
operator fun get(key: String): Snapshot? {
operator fun get(key: String): Snapshot? = synchronized(lock) {
checkNotClosed()
validateKey(key)
initialize()
Expand Down Expand Up @@ -558,7 +557,7 @@ internal class DiskLruCache(

/** Closes this cache. Stored values will remain on the filesystem. */
override fun close() = synchronized(lock) {
if (!initialized.value || closed) {
if (!initialized || closed) {
closed = true
return
}
Expand All @@ -577,7 +576,7 @@ internal class DiskLruCache(
}

fun flush() = synchronized(lock) {
if (!initialized.value) return
if (!initialized) return

checkNotClosed()
trimToSize()
Expand Down Expand Up @@ -631,7 +630,7 @@ internal class DiskLruCache(
private fun launchCleanup() {
cleanupScope.launch {
synchronized(lock) {
if (initialized.value || closed) return@launch
if (initialized || closed) return@launch
try {
trimToSize()
} catch (_: IOException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import io.ktor.util.toByteArray
import kotlinx.coroutines.withContext
import okio.Path

@OptIn(InternalCompottieApi::class)
private val NetworkLock = MapMutex()

@OptIn(InternalCompottieApi::class)
internal suspend fun networkLoad(
client: HttpClient,
Expand All @@ -16,32 +19,37 @@ internal suspend fun networkLoad(
url: String
): Pair<Path?, ByteArray?> {
return withContext(ioDispatcher()) {
try {
NetworkLock.withLock(url) {
try {
cacheStrategy.load(url)?.let {
return@withContext cacheStrategy.path(url) to it
try {
cacheStrategy.load(url)?.let {
return@withLock cacheStrategy.path(url) to it
}
} catch (_: Throwable) {
}
} catch (_: Throwable) {
}

val ktorUrl = try {
Url(url)
} catch (t: URLParserException) {
return@withContext null to null
}
val ktorUrl = try {
Url(url)
} catch (t: URLParserException) {
return@withLock null to null
}

val bytes = request(client, ktorUrl).execute().bodyAsChannel().toByteArray()
val bytes = request(client, ktorUrl).execute().bodyAsChannel().toByteArray()

try {
cacheStrategy.save(url, bytes)?.let {
return@withContext it to bytes
try {
cacheStrategy.save(url, bytes)?.let {
return@withLock it to bytes
}
} catch (e: Throwable) {
Compottie.logger?.error(
"${this::class.simpleName} failed to cache downloaded asset",
e
)
}
} catch (e: Throwable) {
Compottie.logger?.error("${this::class.simpleName} failed to cache downloaded asset", e)
null to bytes
} catch (t: Throwable) {
null to null
}
null to bytes
} catch (t: Throwable) {
null to null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,65 +54,13 @@ private class NetworkCompositionSpec(
private val fontManager = NetworkFontManager(client, request, cacheStrategy)

@OptIn(InternalCompottieApi::class)
override suspend fun load(cacheKey : Any?): LottieComposition {
override suspend fun load(): LottieComposition {
return withContext(ioDispatcher()) {
mainMutex.withLock { mutexByUrl.getOrPut(url) { Mutex() } }.withLock {
try {
LottieComposition.getOrCreate(cacheKey) {
try {
Compottie.logger?.info("Looking for animation in cache...")
cacheStrategy.load(url)?.let {
Compottie.logger?.info("Animation was found in cache. Parsing...")
return@getOrCreate it.decodeToLottieComposition(format).also {
Compottie.logger?.info("Animation was successfully loaded from cache")
}
} ?: run {
Compottie.logger?.info("Animation wasn't found in cache")
}
} catch (t : UnsupportedFileSystemException) {
Compottie.logger?.info("File system cache is disabled for this strategy on the current platform")
} catch (_: Throwable) {
Compottie.logger?.warn("Failed to load or decode animation from cache")
}

Compottie.logger?.info("Fetching animation from web...")
val (_, bytes) = networkLoad(client, cacheStrategy, request, url)

val bytes = try {
val response = request(client, Url(url)).execute()

if (!response.status.isSuccess()) {
Compottie.logger?.warn("Animation request failed with ${response.status.value} status code")
throw ClientRequestException(response, response.bodyAsText())
}

response.bodyAsChannel().toByteArray()
} catch (t : ClientRequestException){
Compottie.logger?.warn("Animation request failed with ${t.response.status.value} status code")
throw t
}
Compottie.logger?.info("Animation was loaded from web. Parsing...")

val composition = bytes.decodeToLottieComposition(format)
Compottie.logger?.info("Animation was successfully loaded from web. Caching...")

try {
cacheStrategy.save(url, bytes)
Compottie.logger?.info("Animation was successfully saved to cache")
} catch (t : UnsupportedFileSystemException) {
Compottie.logger?.info("File system cache is disabled for this strategy on the current platform")
} catch (t: Throwable) {
Compottie.logger?.error(
"Failed to cache animation",
t
)
}
composition
}
} finally {
mainMutex.withLock {
mutexByUrl.remove(url)
}
}
checkNotNull(bytes?.decodeToLottieComposition(format)){
"Failed to load animation $url"
}.apply {
launch {
prepareAssets(assetsManager)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public fun rememberLottieComposition(
null -> null
else -> key
}
specInstance.load(k)
LottieComposition.getOrCreate(k, specInstance::load)
}
result.complete(composition)
} catch (c: CancellationException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public interface LottieCompositionSpec {
* */
public val key : String?

public suspend fun load(cacheKey : Any? = null) : LottieComposition
public suspend fun load() : LottieComposition

public companion object {

Expand All @@ -39,11 +39,9 @@ private value class JsonStringImpl(
get() = "string_${jsonString.hashCode()}"

@OptIn(InternalCompottieApi::class)
override suspend fun load(cacheKey: Any?): LottieComposition {
override suspend fun load(): LottieComposition {
return withContext(ioDispatcher()) {
LottieComposition.getOrCreate(cacheKey) {
LottieComposition.parse(jsonString)
}
LottieComposition.parse(jsonString)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ internal class LruMap<T : Any>(
private val limit : () -> Int,
) : MutableMap<Any, T> by delegate {

private val suspendGetOrPutMutex = Mutex()
@OptIn(InternalCompottieApi::class)
private val suspendGetOrPutMutex = MapMutex()
private val lock = SynchronizedObject()

override fun put(key: Any, value: T): T? = synchronized(lock) {
Expand Down Expand Up @@ -44,16 +45,10 @@ internal class LruMap<T : Any>(
}
}

suspend fun getOrPutSuspend(key: Any?, put: suspend () -> T): T {
return suspendGetOrPutMutex.withLock {
if (key == null)
return@withLock put()

getRaw(key) ?: run {
val v = put()
putRaw(key, v)
v
}
@OptIn(InternalCompottieApi::class)
suspend fun getOrPutSuspend(key: Any, put: suspend () -> T): T {
return suspendGetOrPutMutex.withLock(key) {
getRaw(key) ?: put().also { putRaw(key, it) }
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.github.alexzhirkevich.compottie

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

@InternalCompottieApi
public class MapMutex {
private val lock = Mutex()
private val mapLock = mutableMapOf<Any, Mutex>()

public suspend fun <T> withLock(key: Any, action: suspend () -> T): T {
return try {
lock.withLock { mapLock.getOrPut(key, ::Mutex) }.withLock { action() }
} finally {
lock.withLock { mapLock.remove(key) }
}
}
}
Loading

0 comments on commit c972dbf

Please sign in to comment.