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
49 changes: 49 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ dependencies {
#### Key Components

- **ApplicationsDao**: Provides methods for loading and searching applications in the database, as well as checking and retrieving recommended applications
- **CategoriesDao**: Provides methods for retrieving categories and applications by category
- **VersionDao**: Handles database version management operations
- **AppInfoWithExtras**: A data class that extends the GooglePlayApplicationInfo model with additional information
- **Data Consistency Verification**: Utilities to ensure alignment between JSON policy files and database records
Expand Down Expand Up @@ -535,6 +536,54 @@ val recommendedApps = ApplicationsDao.getRecommendedApplications(
}
)

// Get all categories with localized names
val categories = CategoriesDao.getAllCategories(
database = database,
deviceLanguage = "en"
)

// Get applications by category ID
val appsByCategoryId = CategoriesDao.getApplicationsByCategoryId(
database = database,
categoryId = 1,
deviceLanguage = "en",
creator = { id, categoryLocalizedName, appInfo ->
AppInfoWithExtras(
id = id,
categoryLocalizedName = categoryLocalizedName,
app = appInfo
)
}
)

// Get applications by category name
val appsByCategoryName = CategoriesDao.getApplicationsByCategoryName(
database = database,
categoryName = "NAVIGATION",
deviceLanguage = "en",
creator = { id, categoryLocalizedName, appInfo ->
AppInfoWithExtras(
id = id,
categoryLocalizedName = categoryLocalizedName,
app = appInfo
)
}
)

// Get applications by category enum
val appsByCategory = CategoriesDao.getApplicationsByCategory(
database = database,
category = AppCategory.NAVIGATION,
deviceLanguage = "en",
creator = { id, categoryLocalizedName, appInfo ->
AppInfoWithExtras(
id = id,
categoryLocalizedName = categoryLocalizedName,
app = appInfo
)
}
)

// Get the current database version
val currentVersion = VersionDao.getCurrentVersion(database)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package io.github.kdroidfilter.database.dao

import io.github.kdroidfilter.database.store.Database
import io.github.kdroidfilter.database.core.AppCategory
import io.github.kdroidfilter.database.localization.LocalizedAppCategory
import io.github.kdroidfilter.database.store.App_categories
import io.github.kdroidfilter.database.store.Applications
import io.github.kdroidfilter.database.store.Developers
import io.github.kdroidfilter.storekit.gplay.core.model.GooglePlayApplicationInfo

