Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions zipline-loader/api/android/zipline-loader.api
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public final class app/cash/zipline/loader/SignatureAlgorithmId : java/lang/Enum
}

public final class app/cash/zipline/loader/ZiplineCache : java/io/Closeable {
public synthetic fun <init> (Lapp/cash/sqldelight/db/SqlDriver;Lapp/cash/zipline/loader/internal/cache/Database;Lokio/FileSystem;Lokio/Path;JLapp/cash/zipline/loader/LoaderEventListener;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close ()V
}

Expand Down
1 change: 1 addition & 0 deletions zipline-loader/api/jvm/zipline-loader.api
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public final class app/cash/zipline/loader/SignatureAlgorithmId : java/lang/Enum
}

public final class app/cash/zipline/loader/ZiplineCache : java/io/Closeable {
public synthetic fun <init> (Lapp/cash/sqldelight/db/SqlDriver;Lapp/cash/zipline/loader/internal/cache/Database;Lokio/FileSystem;Lokio/Path;JLapp/cash/zipline/loader/LoaderEventListener;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close ()V
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import app.cash.sqldelight.db.SqlSchema
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import okio.Path

internal actual class SqlDriverFactory(
internal class AndroidSqliteDriverFactory(
private val context: Context,
) {
actual fun create(path: Path, schema: SqlSchema<QueryResult.Value<Unit>>): SqlDriver {
) : SqlDriverFactory {
override fun create(path: Path, schema: SqlSchema<QueryResult.Value<Unit>>): SqlDriver {
validateDbPath(path)
return AndroidSqliteDriver(
schema = schema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
package app.cash.zipline.loader

import android.content.Context
import app.cash.zipline.loader.internal.cache.SqlDriverFactory
import app.cash.zipline.loader.internal.cache.AndroidSqliteDriverFactory
import okio.FileSystem
import okio.Path

Expand All @@ -28,7 +28,7 @@ fun ZiplineCache(
loaderEventListener: LoaderEventListener,
): ZiplineCache {
return ZiplineCache(
sqlDriverFactory = SqlDriverFactory(context),
sqlDriverFactory = AndroidSqliteDriverFactory(context),
fileSystem = fileSystem,
directory = directory,
maxSizeInBytes = maxSizeInBytes,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2025 Cash App
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.cash.zipline.loader

import app.cash.sqldelight.Query
import app.cash.sqldelight.db.QueryResult
import app.cash.sqldelight.db.SqlCursor
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.db.SqlPreparedStatement
import okio.IOException

/**
* A SQL Driver that always throws [IOException]. Used this when creating a driver fails, such as
* when the disk is full.
*/
internal class NullSqlDriver : SqlDriver {
override fun <R> executeQuery(
identifier: Int?,
sql: String,
mapper: (SqlCursor) -> QueryResult<R>,
parameters: Int,
binders: (SqlPreparedStatement.() -> Unit)?,
) = throw IOException("NullSqlDriver")

override fun execute(
identifier: Int?,
sql: String,
parameters: Int,
binders: (SqlPreparedStatement.() -> Unit)?,
) = throw IOException("NullSqlDriver")

override fun newTransaction() = throw IOException("NullSqlDriver")

override fun currentTransaction() = throw IOException("NullSqlDriver")

override fun addListener(vararg queryKeys: String, listener: Query.Listener) = Unit

override fun removeListener(vararg queryKeys: String, listener: Query.Listener) = Unit

override fun notifyListeners(vararg queryKeys: String) = Unit

override fun close() = Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import okio.ByteString.Companion.decodeHex
import okio.Closeable
import okio.FileNotFoundException
import okio.FileSystem
import okio.IOException
import okio.Path

/**
Expand All @@ -44,15 +43,15 @@ import okio.Path
* If multiple threads in a single process operate on a cache instance simultaneously, downloads may
* be repeated but no thread will be blocked.
*/
class ZiplineCache internal constructor(
class ZiplineCache private constructor(
private val driver: SqlDriver,
private val database: Database,
private val fileSystem: FileSystem,
private val directory: Path,
private val maxSizeInBytes: Long,
private val loaderEventListener: LoaderEventListener,
private var hasWriteFailures: Boolean,
) : Closeable {
private var hasWriteFailures = false

/*
* Files are named by their SHA-256 hashes. We use a SQLite database for file metadata: which
Expand Down Expand Up @@ -192,7 +191,7 @@ class ZiplineCache internal constructor(
fileSystem.read(path) {
readByteString()
}
} catch (e: FileNotFoundException) {
} catch (_: FileNotFoundException) {
null // Might have been pruned while we were trying to read?
}

Expand All @@ -201,19 +200,14 @@ class ZiplineCache internal constructor(
try {
fileSystem.delete(path)
database.filesQueries.delete(metadata.id)
} catch (ignored: Exception) {
} catch (_: Exception) {
}
return null
}

return result
}

internal fun pin(applicationName: String, sha256: ByteString) {
val fileId = database.filesQueries.get(sha256.hex()).executeAsOneOrNull()?.id ?: return
createPinIfNotExists(applicationName, fileId)
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was unused

internal fun unpin(applicationName: String, sha256: ByteString) {
val fileId = database.filesQueries.get(sha256.hex()).executeAsOneOrNull()?.id ?: return
database.pinsQueries.delete_pin(applicationName, fileId)
Expand Down Expand Up @@ -361,11 +355,11 @@ class ZiplineCache internal constructor(
}

private fun createPinIfNotExists(
application_name: String,
file_id: Long,
applicationName: String,
fileId: Long,
) {
database.pinsQueries.get_pin(file_id, application_name).executeAsOneOrNull()
?: database.pinsQueries.create_pin(file_id, application_name)
database.pinsQueries.get_pin(fileId, applicationName).executeAsOneOrNull()
?: database.pinsQueries.create_pin(fileId, applicationName)
}

/**
Expand Down Expand Up @@ -414,7 +408,7 @@ class ZiplineCache internal constructor(
*
* It will also delete dirty files that were open when the previous run completed.
*/
internal fun initialize() {
private fun initialize() {
try {
deleteDirtyFiles()
prune()
Expand Down Expand Up @@ -459,9 +453,7 @@ class ZiplineCache internal constructor(
/** Returns the number of pins in the cache DB. */
internal fun countPins() = database.pinsQueries.count().executeAsOne().toInt()

private fun path(metadata: Files): Path {
return directory / "entry-${metadata.id}.bin"
}
private fun path(metadata: Files): Path = directory / "entry-${metadata.id}.bin"

/**
* Update file record freshAt timestamp to reflect that the manifest is still seen as fresh.
Expand Down Expand Up @@ -490,26 +482,35 @@ class ZiplineCache internal constructor(
loaderEventListener.cacheStorageFailed(applicationName, e)
}
}
}

internal fun ZiplineCache(
sqlDriverFactory: SqlDriverFactory,
fileSystem: FileSystem,
directory: Path,
maxSizeInBytes: Long,
loaderEventListener: LoaderEventListener,
): ZiplineCache {
fileSystem.createDirectories(directory, mustCreate = false)
val driver: SqlDriver = sqlDriverFactory.create(directory / "zipline.db", Database.Schema)
val database = createDatabase(driver = driver)
val cache = ZiplineCache(
driver = driver,
database = database,
fileSystem = fileSystem,
directory = directory,
maxSizeInBytes = maxSizeInBytes,
loaderEventListener = loaderEventListener,
)
cache.initialize()
return cache
internal companion object {
internal operator fun invoke(
sqlDriverFactory: SqlDriverFactory,
fileSystem: FileSystem,
directory: Path,
maxSizeInBytes: Long,
loaderEventListener: LoaderEventListener,
): ZiplineCache {
val (driver, hasWriteFailures) = try {
fileSystem.createDirectories(directory, mustCreate = false)
sqlDriverFactory.create(directory / "zipline.db", Database.Schema) to false
} catch (e: Exception) {
loaderEventListener.cacheStorageFailed(null, e)
NullSqlDriver() to true
}

val database = createDatabase(driver = driver)
val cache = ZiplineCache(
driver = driver,
database = database,
fileSystem = fileSystem,
directory = directory,
maxSizeInBytes = maxSizeInBytes,
loaderEventListener = loaderEventListener,
hasWriteFailures = hasWriteFailures,
)
cache.initialize()
return cache
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.db.SqlSchema
import okio.Path

internal expect class SqlDriverFactory {
internal interface SqlDriverFactory {
/**
* Create a SqlDriver to be used in creating and managing a SqlLite instance on disk.
*
Expand All @@ -39,11 +39,9 @@ internal fun validateDbPath(path: Path) {
}
}

internal fun createDatabase(driver: SqlDriver): Database {
return Database(
internal fun createDatabase(driver: SqlDriver): Database = Database(
driver,
filesAdapter = Files.Adapter(
file_stateAdapter = EnumColumnAdapter(),
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package app.cash.zipline.loader.internal.cache
import app.cash.sqldelight.db.QueryResult
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.db.SqlPreparedStatement
import app.cash.sqldelight.db.SqlSchema
import app.cash.zipline.ZiplineManifest
import app.cash.zipline.loader.LoaderEventListener
import app.cash.zipline.loader.ZiplineCache
Expand Down Expand Up @@ -94,7 +95,7 @@ class CacheFaultsTester {
* https://www.sqlite.org/tempfiles.html
*/
val fileNames: List<String>
get() = fileSystem.list(directory)
get() = (fileSystem.listOrNull(directory) ?: listOf())
.map { it.name }
.filterNot { it.startsWith("zipline.db-") }

Expand All @@ -104,22 +105,9 @@ class CacheFaultsTester {
*/
var storageFailureCount = 0

init {
fileSystem.createDirectories(directory)
}

suspend fun withCache(block: suspend Session.() -> Unit) {
val driver = LimitWritesSqlDriver(
testSqlDriverFactory().create(
path = directory / "zipline.db",
schema = Database.Schema,
),
)
val database = createDatabase(driver)

val cache = ZiplineCache(
driver = driver,
database = database,
sqlDriverFactory = LimitWritesSqlDriverFactory(),
fileSystem = LimitWritesFileSystem(fileSystem),
directory = directory,
maxSizeInBytes = cacheSize,
Expand All @@ -131,7 +119,6 @@ class CacheFaultsTester {
)

try {
cache.initialize()
Session(cache).block()
} finally {
cache.close()
Expand Down Expand Up @@ -197,6 +184,13 @@ class CacheFaultsTester {
private inner class LimitWritesFileSystem(
delegate: FileSystem,
) : ForwardingFileSystem(delegate) {
override fun createDirectory(dir: Path, mustCreate: Boolean) {
fileSystemWriteCount++
if (fileSystemWriteCount >= fileSystemWriteLimit) throw IOException("write limit exceeded")

super.createDirectory(dir, mustCreate)
}

override fun sink(file: Path, mustCreate: Boolean): Sink {
fileSystemWriteCount++
if (fileSystemWriteCount >= fileSystemWriteLimit) throw IOException("write limit exceeded")
Expand Down Expand Up @@ -229,6 +223,25 @@ class CacheFaultsTester {
}
}

private inner class LimitWritesSqlDriverFactory : SqlDriverFactory {
override fun create(
path: Path,
schema: SqlSchema<QueryResult.Value<Unit>>,
): SqlDriver {
fileSystemWriteCount++
if (fileSystemWriteCount >= fileSystemWriteLimit) {
throw SqlFaultException("write limit exceeded")
}

return LimitWritesSqlDriver(
testSqlDriverFactory().create(
path = directory / "zipline.db",
schema = Database.Schema,
),
)
}
}

private inner class LimitWritesSqlDriver(
private val delegate: SqlDriver,
) : SqlDriver by delegate {
Expand Down
Loading