Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Loading