diff --git a/CHANGELOG.md b/CHANGELOG.md index 0742fbf64..6b845741a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co - Add support for using the user's chosen rating system for Kitsu ([@MajorTanya](https://github.com/MajorTanya)) ([#3818](https://github.com/mihonapp/mihon/pull/3818)) - Add support for the Year, Month, and Day fields in ComicInfo.xml files for chapter dating ([@MajorTanya](https://github.com/MajorTanya)) ([#3967](https://github.com/mihonapp/mihon/pull/3967)) - Add refresh buttons to trackers in Settings to update displayed usernames (all trackers) & rating systems (where supported) ([@MajorTanya](https://github.com/MajorTanya)) ([#3828](https://github.com/mihonapp/mihon/pull/3828)) +- Add setting to toggle chapter name hash suffix ([@choppeh](https://github.com/choppeh)) ([#3966](https://github.com/mihonapp/mihon/pull/3966)) ### Improved - Show updates and upcoming filter icon as active for categories ([@Secozzi](https://github.com/Secozzi)) ([#3772](https://github.com/mihonapp/mihon/pull/3772)) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d4c3d7a4f..9e7717436 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,7 +37,7 @@ android { defaultConfig { applicationId = "app.mihon" - versionCode = 33 + versionCode = 34 versionName = "0.20.4" buildConfigField("boolean", "TELEMETRY_INCLUDED", "${Config.includeTelemetry}") diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt index aa9421c93..0485a4103 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt @@ -318,6 +318,11 @@ object SettingsAdvancedScreen : SearchableSettings { title = stringResource(MR.strings.pref_disallow_non_ascii_filenames), subtitle = stringResource(MR.strings.pref_disallow_non_ascii_filenames_details), ), + Preference.PreferenceItem.SwitchPreference( + preference = libraryPreferences.enableChapterNameHash, + title = stringResource(MR.strings.pref_enable_chapter_name_hash), + subtitle = stringResource(MR.strings.pref_enable_chapter_name_hash_details), + ), ), ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt index e4d2ec18c..5e5f5e442 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt @@ -167,15 +167,25 @@ class DownloadProvider( chapterScanlator: String?, chapterUrl: String, disallowNonAsciiFilenames: Boolean = libraryPreferences.disallowNonAsciiFilenames.get(), + enableChapterNameHash: Boolean = libraryPreferences.enableChapterNameHash.get(), ): String { - var dirName = sanitizeChapterName(chapterName) - if (!chapterScanlator.isNullOrBlank()) { - dirName = chapterScanlator + "_" + dirName + return buildString { + if (!chapterScanlator.isNullOrBlank()) { + append(chapterScanlator + "_") + } + + // Subtract 7 bytes for hash and underscore, 4 bytes for .cbz + append( + DiskUtil.buildValidFilename( + sanitizeChapterName(chapterName), + DiskUtil.MAX_FILE_NAME_BYTES - 11, + disallowNonAsciiFilenames, + ), + ) + if (enableChapterNameHash) { + append("_${md5(chapterUrl).take(6)}") + } } - // Subtract 7 bytes for hash and underscore, 4 bytes for .cbz - dirName = DiskUtil.buildValidFilename(dirName, DiskUtil.MAX_FILE_NAME_BYTES - 11, disallowNonAsciiFilenames) - dirName += "_" + md5(chapterUrl).take(6) - return dirName } /** @@ -191,7 +201,7 @@ class DownloadProvider( chapterName: String, chapterScanlator: String?, chapterUrl: String, - ): List { + ): Set { val sanitizedChapterName = sanitizeChapterName(chapterName) val chapterNameV1 = DiskUtil.buildValidFilename( when { @@ -200,23 +210,27 @@ class DownloadProvider( }, ) - // Get the filename that would be generated if the user were - // using the other value for the disallow non-ASCII - // filenames setting. This ensures that chapters downloaded - // before the user changed the setting can still be found. - val otherChapterDirName = - getChapterDirName( - chapterName, - chapterScanlator, - chapterUrl, - !libraryPreferences.disallowNonAsciiFilenames.get(), - ) + // Generate all possible legacy directory name variations by combining + // different states of non-ASCII filenames and chapter name hash settings. + // This ensures that chapters downloaded under any past configuration + // combination can still be successfully found. + val booleanPairPermutation = listOf(false to false, false to true, true to false, true to true) + val othersChapterDirNames = booleanPairPermutation + .map { (disallowNonAsciiFilenames, enableChapterNameHash) -> + getChapterDirName( + chapterName, + chapterScanlator, + chapterUrl, + disallowNonAsciiFilenames, + enableChapterNameHash, + ) + } - return buildList(2) { + return buildSet { // Chapter name without hash (unable to handle duplicate // chapter names) add(chapterNameV1) - add(otherChapterDirName) + addAll(othersChapterDirNames) } } diff --git a/app/src/main/java/mihon/core/migration/migrations/ChapterNameSuffixMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ChapterNameSuffixMigration.kt new file mode 100644 index 000000000..dcba9ca5b --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ChapterNameSuffixMigration.kt @@ -0,0 +1,23 @@ +package mihon.core.migration.migrations + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesIntoSet +import dev.zacsweers.metro.Inject +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.domain.library.service.LibraryPreferences + +@Inject +@ContributesIntoSet(AppScope::class) +class ChapterNameSuffixMigration( + private val preference: LibraryPreferences, +) : Migration { + override val version: Float = 34f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + if (migrationContext.previousVersion in 13..33) { + preference.enableChapterNameHash.set(true) + } + return true + } +} diff --git a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt index c402efbf2..eee7e2f06 100644 --- a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt @@ -219,6 +219,10 @@ class LibraryPreferences( "disallow_non_ascii_filenames", false, ) + val enableChapterNameHash: Preference = preferenceStore.getBoolean( + "pref_enable_chapter_name_hash", + false, + ) // endregion diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 0917e50ce..9116e336f 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -326,6 +326,8 @@ Hide missing chapter indicators Disallow non-ASCII filenames Ensures compatibility with certain storage media that don't support Unicode. When this is enabled, you'll need to manually rename source and manga folders by replacing non-ASCII characters with their lowercase UTF-8 hexadecimal representations. Chapter files don't need to be renamed. + Enable chapter name hash suffix + Ensures a unique hash suffix is added to chapter names to prevent naming conflicts. If disabled, chapters with identical names cannot be downloaded. Multi