/**
* Data Access Object for Categories
* Contains functions for database operations related to categories and retrieving applications by category
*/
object CategoriesDao {

/**
* Gets all categories from the database
* @param database The database instance
* @param deviceLanguage The device language for localized category names
* @return A list of pairs containing the category enum and its localized name
*/
fun getAllCategories(
database: Database,
deviceLanguage: String
): List<Pair<AppCategory, String>> {
val categoriesQueries = database.app_categoriesQueries

return categoriesQueries.getAllCategories().executeAsList().map { category ->
val categoryEnum = AppCategory.valueOf(category.category_name)
val localizedName = LocalizedAppCategory.getLocalizedName(categoryEnum, deviceLanguage)

Pair(categoryEnum, localizedName)
}
}

/**
* Gets applications by category ID
* @param database The database instance
* @param categoryId The category ID
* @param deviceLanguage The device language for localized category names
* @param creator A function to create the return type from the application data
* @return A list of applications in the specified category
*/
fun <T> getApplicationsByCategoryId(
database: Database,
categoryId: Long,
deviceLanguage: String,
creator: (Long, String, GooglePlayApplicationInfo) -> T
): List<T> {
val applicationsQueries = database.applicationsQueries
val developersQueries = database.developersQueries
val categoriesQueries = database.app_categoriesQueries

return applicationsQueries.getApplicationsByCategory(categoryId).executeAsList().map { app ->
val developer = developersQueries.getDeveloperById(app.developer_id).executeAsOne()
val category = categoriesQueries.getCategoryById(app.app_category_id).executeAsOne()

ApplicationsDao.createAppInfoWithExtras(app, developer, category, deviceLanguage, creator)
}
}

/**
* Gets applications by category name
* @param database The database instance
* @param categoryName The category name (must match an AppCategory enum value)
* @param deviceLanguage The device language for localized category names
* @param creator A function to create the return type from the application data
* @return A list of applications in the specified category
*/
fun <T> getApplicationsByCategoryName(
database: Database,
categoryName: String,
deviceLanguage: String,
creator: (Long, String, GooglePlayApplicationInfo) -> T
): List<T> {
val applicationsQueries = database.applicationsQueries
val developersQueries = database.developersQueries
val categoriesQueries = database.app_categoriesQueries

return applicationsQueries.getApplicationsByCategoryName(categoryName).executeAsList().map { app ->
val developer = developersQueries.getDeveloperById(app.developer_id).executeAsOne()
val category = categoriesQueries.getCategoryById(app.app_category_id).executeAsOne()

ApplicationsDao.createAppInfoWithExtras(app, developer, category, deviceLanguage, creator)
}
}

/**
* Gets applications by category enum
* @param database The database instance
* @param category The AppCategory enum
* @param deviceLanguage The device language for localized category names
* @param creator A function to create the return type from the application data
* @return A list of applications in the specified category
*/
fun <T> getApplicationsByCategory(
database: Database,
category: AppCategory,
deviceLanguage: String,
creator: (Long, String, GooglePlayApplicationInfo) -> T
): List<T> {
return getApplicationsByCategoryName(database, category.name, deviceLanguage, creator)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package io.github.kdroidfilter.database.dao

import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
import io.github.kdroidfilter.database.core.AppCategory
import io.github.kdroidfilter.database.downloader.DatabaseDownloader
import io.github.kdroidfilter.database.store.Database
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import java.io.File
import java.nio.file.Path
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

/**
* Tests for the CategoriesDao
* These tests download the database from GitHub releases and use it to test the DAO functionality
*/
class CategoriesDaoTest {

private lateinit var database: Database
private lateinit var driver: SqlDriver
private lateinit var databaseFile: File

@BeforeEach
fun setup(@TempDir tempDir: Path) {
// Download the database from GitHub releases
val language = "en" // Use English for tests
val dbFileName = "store-database-$language.db"
databaseFile = tempDir.resolve(dbFileName).toFile()

println("[DEBUG_LOG] Temp directory: ${tempDir.toAbsolutePath()}")
println("[DEBUG_LOG] Database file: ${databaseFile.absolutePath}")

// Download the database
runBlocking {
val downloader = DatabaseDownloader()
val success = downloader.downloadLatestStoreDatabaseForLanguage(
tempDir.toString(),
language
)

println("[DEBUG_LOG] Database download success: $success")
assertTrue(success, "Database download should succeed")
assertTrue(databaseFile.exists(), "Database file should exist")
assertTrue(databaseFile.length() > 0, "Database file should not be empty")
}

// Create the database driver and connection
driver = JdbcSqliteDriver("jdbc:sqlite:${databaseFile.absolutePath}")
database = Database(driver)

println("[DEBUG_LOG] Database connection established")
}

@AfterEach
fun tearDown() {
driver.close()
}

@Test
fun testGetAllCategories() {
// Test getting all categories
val categories = CategoriesDao.getAllCategories(
database = database,
deviceLanguage = "en"
)

println("[DEBUG_LOG] Loaded ${categories.size} categories")
assertTrue(categories.isNotEmpty(), "Should load at least one category")

// Verify the categories contain valid data
categories.forEach { (category, localizedName) ->
println("[DEBUG_LOG] Category: ${category.name} - $localizedName")
assertNotNull(category, "Category should not be null")
assertNotNull(localizedName, "Localized name should not be null")
}
}

@Test
fun testGetApplicationsByCategoryId() {
// First get all categories to find a valid category ID
val categories = database.app_categoriesQueries.getAllCategories().executeAsList()
assertTrue(categories.isNotEmpty(), "Should have categories to test")

// Get the first category's ID
val categoryId = categories.first().id
println("[DEBUG_LOG] Testing getApplicationsByCategoryId for category ID: $categoryId")

// Get applications by category ID
val applications = CategoriesDao.getApplicationsByCategoryId(
database = database,
categoryId = categoryId,
deviceLanguage = "en",
creator = { id, categoryLocalizedName, appInfo ->
AppInfoWithExtras(
id = id,
categoryLocalizedName = categoryLocalizedName,
app = appInfo
)
}
)

println("[DEBUG_LOG] Found ${applications.size} applications for category ID $categoryId")

// Verify all applications have the correct category ID
applications.forEach { app ->
val appDetails = database.applicationsQueries.getApplicationById(app.id).executeAsOne()
assertEquals(categoryId, appDetails.app_category_id, "Application should have the correct category ID")
}
}

@Test
fun testGetApplicationsByCategoryName() {
// First get all categories to find a valid category name
val categories = database.app_categoriesQueries.getAllCategories().executeAsList()
assertTrue(categories.isNotEmpty(), "Should have categories to test")

// Get the first category's name
val categoryName = categories.first().category_name
println("[DEBUG_LOG] Testing getApplicationsByCategoryName for category name: $categoryName")

// Get applications by category name
val applications = CategoriesDao.getApplicationsByCategoryName(
database = database,
categoryName = categoryName,
deviceLanguage = "en",
creator = { id, categoryLocalizedName, appInfo ->
AppInfoWithExtras(
id = id,
categoryLocalizedName = categoryLocalizedName,
app = appInfo
)
}
)

println("[DEBUG_LOG] Found ${applications.size} applications for category name $categoryName")

// Verify all applications have the correct category name
applications.forEach { app ->
val appDetails = database.applicationsQueries.getApplicationById(app.id).executeAsOne()
val appCategory = database.app_categoriesQueries.getCategoryById(appDetails.app_category_id).executeAsOne()
assertEquals(categoryName, appCategory.category_name, "Application should have the correct category name")
}
}

@Test
fun testGetApplicationsByCategory() {
// First get all categories to find a valid category
val dbCategories = database.app_categoriesQueries.getAllCategories().executeAsList()
assertTrue(dbCategories.isNotEmpty(), "Should have categories to test")

// Get the first category's name and convert to enum
val categoryName = dbCategories.first().category_name
val category = AppCategory.valueOf(categoryName)
println("[DEBUG_LOG] Testing getApplicationsByCategory for category: $category")

// Get applications by category enum
val applications = CategoriesDao.getApplicationsByCategory(
database = database,
category = category,
deviceLanguage = "en",
creator = { id, categoryLocalizedName, appInfo ->
AppInfoWithExtras(
id = id,
categoryLocalizedName = categoryLocalizedName,
app = appInfo
)
}
)

println("[DEBUG_LOG] Found ${applications.size} applications for category $category")

// Verify all applications have the correct category
applications.forEach { app ->
val appDetails = database.applicationsQueries.getApplicationById(app.id).executeAsOne()
val appCategory = database.app_categoriesQueries.getCategoryById(appDetails.app_category_id).executeAsOne()
assertEquals(category.name, appCategory.category_name, "Application should have the correct category")
}
}
}
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
Expand All @@ -49,7 +50,6 @@ storekit-aptoide-api = { module = "io.github.kdroidfilter:storekit-aptoide-api",
storekit-gplayscrapper = { module = "io.github.kdroidfilter:storekit-gplay-scrapper", version.ref = "storekit" }
storekit-gplaycore = { module ="io.github.kdroidfilter:storekit-gplay-core", version.ref = "storekit" }
platform-tools-release-fetcher = {module = "io.github.kdroidfilter:platformtools.releasefetcher", version.ref = "platformtools"}
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }

sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqlDelight" }
sqldelight-coroutines-extensions = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqlDelight" }
Expand Down