From ad80a4a39b608ce8cbaa902115ecfa00c5a7770f Mon Sep 17 00:00:00 2001 From: darken Date: Sat, 19 Aug 2023 14:57:39 +0200 Subject: [PATCH 1/2] SystemCleaner: Support for importing SD Maid 1/Legacy filters. --- .../common/serialization/RegexAdapter.kt | 25 ++- .../SerializationCommonModule.kt | 1 + .../common/serialization/RegexAdapterTest.kt | 77 +++++++ .../core/filter/custom/CustomFilter.kt | 7 +- .../core/filter/custom/CustomFilterConfig.kt | 12 +- .../core/filter/custom/CustomFilterRepo.kt | 16 +- .../core/filter/custom/LegacyFilterSupport.kt | 188 ++++++++++++++++++ .../core/filter/stock/AdvertisementFilter.kt | 2 +- .../core/filter/stock/AnalyticsFilter.kt | 2 +- .../core/filter/stock/AnrFilter.kt | 2 +- .../core/filter/stock/LinuxFilesFilter.kt | 2 +- .../core/filter/stock/LostDirFilter.kt | 2 +- .../core/filter/stock/UsagestatsFilter.kt | 2 +- .../systemcleaner/core/sieve/BaseSieve.kt | 4 +- .../editor/CustomFilterEditorFragment.kt | 2 +- .../editor/CustomFilterEditorViewModel.kt | 8 +- .../list/CustomFilterListViewModel.kt | 6 +- .../settings/SystemCleanerSettingsFragment.kt | 4 - .../core/filter/BaseSieveTest.kt | 2 +- .../filter/custom/CustomFilterConfigTest.kt | 2 +- .../filter/custom/LegacyFilterSupportTest.kt | 121 +++++++++++ 21 files changed, 451 insertions(+), 36 deletions(-) create mode 100644 app-common/src/test/java/eu/darken/sdmse/common/serialization/RegexAdapterTest.kt create mode 100644 app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/LegacyFilterSupport.kt create mode 100644 app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/custom/LegacyFilterSupportTest.kt diff --git a/app-common/src/main/java/eu/darken/sdmse/common/serialization/RegexAdapter.kt b/app-common/src/main/java/eu/darken/sdmse/common/serialization/RegexAdapter.kt index 94348ec43..d0f5ee775 100644 --- a/app-common/src/main/java/eu/darken/sdmse/common/serialization/RegexAdapter.kt +++ b/app-common/src/main/java/eu/darken/sdmse/common/serialization/RegexAdapter.kt @@ -1,16 +1,27 @@ package eu.darken.sdmse.common.serialization import com.squareup.moshi.FromJson +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass import com.squareup.moshi.ToJson -class RegexAdapter(private val caseInsensitive: Boolean) { +class RegexAdapter { + @ToJson - fun toJson(value: Regex): String = value.toString() + fun toJson(value: Regex): Wrapper = Wrapper( + pattern = value.pattern, + options = value.options, + ) @FromJson - fun fromJson(raw: String): Regex = if (caseInsensitive) { - Regex(raw, RegexOption.IGNORE_CASE) - } else { - Regex(raw) - } + fun fromJson(raw: Wrapper): Regex = Regex( + pattern = raw.pattern, + options = raw.options + ) + + @JsonClass(generateAdapter = true) + data class Wrapper( + @Json(name = "pattern") val pattern: String, + @Json(name = "options") val options: Set + ) } \ No newline at end of file diff --git a/app-common/src/main/java/eu/darken/sdmse/common/serialization/SerializationCommonModule.kt b/app-common/src/main/java/eu/darken/sdmse/common/serialization/SerializationCommonModule.kt index cb4a187d9..6a6c9f7d0 100644 --- a/app-common/src/main/java/eu/darken/sdmse/common/serialization/SerializationCommonModule.kt +++ b/app-common/src/main/java/eu/darken/sdmse/common/serialization/SerializationCommonModule.kt @@ -24,6 +24,7 @@ class SerializationCommonModule { add(FileAdapter()) add(UriAdapter()) add(OffsetDateTimeAdapter()) + add(RegexAdapter()) }.build() } diff --git a/app-common/src/test/java/eu/darken/sdmse/common/serialization/RegexAdapterTest.kt b/app-common/src/test/java/eu/darken/sdmse/common/serialization/RegexAdapterTest.kt new file mode 100644 index 000000000..e7060ebf2 --- /dev/null +++ b/app-common/src/test/java/eu/darken/sdmse/common/serialization/RegexAdapterTest.kt @@ -0,0 +1,77 @@ +package eu.darken.sdmse.common.serialization + +import com.squareup.moshi.JsonClass +import com.squareup.moshi.Moshi +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.json.toComparableJson + +class RegexAdapterTest : BaseTest() { + + val moshi = Moshi.Builder().apply { + add(RegexAdapter()) + }.build() + + @JsonClass(generateAdapter = true) + data class TestContainer( + val regexValue: Regex?, + val regexList: List + ) + + val adapter = moshi.adapter(TestContainer::class.java) + + @Test + fun `serialize test container`() { + val before = TestContainer( + regexValue = Regex("value", RegexOption.LITERAL), + regexList = listOf( + Regex("ele1", RegexOption.COMMENTS), + Regex("ele2", setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.MULTILINE)), + ) + ) + + val rawJson = adapter.toJson(before) + + rawJson.toComparableJson() shouldBe """ + { + "regexValue": { + "pattern": "value", + "options": [ + "LITERAL" + ] + }, + "regexList": [ + { + "pattern": "ele1", + "options": [ + "COMMENTS" + ] + }, + { + "pattern": "ele2", + "options": [ + "MULTILINE", + "DOT_MATCHES_ALL" + ] + } + ] + } + """.toComparableJson() + + val after = adapter.fromJson(rawJson)!! + after.regexValue!!.apply { + pattern shouldBe before.regexValue!!.pattern + options shouldBe before.regexValue.options + } + after.regexList[0].apply { + pattern shouldBe before.regexList[0].pattern + options shouldBe before.regexList[0].options + } + after.regexList[1].apply { + pattern shouldBe before.regexList[1].pattern + options shouldBe before.regexList[1].options + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilter.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilter.kt index 81a107f24..6077ad359 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilter.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilter.kt @@ -47,7 +47,12 @@ class CustomFilter @AssistedInject constructor( }?.toSet(), pathCriteria = filterConfig.pathCriteria, nameCriteria = filterConfig.nameCriteria, - pathExclusions = filterConfig.exclusion, + pathExclusions = filterConfig.exclusionCriteria, + minimumSize = filterConfig.sizeMinimum, + maximumSize = filterConfig.sizeMaximum, + minimumAge = filterConfig.ageMinimum, + maximumAge = filterConfig.ageMaximum, + pathRegexes = filterConfig.pathRegexes, ) sieve = baseSieveFactory.create(sieveConfig) log(TAG) { "initialized()" } diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfig.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfig.kt index 97c9fe4ff..7ca3bcc1f 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfig.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfig.kt @@ -9,21 +9,27 @@ import eu.darken.sdmse.systemcleaner.core.filter.FilterIdentifier import eu.darken.sdmse.systemcleaner.core.sieve.NameCriterium import eu.darken.sdmse.systemcleaner.core.sieve.SegmentCriterium import kotlinx.parcelize.Parcelize +import java.time.Duration import java.time.Instant @Parcelize @JsonClass(generateAdapter = true) data class CustomFilterConfig( - @Json(name = "configVersion") val configVersion: Int = 1, - @Json(name = "identifier") val identifier: FilterIdentifier, + @Json(name = "configVersion") val configVersion: Int = 6, + @Json(name = "id") val identifier: FilterIdentifier, @Json(name = "createdAt") val createdAt: Instant = Instant.now(), @Json(name = "modifiedAt") val modifiedAt: Instant = Instant.now(), @Json(name = "label") val label: String, @Json(name = "areas") val areas: Set? = null, @Json(name = "fileTypes") val fileTypes: Set? = null, @Json(name = "pathCriteria") val pathCriteria: Set? = null, - @Json(name = "pathExclusionCriteria") val exclusion: Set? = null, + @Json(name = "pathExclusionCriteria") val exclusionCriteria: Set? = null, @Json(name = "nameCriteria") val nameCriteria: Set? = null, + @Json(name = "sizeMin") val sizeMinimum: Long? = null, + @Json(name = "sizeMax") val sizeMaximum: Long? = null, + @Json(name = "ageMin") val ageMinimum: Duration? = null, + @Json(name = "ageMax") val ageMaximum: Duration? = null, + @Json(name = "pathRegexes") val pathRegexes: Set? = null, ) : Parcelable { val isUnderdefined: Boolean get() = pathCriteria.isNullOrEmpty() && nameCriteria.isNullOrEmpty() diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterRepo.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterRepo.kt index f11a4fe5d..e6d036a02 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterRepo.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterRepo.kt @@ -7,6 +7,7 @@ import eu.darken.sdmse.common.datastore.value import eu.darken.sdmse.common.debug.logging.Logging.Priority.ERROR import eu.darken.sdmse.common.debug.logging.Logging.Priority.VERBOSE import eu.darken.sdmse.common.debug.logging.Logging.Priority.WARN +import eu.darken.sdmse.common.debug.logging.asLog import eu.darken.sdmse.common.debug.logging.log import eu.darken.sdmse.common.debug.logging.logTag import eu.darken.sdmse.common.serialization.fromFile @@ -32,6 +33,7 @@ class CustomFilterRepo @Inject constructor( private val context: Context, private val baseMoshi: Moshi, private val settings: SystemCleanerSettings, + private val legacyImporter: LegacyFilterSupport, ) { private val moshi by lazy { @@ -123,12 +125,16 @@ class CustomFilterRepo @Inject constructor( suspend fun importFilter(rawFilters: List) { log(TAG) { "importFilter($rawFilters)" } - val configs = rawFilters.mapNotNull { + val configs = rawFilters.map { rawFilter -> try { - configAdapter.fromJson(it.payload) - } catch (e: Exception) { - log(TAG, ERROR) { "Failed to import $it" } - null + configAdapter.fromJson(rawFilter.payload)!! + } catch (ogError: Exception) { + try { + legacyImporter.import(rawFilter.payload)!! + } catch (_: Exception) { + log(TAG, ERROR) { "Failed to import $rawFilter: ${ogError.asLog()}" } + throw ogError + } } }.toSet() save(configs) diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/LegacyFilterSupport.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/LegacyFilterSupport.kt new file mode 100644 index 000000000..6e2becc72 --- /dev/null +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/LegacyFilterSupport.kt @@ -0,0 +1,188 @@ +package eu.darken.sdmse.systemcleaner.core.filter.custom + +import androidx.annotation.Keep +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.squareup.moshi.Moshi +import eu.darken.sdmse.common.areas.DataArea +import eu.darken.sdmse.common.debug.logging.Logging.Priority.WARN +import eu.darken.sdmse.common.debug.logging.asLog +import eu.darken.sdmse.common.debug.logging.log +import eu.darken.sdmse.common.debug.logging.logTag +import eu.darken.sdmse.common.files.FileType +import eu.darken.sdmse.common.files.toSegs +import eu.darken.sdmse.systemcleaner.core.sieve.NameCriterium +import eu.darken.sdmse.systemcleaner.core.sieve.SegmentCriterium +import java.time.Duration +import java.util.UUID +import javax.inject.Inject + + +class LegacyFilterSupport @Inject constructor( + private val moshi: Moshi, +) { + private val adapter by lazy { moshi.adapter(Filter::class.java) } + + suspend fun import(payload: String): CustomFilterConfig? { + log(TAG) { "Importing $payload" } + + val legacyFilter = try { + adapter.fromJson(payload)!! + } catch (e: Exception) { + log(TAG, WARN) { "Failed to import $payload: ${e.asLog()}" } + return null + } + + val pathCriteria = mutableSetOf() + legacyFilter.possibleBasePathes + ?.map { + SegmentCriterium( + segments = it.toSegs(), + mode = SegmentCriterium.Mode.Start(allowPartial = true, ignoreCase = true) + ) + } + ?.forEach { pathCriteria.add(it) } + legacyFilter.possiblePathContains + ?.map { + SegmentCriterium( + segments = it.toSegs(), + mode = SegmentCriterium.Mode.Contain(allowPartial = true, ignoreCase = true) + ) + } + ?.forEach { pathCriteria.add(it) } + + val nameCriteria = mutableSetOf() + legacyFilter.possibleNameInits + ?.map { NameCriterium(name = it, mode = NameCriterium.Mode.Start(ignoreCase = true)) } + ?.forEach { nameCriteria.add(it) } + + legacyFilter.possibleNameEndings + ?.map { NameCriterium(name = it, mode = NameCriterium.Mode.End(ignoreCase = true)) } + ?.forEach { nameCriteria.add(it) } + + return CustomFilterConfig( + label = legacyFilter.label, + identifier = UUID.randomUUID().toString(), + pathCriteria = pathCriteria, + nameCriteria = nameCriteria, + exclusionCriteria = legacyFilter.exclusions?.map { + SegmentCriterium( + segments = it.toSegs(), + mode = SegmentCriterium.Mode.Contain(allowPartial = true, ignoreCase = true) + ) + }?.toSet(), + pathRegexes = legacyFilter.regex?.map { + Regex(it) + }?.toSet(), + sizeMinimum = legacyFilter.minimumSize, + sizeMaximum = legacyFilter.maximumSize, + ageMinimum = legacyFilter.minimumAge?.let { Duration.ofMillis(it) }, + ageMaximum = legacyFilter.maximumAge?.let { Duration.ofMillis(it) }, + fileTypes = legacyFilter.targetType?.let { + when (it) { + Filter.TargetType.FILE -> setOf(FileType.FILE) + Filter.TargetType.DIRECTORY -> setOf(FileType.DIRECTORY) + Filter.TargetType.UNDEFINED -> null + } + }, + areas = legacyFilter.locations?.mapNotNull { + when (it) { + Filter.Location.SDCARD -> DataArea.Type.SDCARD + Filter.Location.PUBLIC_MEDIA -> DataArea.Type.PUBLIC_MEDIA + Filter.Location.PUBLIC_DATA -> DataArea.Type.PUBLIC_DATA + Filter.Location.PUBLIC_OBB -> DataArea.Type.PUBLIC_OBB + Filter.Location.PRIVATE_DATA -> DataArea.Type.PRIVATE_DATA + Filter.Location.APP_LIB -> DataArea.Type.APP_LIB + Filter.Location.APP_ASEC -> DataArea.Type.APP_ASEC + Filter.Location.APP_APP -> DataArea.Type.APP_APP + Filter.Location.APP_APP_PRIVATE -> DataArea.Type.APP_APP_PRIVATE + Filter.Location.DALVIK_DEX -> DataArea.Type.DALVIK_DEX + Filter.Location.DALVIK_PROFILE -> DataArea.Type.DALVIK_PROFILE + Filter.Location.SYSTEM -> DataArea.Type.SYSTEM + Filter.Location.SYSTEM_APP -> DataArea.Type.SYSTEM_APP + Filter.Location.SYSTEM_PRIV_APP -> DataArea.Type.SYSTEM_PRIV_APP + Filter.Location.DOWNLOAD_CACHE -> DataArea.Type.DOWNLOAD_CACHE + Filter.Location.DATA -> DataArea.Type.DATA + Filter.Location.DATA_SYSTEM -> DataArea.Type.DATA_SYSTEM + Filter.Location.DATA_SYSTEM_CE -> DataArea.Type.DATA_SYSTEM_CE + Filter.Location.DATA_SYSTEM_DE -> DataArea.Type.DATA_SYSTEM_DE + Filter.Location.PORTABLE -> DataArea.Type.PORTABLE + Filter.Location.OEM -> DataArea.Type.OEM + Filter.Location.DATA_SDEXT2 -> DataArea.Type.DATA_SDEXT2 + Filter.Location.ROOT -> null + Filter.Location.VENDOR -> null + Filter.Location.MNT_SECURE_ASEC -> null + Filter.Location.UNKNOWN -> null + } + }?.toSet()?.takeIf { it.isNotEmpty() } + ) + } + + @JsonClass(generateAdapter = true) + data class Filter( + @Json(name = "label") val label: String, + @Json(name = "targetType") val targetType: TargetType?, + @Json(name = "isEmpty") val isEmpty: Boolean?, + @Json(name = "locations") val locations: Set?, + @Json(name = "mainPath") val possibleBasePathes: Set?, + @Json(name = "pathContains") val possiblePathContains: Set?, + @Json(name = "possibleNameInits") val possibleNameInits: Set?, + @Json(name = "possibleNameEndings") val possibleNameEndings: Set?, + @Json(name = "exclusions") val exclusions: Set?, + @Json(name = "regexes") val regex: Set?, + @Json(name = "maximumSize") val maximumSize: Long?, + @Json(name = "minimumSize") val minimumSize: Long?, + @Json(name = "maximumAge") val maximumAge: Long?, + @Json(name = "minimumAge") val minimumAge: Long?, + ) { + + // @Json(name = "version") private int version = VERSION; + // @Json(name = "identifier") private String identifier; + // @Json(name = "color") private String color; + // @Json(name = "description") private String description; + // @Nullable @Json(name = "rootOnly") private Boolean rootOnly; + + @Keep + @JsonClass(generateAdapter = false) + enum class TargetType { + @Json(name = "FILE") FILE, + @Json(name = "DIRECTORY") DIRECTORY, + @Json(name = "UNDEFINED") UNDEFINED, + } + + @Keep + @JsonClass(generateAdapter = false) + enum class Location(val raw: String) { + @Json(name = "SDCARD") SDCARD("SDCARD"), + @Json(name = "PUBLIC_MEDIA") PUBLIC_MEDIA("PUBLIC_MEDIA"), + @Json(name = "PUBLIC_DATA") PUBLIC_DATA("PUBLIC_DATA"), + @Json(name = "PUBLIC_OBB") PUBLIC_OBB("PUBLIC_OBB"), + @Json(name = "PRIVATE_DATA") PRIVATE_DATA("PRIVATE_DATA"), + @Json(name = "APP_LIB") APP_LIB("APP_LIB"), + @Json(name = "APP_ASEC") APP_ASEC("APP_ASEC"), + @Json(name = "MNT_SECURE_ASEC") MNT_SECURE_ASEC("MNT_SECURE_ASEC"), + @Json(name = "APP_APP") APP_APP("APP_APP"), + @Json(name = "APP_APP_PRIVATE") APP_APP_PRIVATE("APP_APP_PRIVATE"), + @Json(name = "DALVIK_DEX") DALVIK_DEX("DALVIK_DEX"), + @Json(name = "DALVIK_PROFILE") DALVIK_PROFILE("DALVIK_PROFILE"), + @Json(name = "SYSTEM_APP") SYSTEM_APP("SYSTEM_APP"), + @Json(name = "SYSTEM_PRIV_APP") SYSTEM_PRIV_APP("SYSTEM_PRIV_APP"), + @Json(name = "DOWNLOAD_CACHE") DOWNLOAD_CACHE("DOWNLOAD_CACHE"), + @Json(name = "SYSTEM") SYSTEM("SYSTEM"), + @Json(name = "DATA") DATA("DATA"), + @Json(name = "DATA_SYSTEM") DATA_SYSTEM("DATA_SYSTEM"), + @Json(name = "DATA_SYSTEM_CE") DATA_SYSTEM_CE("DATA_SYSTEM_CE"), + @Json(name = "DATA_SYSTEM_DE") DATA_SYSTEM_DE("DATA_SYSTEM_DE"), + @Json(name = "PORTABLE") PORTABLE("PORTABLE"), + @Json(name = "ROOT") ROOT("ROOT"), + @Json(name = "VENDOR") VENDOR("VENDOR"), + @Json(name = "OEM") OEM("OEM"), + @Json(name = "DATA_SDEXT2") DATA_SDEXT2("DATA_SDEXT2"), + @Json(name = "UNKNOWN") UNKNOWN("UNKNOWN"), + } + } + + companion object { + private val TAG = logTag("SystemCleaner", "CustomFilter", "Repo") + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/AdvertisementFilter.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/AdvertisementFilter.kt index 618532921..7381fa786 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/AdvertisementFilter.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/AdvertisementFilter.kt @@ -169,7 +169,7 @@ class AdvertisementFilter @Inject constructor( val config = BaseSieve.Config( areaTypes = targetAreas(), pfpCriteria = pfpCriteria, - regexes = rawRegexes.map { Regex(it) }.toSet() + pathRegexes = rawRegexes.map { Regex(it) }.toSet() ) sieve = baseSieveFactory.create(config) log(TAG) { "initialized()" } diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/AnalyticsFilter.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/AnalyticsFilter.kt index ecb32d15b..a9f0e5ba9 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/AnalyticsFilter.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/AnalyticsFilter.kt @@ -58,7 +58,7 @@ class AnalyticsFilter @Inject constructor( areaTypes = targetAreas(), targetTypes = setOf(TargetType.FILE), pathCriteria = pathContains, - regexes = regexes, + pathRegexes = regexes, ) sieve = baseSieveFactory.create(config) log(TAG) { "initialized()" } diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/AnrFilter.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/AnrFilter.kt index 4e370b7bd..0d2435a6c 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/AnrFilter.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/AnrFilter.kt @@ -70,7 +70,7 @@ class AnrFilter @Inject constructor( pfpCriteria = setOf( SegmentCriterium(segs("anr"), mode = SegmentCriterium.Mode.Ancestor()) ), - regexes = regexPairs.map { Regex(it.second) }.toSet(), + pathRegexes = regexPairs.map { Regex(it.second) }.toSet(), ) sieve = baseSieveFactory.create(config) diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/LinuxFilesFilter.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/LinuxFilesFilter.kt index ac96719b5..02e55a631 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/LinuxFilesFilter.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/LinuxFilesFilter.kt @@ -49,7 +49,7 @@ class LinuxFilesFilter @Inject constructor( val config = BaseSieve.Config( targetTypes = setOf(BaseSieve.TargetType.DIRECTORY), areaTypes = targetAreas(), - regexes = regexes, + pathRegexes = regexes, ) sieve = baseSieveFactory.create(config) log(TAG) { "initialized()" } diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/LostDirFilter.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/LostDirFilter.kt index 6163b8c63..4d32b4fb9 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/LostDirFilter.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/LostDirFilter.kt @@ -49,7 +49,7 @@ class LostDirFilter @Inject constructor( val config = BaseSieve.Config( targetTypes = setOf(BaseSieve.TargetType.FILE), areaTypes = targetAreas(), - regexes = regexes, + pathRegexes = regexes, ) sieve = baseSieveFactory.create(config) log(TAG) { "initialized()" } diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/UsagestatsFilter.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/UsagestatsFilter.kt index edbae6dbc..d43b00513 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/UsagestatsFilter.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/stock/UsagestatsFilter.kt @@ -52,7 +52,7 @@ class UsagestatsFilter @Inject constructor( pfpCriteria = setOf( SegmentCriterium(segs("usagestats"), mode = SegmentCriterium.Mode.Ancestor()), ), - regexes = setOf( + pathRegexes = setOf( Regex(".+/usagestats/[0-9]+/.+") ) ) diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/sieve/BaseSieve.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/sieve/BaseSieve.kt index d8e7bef56..6678dad20 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/sieve/BaseSieve.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/sieve/BaseSieve.kt @@ -183,7 +183,7 @@ class BaseSieve @AssistedInject constructor( if (isExcluded) return Result(matches = false) } - config.regexes + config.pathRegexes ?.takeIf { it.isNotEmpty() } ?.let { regexes -> if (regexes.none { it.matches(subject.path) }) return Result(matches = false) @@ -295,8 +295,8 @@ class BaseSieve @AssistedInject constructor( val nameCriteria: Set? = null, val pathExclusions: Set? = null, val pfpExclusions: Set? = null, + val pathRegexes: Set? = null, val isEmpty: Boolean? = null, - val regexes: Set? = null, val maximumSize: Long? = null, val minimumSize: Long? = null, val maximumAge: Duration? = null, diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/customfilter/editor/CustomFilterEditorFragment.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/customfilter/editor/CustomFilterEditorFragment.kt index 014fd4abf..cc719c19f 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/customfilter/editor/CustomFilterEditorFragment.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/customfilter/editor/CustomFilterEditorFragment.kt @@ -146,7 +146,7 @@ class CustomFilterEditorFragment : Fragment3(R.layout.systemcleaner_customfilter config.nameCriteria?.toList() ?: emptyList() ) exclusionsInput.setTags( - config.exclusion?.toList() ?: emptyList() + config.exclusionCriteria?.toList() ?: emptyList() ) areaChips.entries.forEach { (type, chip) -> diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/customfilter/editor/CustomFilterEditorViewModel.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/customfilter/editor/CustomFilterEditorViewModel.kt index 57a5c5a6b..3136c4b88 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/customfilter/editor/CustomFilterEditorViewModel.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/customfilter/editor/CustomFilterEditorViewModel.kt @@ -166,16 +166,16 @@ class CustomFilterEditorViewModel @Inject constructor( fun addExclusion(criterium: SegmentCriterium) = launch { log(TAG) { "addExclusion($criterium)" } currentState.updateBlocking { - val new = (current.exclusion ?: emptySet()).toMutableSet().apply { add(criterium) } - copy(current = current.copy(exclusion = new)) + val new = (current.exclusionCriteria ?: emptySet()).toMutableSet().apply { add(criterium) } + copy(current = current.copy(exclusionCriteria = new)) } } fun removeExclusion(criterium: SegmentCriterium) = launch { log(TAG) { "removeExclusion($criterium)" } currentState.updateBlocking { - val new = (current.exclusion ?: emptySet()).toMutableSet().apply { remove(criterium) } - copy(current = current.copy(exclusion = new.takeIf { it.isNotEmpty() })) + val new = (current.exclusionCriteria ?: emptySet()).toMutableSet().apply { remove(criterium) } + copy(current = current.copy(exclusionCriteria = new.takeIf { it.isNotEmpty() })) } } diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/customfilter/list/CustomFilterListViewModel.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/customfilter/list/CustomFilterListViewModel.kt index 9994da9d6..2bcbef854 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/customfilter/list/CustomFilterListViewModel.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/customfilter/list/CustomFilterListViewModel.kt @@ -124,7 +124,11 @@ class CustomFilterListViewModel @Inject constructor( } .map { RawFilter(it.first.toString(), it.second) } - customFilterRepo.importFilter(rawFilter) + try { + customFilterRepo.importFilter(rawFilter) + } catch (e: Exception) { + errorEvents.postValue(e) + } } private var stagedExport: Collection? = null diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/settings/SystemCleanerSettingsFragment.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/settings/SystemCleanerSettingsFragment.kt index b67b97a72..d220e8e4c 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/settings/SystemCleanerSettingsFragment.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/ui/settings/SystemCleanerSettingsFragment.kt @@ -2,7 +2,6 @@ package eu.darken.sdmse.systemcleaner.ui.settings import android.os.Bundle import android.view.View -import android.widget.Toast import androidx.annotation.Keep import androidx.fragment.app.viewModels import androidx.preference.Preference @@ -38,9 +37,6 @@ class SystemCleanerSettingsFragment : PreferenceFragment2() { override fun onPreferencesCreated() { super.onPreferencesCreated() findPreference("filter.custom")!!.setOnPreferenceClickListener { - if (isPro == false) { - Toast.makeText(requireContext(), R.string.upgrade_feature_requires_pro, Toast.LENGTH_SHORT).show() - } SettingsFragmentDirections.actionSettingsContainerFragmentToCustomFilterListFragment().navigate() true } diff --git a/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/BaseSieveTest.kt b/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/BaseSieveTest.kt index be4715e53..68d45d022 100644 --- a/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/BaseSieveTest.kt +++ b/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/BaseSieveTest.kt @@ -118,7 +118,7 @@ class BaseSieveTest : BaseTest() { @Test fun `just regex`() = runTest { val config = Config( - regexes = setOf(Regex(".+/a.c/[0-9]+$")) + pathRegexes = setOf(Regex(".+/a.c/[0-9]+$")) ) config.match( diff --git a/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfigTest.kt b/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfigTest.kt index 660cdb3a9..c516ebb48 100644 --- a/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfigTest.kt +++ b/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfigTest.kt @@ -42,7 +42,7 @@ class CustomFilterConfigTest : BaseTest() { rawJson.toComparableJson() shouldBe """ { "configVersion": 1.0, - "identifier": "some-id", + "id": "some-id", "createdAt": "2023-08-14T19:55:42.921588Z", "modifiedAt": "2023-08-14T19:55:42.921591Z", "label": "My label", diff --git a/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/custom/LegacyFilterSupportTest.kt b/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/custom/LegacyFilterSupportTest.kt new file mode 100644 index 000000000..7edfed271 --- /dev/null +++ b/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/custom/LegacyFilterSupportTest.kt @@ -0,0 +1,121 @@ +package eu.darken.sdmse.systemcleaner.core.filter.custom + +import eu.darken.sdmse.common.areas.DataArea +import eu.darken.sdmse.common.files.FileType +import eu.darken.sdmse.common.files.segs +import eu.darken.sdmse.common.serialization.SerializationAppModule +import eu.darken.sdmse.systemcleaner.core.sieve.NameCriterium +import eu.darken.sdmse.systemcleaner.core.sieve.SegmentCriterium +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class LegacyFilterSupportTest : BaseTest() { + + private fun create() = LegacyFilterSupport( + moshi = SerializationAppModule().moshi() + ) + + @Test + fun `import SD Maid 1 legacy v5 filter`() = runTest { + val rawLegacyFilter = """ + { + "color": "#03a9f4", + "description": "5 items @ /storage/emulated/0", + "exclusions": [ + "backup" + ], + "identifier": "91c26321f304.scuf.sdm", + "isEmpty": false, + "label": "Legacy Test Filter", + "locations": [ + "SDCARD", "PORTABLE" + ], + "mainPath": [ + "/storage/emulated/0/Bpics/", + "/storage/emulated/0/atest.json", + "/storage/emulated/0/eu.darken.sdmse-test-area-access", + "/storage/emulated/0/eu.darken.sdmse-test-area-access" + ], + "maximumAge": 999999, + "maximumSize": 9001, + "minimumAge": 333333, + "minimumSize": 13, + "pathContains": [ + "atest" + ], + "possibleNameEndings": [ + ".json", + "5" + ], + "possibleNameInits": [ + "a", + "e" + ], + "regexes": [ + ".+" + ], + "targetType": "FILE", + "version": 4 + } + """.trimIndent() + + + val importedFilter = create().import(rawLegacyFilter) + importedFilter shouldNotBe null + importedFilter!!.apply { + label shouldBe "Legacy Test Filter" + areas shouldBe setOf(DataArea.Type.SDCARD, DataArea.Type.PORTABLE) + pathCriteria shouldBe setOf( + SegmentCriterium( + segments = segs("", "storage", "emulated", "0", "Bpics", ""), + mode = SegmentCriterium.Mode.Start(allowPartial = true, ignoreCase = true), + ), + SegmentCriterium( + segments = segs("", "storage", "emulated", "0", "atest.json"), + mode = SegmentCriterium.Mode.Start(allowPartial = true, ignoreCase = true), + ), + SegmentCriterium( + segments = segs("", "storage", "emulated", "0", "eu.darken.sdmse-test-area-access"), + mode = SegmentCriterium.Mode.Start(allowPartial = true, ignoreCase = true), + ), + SegmentCriterium( + segments = segs("", "storage", "emulated", "0", "eu.darken.sdmse-test-area-access"), + mode = SegmentCriterium.Mode.Start(allowPartial = true, ignoreCase = true), + ), + SegmentCriterium( + segments = segs("atest"), + mode = SegmentCriterium.Mode.Contain(allowPartial = true, ignoreCase = true), + ), + ) + nameCriteria shouldBe setOf( + NameCriterium( + name = ".json", + mode = NameCriterium.Mode.End(ignoreCase = true), + ), + NameCriterium( + name = "5", + mode = NameCriterium.Mode.End(ignoreCase = true), + ), + NameCriterium( + name = "a", + mode = NameCriterium.Mode.Start(ignoreCase = true), + ), + NameCriterium( + name = "e", + mode = NameCriterium.Mode.Start(ignoreCase = true), + ), + ) + + fileTypes shouldBe setOf(FileType.FILE) + exclusionCriteria shouldBe setOf( + SegmentCriterium( + segments = segs("backup"), + mode = SegmentCriterium.Mode.Contain(allowPartial = true, ignoreCase = true), + ), + ) + } + } +} \ No newline at end of file From 72bf00e7b1b34541d77e8e112a00898701e986f8 Mon Sep 17 00:00:00 2001 From: darken Date: Sat, 19 Aug 2023 15:01:53 +0200 Subject: [PATCH 2/2] Fix test --- .../systemcleaner/core/filter/custom/CustomFilterConfig.kt | 2 +- .../systemcleaner/core/filter/custom/CustomFilterConfigTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfig.kt b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfig.kt index 7ca3bcc1f..2abb08079 100644 --- a/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfig.kt +++ b/app/src/main/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfig.kt @@ -15,7 +15,7 @@ import java.time.Instant @Parcelize @JsonClass(generateAdapter = true) data class CustomFilterConfig( - @Json(name = "configVersion") val configVersion: Int = 6, + @Json(name = "configVersion") val configVersion: Long = 6L, @Json(name = "id") val identifier: FilterIdentifier, @Json(name = "createdAt") val createdAt: Instant = Instant.now(), @Json(name = "modifiedAt") val modifiedAt: Instant = Instant.now(), diff --git a/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfigTest.kt b/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfigTest.kt index c516ebb48..4060b7947 100644 --- a/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfigTest.kt +++ b/app/src/test/java/eu/darken/sdmse/systemcleaner/core/filter/custom/CustomFilterConfigTest.kt @@ -41,7 +41,7 @@ class CustomFilterConfigTest : BaseTest() { val rawJson = adapter.toJson(original) rawJson.toComparableJson() shouldBe """ { - "configVersion": 1.0, + "configVersion": 6, "id": "some-id", "createdAt": "2023-08-14T19:55:42.921588Z", "modifiedAt": "2023-08-14T19:55:42.921591Z",