diff --git a/.github/workflows/call-validate-gradle-wrapper.yml b/.github/workflows/call-validate-gradle-wrapper.yml new file mode 100644 index 0000000..7b8b8d2 --- /dev/null +++ b/.github/workflows/call-validate-gradle-wrapper.yml @@ -0,0 +1,11 @@ +name: "Validate gradle wrapper" +on: + workflow_call: + +jobs: + validation: + name: "Validate gradle wrapper" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + - uses: gradle/wrapper-validation-action@f9c9c575b8b21b6485636a91ffecd10e558c62f6 # v3 \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..0890a3f --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,69 @@ +name: Pull Request CI + +on: + pull_request: + branches: + - 'dev' + +# Concurrency strategy: +# github.workflow: distinguish this workflow from others +# github.event_name: distinguish `push` event from `pull_request` and 'merge_group' event +# github.ref_name: distinguish branch +# github.repository: distinguish owner+repository +# +# Reference: +# https://docs.github.com/en/actions/using-jobs/using-concurrency +# https://docs.github.com/en/actions/learn-github-actions/contexts#github-context +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{github.ref_name}}-${{github.repository}} + cancel-in-progress: true + + +jobs: + validate_gradle_wrapper: + name: "Validate gradle wrapper" + uses: ./.github/workflows/call-validate-gradle-wrapper.yml + info: + name: "Display concurrency info" + runs-on: ubuntu-latest + needs: [ validate_gradle_wrapper ] + steps: + - run: | + echo "github.workflow=${{ github.workflow }}" + echo "github.event_name=${{ github.event_name }}" + echo "github.ref_name=${{ github.ref_name }}" + echo "github.repository=${{ github.repository }}" + check_kenerator_configuration: + name: "Check config generation is fine" + runs-on: ubuntu-latest + needs: [ validate_gradle_wrapper ] + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + with: + submodules: 'recursive' + - name: Set up JDK 1.17 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 # v4 + with: + distribution: 'temurin' + java-version: '17' + - name: Run configuration kenerator + uses: gradle/gradle-build-action@ac2d340dc04d9e1113182899e983b5400c17cda1 # v3 + with: + arguments: :modules:kenerator:configuration:run + check_db_configuration: + name: "Check config generation is fine" + runs-on: ubuntu-latest + needs: [ validate_gradle_wrapper ] + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + with: + submodules: 'recursive' + - name: Set up JDK 1.17 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 # v4 + with: + distribution: 'temurin' + java-version: '17' + - name: Run SQL kenerator + uses: gradle/gradle-build-action@ac2d340dc04d9e1113182899e983b5400c17cda1 # v3 + with: + arguments: :modules:kenerator:sql:run \ No newline at end of file diff --git a/IRDB b/IRDB index babc1ac..ed2fbd4 160000 --- a/IRDB +++ b/IRDB @@ -1 +1 @@ -Subproject commit babc1ac95dbb5f5d062af2cea8a12b30a5a9e254 +Subproject commit ed2fbd4f15cfb2e631de5df0c0cb06ac76acd1bd diff --git a/modules/infrared/src/main/kotlin/com/flipperdevices/infrared/editor/util/InfraredRemoteEncoder.kt b/modules/infrared/src/main/kotlin/com/flipperdevices/infrared/editor/util/InfraredRemoteEncoder.kt index c336d7d..3d01fd9 100644 --- a/modules/infrared/src/main/kotlin/com/flipperdevices/infrared/editor/util/InfraredRemoteEncoder.kt +++ b/modules/infrared/src/main/kotlin/com/flipperdevices/infrared/editor/util/InfraredRemoteEncoder.kt @@ -19,7 +19,7 @@ object InfraredRemoteEncoder { val raw = this as? InfraredRemote.Raw val parsed = this as? InfraredRemote.Parsed return SignalModel.FlipperRemote( - name = this.name, + name = null.orEmpty(), // the name should not affect hash type = this.type, protocol = parsed?.protocol, address = parsed?.address, @@ -30,31 +30,8 @@ object InfraredRemoteEncoder { ) } - private fun SignalModel.FlipperRemote.toInfraredRemote(): InfraredRemote { - return runCatching { - InfraredRemote.Parsed( - nameInternal = name, - typeInternal = type, - protocol = protocol ?: error("Not parsed remote"), - address = address ?: error("Not parsed remote"), - command = command ?: error("Not parsed remote") - ) - }.getOrNull() ?: InfraredRemote.Raw( - nameInternal = name, - typeInternal = type, - frequency = frequency ?: error("Not raw remote"), - dutyCycle = dutyCycle ?: error("Not raw remote"), - data = data ?: error("Not raw remote") - ) - } - fun encode(remote: InfraredRemote): ByteArray { val fRemote = remote.toFlipperRemote() return json.encodeToString(fRemote).encodeToByteArray() } - - fun decode(byteArray: ByteArray): InfraredRemote { - val fRemote: SignalModel.FlipperRemote = json.decodeFromString(byteArray.decodeToString()) - return fRemote.toInfraredRemote() - } } \ No newline at end of file diff --git a/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/Main.kt b/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/Main.kt index 21fffb4..37b3d18 100644 --- a/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/Main.kt +++ b/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/Main.kt @@ -40,6 +40,7 @@ private fun generateDevicesConfigFiles() { ).onEach { irFile -> val config = DefaultDeviceConfigGenerator(AnyDeviceKeyNamesProvider) .generate(irFile) + if (config.keyMap.isEmpty()) error("Config file for ${irFile} is empty") val configFile = irFile.parentFile.resolve("config.json") val string = json.encodeToString(config) configFile.writeText(string) diff --git a/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/category/api/DeviceKeyExt.kt b/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/category/api/DeviceKeyExt.kt index d1c56d5..a02c21e 100644 --- a/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/category/api/DeviceKeyExt.kt +++ b/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/category/api/DeviceKeyExt.kt @@ -195,6 +195,10 @@ object DeviceKeyExt { DeviceKey.ENERGY_SAVE -> listOf( CategoryType.FAN ) + + DeviceKey.SHUTTER -> listOf( + CategoryType.CAMERA + ) } } } diff --git a/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/device/api/DefaultDeviceConfigGenerator.kt b/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/device/api/DefaultDeviceConfigGenerator.kt index f06357d..f960ee3 100644 --- a/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/device/api/DefaultDeviceConfigGenerator.kt +++ b/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/device/api/DefaultDeviceConfigGenerator.kt @@ -11,16 +11,12 @@ import java.io.File class DefaultDeviceConfigGenerator(private val keyNamesProvider: DeviceKeyNamesProvider) : DeviceConfigGenerator { override fun generate(irFile: File): DeviceConfiguration { val remotes = DeviceConfigGenerator.parseRemotes(irFile) - val remoteNameToCount = remotes.groupBy { it.name } val deviceKeyToInstance = remotes.mapNotNull { val name = it.name val deviceKey = keyNamesProvider.getKey(name) ?: return@mapNotNull null val byteArray = InfraredRemoteEncoder.encode(it) val hash = JvmEncoder(ByteArrayEncoder.Algorithm.SHA_256).encode(byteArray) - val identifier = when { - (remoteNameToCount[name]?.size ?: 0) > 1 -> IfrKeyIdentifier.Sha256(name = name, hash = hash) - else -> IfrKeyIdentifier.Name(name = name) - } + val identifier = IfrKeyIdentifier.Sha256(name = name, hash = hash) deviceKey to identifier }.associate { pair -> pair } return DeviceConfiguration(deviceKeyToInstance) diff --git a/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/device/api/DeviceKeyNamesProvider.kt b/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/device/api/DeviceKeyNamesProvider.kt index dcb9dec..ee5c53f 100644 --- a/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/device/api/DeviceKeyNamesProvider.kt +++ b/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/device/api/DeviceKeyNamesProvider.kt @@ -8,7 +8,9 @@ interface DeviceKeyNamesProvider { companion object { fun DeviceKeyNamesProvider.getKey(keyName: String): DeviceKey? { return DeviceKey.entries - .firstOrNull { deviceKey -> getKeyNames(deviceKey).contains(keyName) } + .firstOrNull { deviceKey -> + getKeyNames(deviceKey).any { it.equals(keyName, true) } + } } } } diff --git a/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/device/api/any/AnyDeviceKeyNamesProvider.kt b/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/device/api/any/AnyDeviceKeyNamesProvider.kt index 9d6d068..6344782 100644 --- a/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/device/api/any/AnyDeviceKeyNamesProvider.kt +++ b/modules/kenerator/configuration/src/main/kotlin/com/flipperdevices/ifrmvp/generator/config/device/api/any/AnyDeviceKeyNamesProvider.kt @@ -6,32 +6,40 @@ import com.flipperdevices.ifrmvp.generator.config.device.api.DeviceKeyNamesProvi object AnyDeviceKeyNamesProvider : DeviceKeyNamesProvider { @Suppress("LongMethod", "CyclomaticComplexMethod") override fun getKeyNames(key: DeviceKey): List { + // NOT CASE-SENSITIVE!!! return when (key) { DeviceKey.PWR -> listOf( + "power", "pwr", "power", "power_r", "on", + "on_off", + "on/off", ) DeviceKey.VOL_DOWN -> listOf( "vol-", - "vol-_r" + "vol-_r", + "voldown" ) DeviceKey.VOL_UP -> listOf( "vol+", - "vol+_r" + "vol+_r", + "volup" ) DeviceKey.CH_UP -> listOf( "ch+", - "ch+_r" + "ch+_r", + "ch_next" ) DeviceKey.CH_DOWN -> listOf( "ch-", - "ch-_r" + "ch-_r", + "ch_prev" ) DeviceKey.FOCUS_MORE -> listOf( @@ -133,7 +141,8 @@ object AnyDeviceKeyNamesProvider : DeviceKeyNamesProvider { DeviceKey.FAN_SPEED -> listOf( "fanspeed", - "fan_speed" + "fan_speed", + "speed" ) DeviceKey.NEAR -> listOf( @@ -150,7 +159,8 @@ object AnyDeviceKeyNamesProvider : DeviceKeyNamesProvider { ) DeviceKey.WIND_SPEED -> listOf( - "wind_speed" + "wind_speed", + "strength" ) DeviceKey.MODE -> listOf( @@ -159,11 +169,17 @@ object AnyDeviceKeyNamesProvider : DeviceKeyNamesProvider { ) DeviceKey.FAN_SPEED_UP -> listOf( - "fanspeed+" + "fanspeed+", + "fan+", + "fan_up", + "speed_up" ) DeviceKey.FAN_SPEED_DOWN -> listOf( - "fanspeed-" + "fanspeed-", + "fan-", + "fan_dn", + "speed_down" ) DeviceKey.SHAKE_WIND -> listOf( @@ -175,17 +191,30 @@ object AnyDeviceKeyNamesProvider : DeviceKeyNamesProvider { ) DeviceKey.TEMPERATURE_UP -> listOf( - "temperature_up" + "temperature_up", + "heat+", + "heat_hi", + "temp+", + "heat_up" ) DeviceKey.TEMPERATURE_DOWN -> listOf( - "temperature_down" + "temperature_down", + "heat-", + "heat_lo", + "temp-", + "heat_down" ) DeviceKey.ENERGY_SAVE -> listOf( "energy save", "energy save_r" ) + + DeviceKey.SHUTTER -> listOf( + "shutter", + "trigger" + ) } } } diff --git a/modules/kenerator/sql/src/main/kotlin/com/flipperdevices/ifrmvp/parser/Main.kt b/modules/kenerator/sql/src/main/kotlin/com/flipperdevices/ifrmvp/parser/Main.kt index dc2f566..3ad5b0d 100644 --- a/modules/kenerator/sql/src/main/kotlin/com/flipperdevices/ifrmvp/parser/Main.kt +++ b/modules/kenerator/sql/src/main/kotlin/com/flipperdevices/ifrmvp/parser/Main.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.runBlocking fun main() { runParser() +// fixIrdbStructure() } fun runParser() { @@ -17,10 +18,99 @@ fun runParser() { ) ) runBlocking { - parserModule.fillerController.fillDatabase().join() + parserModule.fillerController.fillDatabase() } } +fun fixIrdbStructure() { + val folder = ParserPathResolver.irDbFolderFolder + .parentFile + .resolve("Flipper-IRDB") + val requiredCategories = listOf( + "Air_Purifiers", + "Cameras", + "DVD_Players", + "Fans", + "Projectors", + "TVs" + ) + // delete non-required categories + folder.listFiles() + .orEmpty() + .filter { !requiredCategories.contains(it.name) } + .onEach { it.deleteRecursively() } + folder.listFiles() + .orEmpty() + .filter { it.isDirectory } + .filter { requiredCategories.contains(it.name) } + .onEach { category -> + // remove non-directory files in categories + category.listFiles() + .orEmpty() + .filter { !it.isDirectory } + .onEach { it.delete() } + category.listFiles() + .orEmpty() + .filter { it.isDirectory } + .filter { !it.name.contains("unknown",true) } + .onEach { brand -> + brand.listFiles() + .orEmpty() + .filter { it.isFile } + .filter { it.extension == "ir" } + .filter { !it.name.contains("unknown", true) } + .filter { !it.name.contains("config", true) } + .filter { !it.nameWithoutExtension.equals(brand.name, true) } + .filter { it.nameWithoutExtension.any { it.isDigit() } } + .onEach { model -> + fun String.replaceInvalidChars(): String { + return this + .replace("-","") + .replace(" ","_") + } + val modelFolder = brand.resolve(model.nameWithoutExtension.replaceInvalidChars()) + modelFolder.mkdir() + model.copyTo(modelFolder.resolve(model.name.replaceInvalidChars())) + model.delete() + } + + // remove non-ir files + brand.listFiles() + .orEmpty() + .filter { it.extension!=".ir" } + .onEach { it.delete() } + // remove non-filtered files and empty folders + brand.listFiles() + .orEmpty() + .filterNot { !it.name.contains("unknown", true) } + .filterNot { !it.name.contains("config", true) } + .filterNot { !it.nameWithoutExtension.equals(brand.name, true) } + .filterNot { it.nameWithoutExtension.any { it.isDigit() } } + .onEach { + it.delete() + if (it.parentFile.listFiles().isNullOrEmpty()) it.parentFile.deleteRecursively() + } + // remove empty folders + brand.listFiles() + .orEmpty() + .filter { it.isDirectory } + .filter { it.listFiles().isNullOrEmpty() } + .onEach { it.deleteRecursively() } + } + + category.listFiles() + .orEmpty() + .filterNot { !it.name.contains("unknown",true) } + .onEach { it.deleteRecursively() } + // remove empty folders + category.listFiles() + .orEmpty() + .filter { it.isDirectory } + .filter { it.listFiles().isNullOrEmpty() } + .onEach { it.deleteRecursively() } + } +} + /** * Fix directory structure */ diff --git a/modules/kenerator/sql/src/main/kotlin/com/flipperdevices/ifrmvp/parser/presentation/FillerController.kt b/modules/kenerator/sql/src/main/kotlin/com/flipperdevices/ifrmvp/parser/presentation/FillerController.kt index 55146de..9ba03b8 100644 --- a/modules/kenerator/sql/src/main/kotlin/com/flipperdevices/ifrmvp/parser/presentation/FillerController.kt +++ b/modules/kenerator/sql/src/main/kotlin/com/flipperdevices/ifrmvp/parser/presentation/FillerController.kt @@ -30,7 +30,7 @@ import java.io.File internal class FillerController(private val database: Database) : CoroutineScope by IoCoroutineScope() { - fun fillDatabase() = launch { + suspend fun fillDatabase() { transaction(database) { // insert all categories CategoryTable.batchInsert(ParserPathResolver.categories) { categoriesFolder -> @@ -131,7 +131,7 @@ internal class FillerController(private val database: Database) : CoroutineScope SignalTable .select(SignalTable.id) .where { SignalTable.brandId eq brandId } - .andWhere { SignalTable.name eq it.name } +// .andWhere { SignalTable.name eq it.name } .andWhere { SignalTable.type eq it.type } .andWhere { SignalTable.protocol eq parsedRemote?.protocol } .andWhere { SignalTable.address eq parsedRemote?.address } @@ -140,7 +140,12 @@ internal class FillerController(private val database: Database) : CoroutineScope .andWhere { SignalTable.dutyCycle eq rawRemote?.dutyCycle } .andWhere { SignalTable.data eq rawRemote?.data } .map { it[SignalTable.id] } - .first() + .firstOrNull() ?: error( + """ + The list is emoty for brand: ${brand.name} category: ${categoryFolder} file: ${irFile.name}; + name: ${it.name} + """.trimIndent() + ) } InfraredFileToSignalTable.batchInsert(signalIds) { this[InfraredFileToSignalTable.infraredFileId] = irFileId @@ -152,6 +157,9 @@ internal class FillerController(private val database: Database) : CoroutineScope brand = brand.name, ifrFolderName = irFile.parentFile.name ) + if (irFileConfiguration.keyMap.entries.isEmpty()) { + error("Configuration file for ${irFile} is empty") + } SignalKeyTable.batchInsert(irFileConfiguration.keyMap.entries) { (baseKey, keyIdentifier) -> this[SignalKeyTable.infraredFileId] = irFileId this[SignalKeyTable.deviceKey] = baseKey @@ -160,10 +168,7 @@ internal class FillerController(private val database: Database) : CoroutineScope this[SignalKeyTable.type] = IfrKeyIdentifier.Empty.TYPE } - is IfrKeyIdentifier.Name -> { - this[SignalKeyTable.remoteKeyName] = keyIdentifier.name - this[SignalKeyTable.type] = IfrKeyIdentifier.Name.TYPE - } + is IfrKeyIdentifier.Name -> error("Identifying by name is not possible!") is IfrKeyIdentifier.Sha256 -> { this[SignalKeyTable.remoteKeyName] = keyIdentifier.name @@ -185,19 +190,21 @@ internal class FillerController(private val database: Database) : CoroutineScope .apply { when (keyIdentifier) { IfrKeyIdentifier.Empty -> this - is IfrKeyIdentifier.Name -> { - andWhere { SignalTable.name eq keyIdentifier.name } - } is IfrKeyIdentifier.Sha256 -> { - andWhere { SignalTable.name eq keyIdentifier.name } - .andWhere { SignalTable.hash eq keyIdentifier.hash } + andWhere { SignalTable.hash eq keyIdentifier.hash } } + + is IfrKeyIdentifier.Name -> error("Identifying by name is not possible!") } } .map { it[SignalTable.id] } .also { assert(it.size == 1) } - .first() + .firstOrNull() ?: error( + """ + Could not resolve identifier ${keyIdentifier} for file ${irFile} + """.trimIndent() + ) } } } diff --git a/modules/model/src/commonMain/kotlin/com/flipperdevices/ifrmvp/backend/model/DeviceKey.kt b/modules/model/src/commonMain/kotlin/com/flipperdevices/ifrmvp/backend/model/DeviceKey.kt index 84703a5..ae4c615 100644 --- a/modules/model/src/commonMain/kotlin/com/flipperdevices/ifrmvp/backend/model/DeviceKey.kt +++ b/modules/model/src/commonMain/kotlin/com/flipperdevices/ifrmvp/backend/model/DeviceKey.kt @@ -84,6 +84,9 @@ enum class DeviceKey { // Camera FAR, + // Camera + SHUTTER, + // DVD PAUSE,