Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ca22bda
feat: make core, dao and search Kotlin Multiplatform (add iOS targets)
kdroidFilter Jun 22, 2026
45b0106
fix(dao): align platform support comments with implementation
Copilot Jun 22, 2026
f75ac4c
feat(sefariasqlite): drive rename from otzaria-library/ForDB CSVs (#3)
BatshevaRich May 19, 2026
eb2aaa4
Feat/talmud flatten gershayim (#4)
BatshevaRich May 19, 2026
24db10a
fix(sefariasqlite): correct Shabbat commentator and set Ramban for Ch…
palmoni5 May 31, 2026
54d55d5
feat(sefariasqlite): seed generation table from סדר הדורות.csv
BatshevaRich May 28, 2026
62afd50
refactor(sefariasqlite): address PR #5 review
BatshevaRich May 28, 2026
2922f28
refactor(sefariasqlite): extract generation seeding to its own task
BatshevaRich May 28, 2026
0b1984c
fix(sefariasqlite): rollback on partial seed failure
BatshevaRich May 28, 2026
48d095e
fix(generations): add generation tables to delta pipeline and fix fal…
BatshevaRich May 31, 2026
437d3ab
Fix code comments
Y-PLONI Jun 2, 2026
731e1ab
fix(sefariasqlite): use full Tosafot Yom Tov instead of Ikkar in Mishnah
palmoni5 Jun 1, 2026
3e04c11
fix(sefariasqlite): fail required rename rule downloads
Y-PLONI Jun 2, 2026
b2abc63
refactor(sefariasqlite): encode ForDB CSV urls at runtime
Y-PLONI Jun 2, 2026
b5dbfa3
fix(sefariasqlite): fail required generation download
Y-PLONI Jun 2, 2026
92e9e68
refactor(sefariasqlite): share rule section processing
Y-PLONI Jun 2, 2026
7fd0426
fix(sefariasqlite): order Radak before Metzudot in Nakh defaults
palmoni5 Jun 2, 2026
c2f46c7
feat(sefariasqlite): expand default commentators for SA, Mishnah, Yer…
palmoni5 Jun 3, 2026
73a6a01
מעבר להורדה מrelease
machzikim1-coder Jun 7, 2026
5264617
FIX
machzikim1-coder Jun 7, 2026
54d0722
refactor(sefariasqlite): drop dead failure counters from rename post-…
BatshevaRich Jun 7, 2026
66d8b0a
עדכון הרשימה השחורה
YOSEFTT Jun 10, 2026
72e01ed
Removing trim from csv files
Y-PLONI Jun 12, 2026
8b4ae4c
Update books blacklist with new entries
YOSEFTT Jun 16, 2026
4caf6b9
Update release manifest [skip ci]
github-actions[bot] Jun 22, 2026
774955e
Delta Fix: Migration to Missing Tables
Y-PLONI Jun 22, 2026
7514586
Update release manifest [skip ci]
github-actions[bot] Jun 22, 2026
1db022d
fix(sefariasqlite): store long description in heDesc, real short desc…
BatshevaRich Jun 5, 2026
b6c6aed
Update release manifest [skip ci]
github-actions[bot] Jun 22, 2026
54889cd
Merge PR #75 (Seder HaDorot, name changes) without gershayim normaliz…
kdroidFilter Jun 25, 2026
ffd3a42
Merge remote-tracking branch 'origin/feat/kmp-multiplatform' into fea…
kdroidFilter Jun 25, 2026
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
2 changes: 1 addition & 1 deletion SeforimMagicIndexer
36 changes: 22 additions & 14 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ tasks.register("generateSeforimDb") {
dependsOn(":otzariasqlite:appendOtzaria")
dependsOn(":otzariasqlite:generateHavroutaLinks")
dependsOn(":sefariasqlite:renameCategories")
dependsOn(":sefariasqlite:seedGenerations")
dependsOn(":catalog:buildCatalog")
dependsOn(":searchindex:buildLuceneIndexDefault")
dependsOn(":packaging:writeReleaseInfo")
Expand All @@ -32,6 +33,7 @@ project(":generator-common").tasks.matching { it.name == "stampSchemaVersion" }.
mustRunAfter(":packaging:writeReleaseInfo")
mustRunAfter(":catalog:buildCatalog")
mustRunAfter(":searchindex:buildLuceneIndexDefault")
mustRunAfter(":sefariasqlite:seedGenerations")
}

// Ensure ordering inside the pipeline task graph
Expand All @@ -53,8 +55,16 @@ project(":sefariasqlite").tasks.matching { it.name == "renameCategories" }.confi
project(":otzariasqlite").tasks.matching { it.name == "generateHavroutaLinks" }.configureEach {
mustRunAfter(":otzariasqlite:appendOtzaria")
}
// seedGenerations runs after all book-writing stages so it can link both
// Sefaria- and Otzaria-sourced books in a single pass.
project(":sefariasqlite").tasks.matching { it.name == "seedGenerations" }.configureEach {
mustRunAfter(":otzariasqlite:appendOtzaria")
mustRunAfter(":otzariasqlite:generateHavroutaLinks")
mustRunAfter(":sefariasqlite:renameCategories")
}
project(":catalog").tasks.matching { it.name == "buildCatalog" }.configureEach {
mustRunAfter(":otzariasqlite:generateHavroutaLinks")
mustRunAfter(":sefariasqlite:seedGenerations")
}
project(":searchindex").tasks.matching { it.name == "buildLuceneIndexDefault" }.configureEach {
mustRunAfter(":catalog:buildCatalog")
Expand Down Expand Up @@ -105,22 +115,20 @@ tasks.register("publishRelease") {
description = "generateSeforimDb + producePatchAndVerify (+ release_meta.json upsert)."
dependsOn("generateSeforimDb")
finalizedBy(":generator-common:producePatchAndVerify")
val prevReleaseDb = providers.gradleProperty("prevReleaseDb")
val buildStatePath = providers.systemProperty("buildStatePath")
.orElse(providers.environmentVariable("BUILD_STATE_PATH"))
.orElse(layout.buildDirectory.file("seforim.db.buildstate").map { it.asFile.absolutePath })
// Operator footgun guard: if -PprevReleaseDb is set we're producing a
// delta against a previous release, which requires the IdAllocator to
// be seeded from that release's build_state. Without it, the allocator
// starts fresh and assigns brand-new ids to every row — the producer
// would then emit a "delta" containing the entire corpus, useless as
// an incremental patch. Fail fast with the path the operator forgot.
doFirst {
val prev = project.findProperty("prevReleaseDb") as String?
val prev = prevReleaseDb.orNull
if (prev != null) {
val explicit = System.getProperty("buildStatePath")
?: System.getenv("BUILD_STATE_PATH")
val buildStateFile = if (explicit != null) {
file(explicit)
} else {
layout.buildDirectory.file("seforim.db.buildstate").get().asFile
}
val buildStateFile = java.io.File(buildStatePath.get())
check(buildStateFile.exists()) {
"publishRelease: -PprevReleaseDb=$prev was set but " +
"$buildStateFile is missing. Copy the previous release's " +
Expand All @@ -137,16 +145,16 @@ project(":generator-common").tasks.matching { it.name == "producePatchAndVerify"
// generateSeforimDb to exist in :generator-common.
mustRunAfter(rootProject.tasks.named("generateSeforimDb"))
// Map the umbrella task's -P props onto the CLI's gradle props.
val prev = project.findProperty("prevReleaseDb") as String?
val from = project.findProperty("fromVersion") as String?
val to = project.findProperty("toVersion") as String?
val prev = providers.gradleProperty("prevReleaseDb").orNull
val from = providers.gradleProperty("fromVersion").orNull
val to = providers.gradleProperty("toVersion").orNull
if (prev != null && from != null && to != null) {
val out = rootProject.layout.buildDirectory
.file("patch-v${from}-v${to}.db").get().asFile.absolutePath
val new = rootProject.layout.buildDirectory.file("seforim.db").get().asFile.absolutePath
this.extensions.extraProperties.set("prevDb", prev)
this.extensions.extraProperties.set("newDb", new)
this.extensions.extraProperties.set("out", out)
(this as JavaExec).systemProperty("prevDb", prev)
(this as JavaExec).systemProperty("newDb", new)
(this as JavaExec).systemProperty("out", out)
}
}

Expand Down
6 changes: 4 additions & 2 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ kotlin {
jvmToolchain(libs.versions.jvmToolchain.get().toInt())

androidLibrary {
namespace = "io.github.kdroidfilter.seforimlibrary"
compileSdk = 35
namespace = "io.github.kdroidfilter.seforimlibrary.core"
compileSdk = 36
minSdk = 21
}
jvm()
iosArm64()
iosSimulatorArm64()

sourceSets {
commonMain.dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import kotlinx.serialization.Serializable
* @property pubPlaces The list of publication places for this book
* @property pubDates The list of publication dates for this book
* @property heShortDesc A short description of the book in Hebrew
* @property heDesc The full (long) description of the book in Hebrew
* @property order The display order of the book within its category
* @property totalLines The total number of lines in the book
* @property hasAltStructures Indicates if the book has alternative TOC structures (e.g., Parasha)
Expand All @@ -36,6 +37,7 @@ data class Book(
val pubPlaces: List<PubPlace> = emptyList(),
val pubDates: List<PubDate> = emptyList(),
val heShortDesc: String? = null,
val heDesc: String? = null,
// Optional notes content: when a companion file named "הערות על <title>" exists,
// its content is attached here instead of being inserted as a separate book.
val notesContent: String? = null,
Expand Down
17 changes: 15 additions & 2 deletions dao/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,31 @@ group = "io.github.kdroidfilter.seforimlibrary"


kotlin {
// Re-apply the default hierarchy (ios/native/apple wiring); adding the manual androidJvmMain
// dependsOn edges below would otherwise disable it and orphan the iOS actuals.
applyDefaultHierarchyTemplate()

jvmToolchain(libs.versions.jvmToolchain.get().toInt())

androidLibrary {
namespace = "io.github.kdroidfilter.seforimlibrary"
compileSdk = 35
namespace = "io.github.kdroidfilter.seforimlibrary.dao"
compileSdk = 36
minSdk = 21
}
jvm()
iosArm64()
iosSimulatorArm64()

sourceSets {
// Shared JVM+Android source set: Android is JVM-based, so the java.util.concurrent.ConcurrentHashMap
// (newConcurrentMap) and System.getenv (Env) actuals live here once.
val androidJvmMain by creating { dependsOn(commonMain.get()) }
jvmMain.get().dependsOn(androidJvmMain)
androidMain.get().dependsOn(androidJvmMain)

commonMain.dependencies {
api(project(":core"))
implementation(libs.filekit.core)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.test)
implementation(libs.kotlinx.serialization.json)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.github.kdroidfilter.seforimlibrary.dao.repository

// Shared by the JVM and Android targets (both JVM-based): java.* is on the classpath.
internal actual fun <K, V> newConcurrentMap(): MutableMap<K, V> = java.util.concurrent.ConcurrentHashMap()
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
package io.github.kdroidfilter.seforimlibrary.env

actual fun getEnvironmentVariable(name: String): String? = System.getenv(name)

Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package io.github.kdroidfilter.seforimlibrary.dao

import io.github.kdroidfilter.seforimlibrary.core.models.PrecomputedCatalog
import io.github.vinceglb.filekit.PlatformFile
import io.github.vinceglb.filekit.exists
import io.github.vinceglb.filekit.readBytes
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.protobuf.ProtoBuf
import java.io.File
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.io.path.readBytes

/**
* Loader for the precomputed catalog tree.
Expand All @@ -15,48 +14,26 @@ import kotlin.io.path.readBytes
object CatalogLoader {

/**
* Loads the precomputed catalog from a file next to the database.
* Loads the precomputed catalog from a `catalog.pb` file next to the database, using FileKit
* for cross-platform file access.
*
* @param dbPath Path to the database file. The catalog file should be in the same directory.
* @return The precomputed catalog, or null if the file doesn't exist or can't be loaded
*/
@OptIn(ExperimentalSerializationApi::class)
fun loadCatalog(dbPath: String): PrecomputedCatalog? {
suspend fun loadCatalog(dbPath: String): PrecomputedCatalog? {
return try {
val dbFile = File(dbPath)
val catalogFile = File(dbFile.parentFile, "catalog.pb")
// catalog.pb sits next to the db file; handle both '/' and '\' separators.
val sep = maxOf(dbPath.lastIndexOf('/'), dbPath.lastIndexOf('\\'))
val catalogPath = if (sep < 0) "catalog.pb" else dbPath.substring(0, sep + 1) + "catalog.pb"

if (!catalogFile.exists()) {
println("Catalog file not found: ${catalogFile.absolutePath}")
return null
}

val bytes = catalogFile.readBytes()
val catalog = ProtoBuf.decodeFromByteArray(PrecomputedCatalog.serializer(), bytes)

println("Loaded precomputed catalog: ${catalog.totalCategories} categories, ${catalog.totalBooks} books")
catalog
} catch (e: Exception) {
println("Failed to load precomputed catalog: ${e.message}")
e.printStackTrace()
null
}
}

/**
* Loads the precomputed catalog from a specific Path.
*
* @param catalogPath Direct path to the catalog.pb file
* @return The precomputed catalog, or null if the file doesn't exist or can't be loaded
*/
fun loadCatalogFromPath(catalogPath: Path): PrecomputedCatalog? {
return try {
if (!catalogPath.exists()) {
val file = PlatformFile(catalogPath)
if (!file.exists()) {
println("Catalog file not found: $catalogPath")
return null
}

val bytes = catalogPath.readBytes()
val bytes = file.readBytes()
val catalog = ProtoBuf.decodeFromByteArray(PrecomputedCatalog.serializer(), bytes)

println("Loaded precomputed catalog: ${catalog.totalCategories} categories, ${catalog.totalBooks} books")
Expand All @@ -67,16 +44,4 @@ object CatalogLoader {
null
}
}

/**
* Checks if a catalog file exists next to the database.
*
* @param dbPath Path to the database file
* @return true if catalog.pb exists, false otherwise
*/
fun catalogExists(dbPath: String): Boolean {
val dbFile = File(dbPath)
val catalogFile = File(dbFile.parentFile, "catalog.pb")
return catalogFile.exists()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ fun io.github.kdroidfilter.seforimlibrary.db.Book.toModel(json: Json, authors: L
pubPlaces = pubPlaces,
pubDates = pubDates,
heShortDesc = heShortDesc,
heDesc = heDesc,
notesContent = notesContent,
order = orderIndex.toFloat(),
totalLines = totalLines.toInt(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.github.kdroidfilter.seforimlibrary.dao.repository

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

/** Dispatcher for blocking DB I/O. */
internal val ioDispatcher: CoroutineDispatcher = Dispatchers.IO

/** Thread-safe map (ConcurrentHashMap on JVM/Android, NSLock-backed map on iOS). */
internal expect fun <K, V> newConcurrentMap(): MutableMap<K, V>
Loading
Loading