Migrations.kt 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. package eu.kanade.tachiyomi
  2. import android.content.Context
  3. import androidx.core.content.edit
  4. import androidx.preference.PreferenceManager
  5. import eu.kanade.domain.base.BasePreferences
  6. import eu.kanade.domain.source.service.SourcePreferences
  7. import eu.kanade.domain.ui.UiPreferences
  8. import eu.kanade.tachiyomi.core.security.SecurityPreferences
  9. import eu.kanade.tachiyomi.data.backup.BackupCreateJob
  10. import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
  11. import eu.kanade.tachiyomi.data.track.TrackerManager
  12. import eu.kanade.tachiyomi.network.NetworkPreferences
  13. import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
  14. import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
  15. import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
  16. import eu.kanade.tachiyomi.util.system.DeviceUtil
  17. import eu.kanade.tachiyomi.util.system.toast
  18. import eu.kanade.tachiyomi.util.system.workManager
  19. import tachiyomi.core.preference.Preference
  20. import tachiyomi.core.preference.PreferenceStore
  21. import tachiyomi.core.preference.TriState
  22. import tachiyomi.core.preference.getAndSet
  23. import tachiyomi.core.preference.getEnum
  24. import tachiyomi.core.preference.minusAssign
  25. import tachiyomi.core.preference.plusAssign
  26. import tachiyomi.domain.backup.service.BackupPreferences
  27. import tachiyomi.domain.library.service.LibraryPreferences
  28. import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
  29. import tachiyomi.i18n.MR
  30. import java.io.File
  31. object Migrations {
  32. /**
  33. * Performs a migration when the application is updated.
  34. *
  35. * @return true if a migration is performed, false otherwise.
  36. */
  37. fun upgrade(
  38. context: Context,
  39. preferenceStore: PreferenceStore,
  40. basePreferences: BasePreferences,
  41. uiPreferences: UiPreferences,
  42. networkPreferences: NetworkPreferences,
  43. sourcePreferences: SourcePreferences,
  44. securityPreferences: SecurityPreferences,
  45. libraryPreferences: LibraryPreferences,
  46. readerPreferences: ReaderPreferences,
  47. backupPreferences: BackupPreferences,
  48. trackerManager: TrackerManager,
  49. ): Boolean {
  50. val lastVersionCode = preferenceStore.getInt("last_version_code", 0)
  51. val oldVersion = lastVersionCode.get()
  52. if (oldVersion < BuildConfig.VERSION_CODE) {
  53. lastVersionCode.set(BuildConfig.VERSION_CODE)
  54. // Always set up background tasks to ensure they're running
  55. LibraryUpdateJob.setupTask(context)
  56. BackupCreateJob.setupTask(context)
  57. // Fresh install
  58. if (oldVersion == 0) {
  59. return false
  60. }
  61. val prefs = PreferenceManager.getDefaultSharedPreferences(context)
  62. if (oldVersion < 14) {
  63. // Restore jobs after upgrading to Evernote's job scheduler.
  64. LibraryUpdateJob.setupTask(context)
  65. }
  66. if (oldVersion < 15) {
  67. // Delete internal chapter cache dir.
  68. File(context.cacheDir, "chapter_disk_cache").deleteRecursively()
  69. }
  70. if (oldVersion < 19) {
  71. // Move covers to external files dir.
  72. val oldDir = File(context.externalCacheDir, "cover_disk_cache")
  73. if (oldDir.exists()) {
  74. val destDir = context.getExternalFilesDir("covers")
  75. if (destDir != null) {
  76. oldDir.listFiles()?.forEach {
  77. it.renameTo(File(destDir, it.name))
  78. }
  79. }
  80. }
  81. }
  82. if (oldVersion < 26) {
  83. // Delete external chapter cache dir.
  84. val extCache = context.externalCacheDir
  85. if (extCache != null) {
  86. val chapterCache = File(extCache, "chapter_disk_cache")
  87. if (chapterCache.exists()) {
  88. chapterCache.deleteRecursively()
  89. }
  90. }
  91. }
  92. if (oldVersion < 43) {
  93. // Restore jobs after migrating from Evernote's job scheduler to WorkManager.
  94. LibraryUpdateJob.setupTask(context)
  95. BackupCreateJob.setupTask(context)
  96. }
  97. if (oldVersion < 44) {
  98. // Reset sorting preference if using removed sort by source
  99. val oldSortingMode = prefs.getInt(libraryPreferences.sortingMode().key(), 0)
  100. if (oldSortingMode == 5) { // SOURCE = 5
  101. prefs.edit {
  102. putInt(libraryPreferences.sortingMode().key(), 0) // ALPHABETICAL = 0
  103. }
  104. }
  105. }
  106. if (oldVersion < 52) {
  107. // Migrate library filters to tri-state versions
  108. fun convertBooleanPrefToTriState(key: String): Int {
  109. val oldPrefValue = prefs.getBoolean(key, false)
  110. return if (oldPrefValue) {
  111. 1
  112. } else {
  113. 0
  114. }
  115. }
  116. prefs.edit {
  117. putInt(
  118. libraryPreferences.filterDownloaded().key(),
  119. convertBooleanPrefToTriState("pref_filter_downloaded_key"),
  120. )
  121. remove("pref_filter_downloaded_key")
  122. putInt(
  123. libraryPreferences.filterUnread().key(),
  124. convertBooleanPrefToTriState("pref_filter_unread_key"),
  125. )
  126. remove("pref_filter_unread_key")
  127. putInt(
  128. libraryPreferences.filterCompleted().key(),
  129. convertBooleanPrefToTriState("pref_filter_completed_key"),
  130. )
  131. remove("pref_filter_completed_key")
  132. }
  133. }
  134. if (oldVersion < 54) {
  135. // Force MAL log out due to login flow change
  136. // v52: switched from scraping to WebView
  137. // v53: switched from WebView to OAuth
  138. if (trackerManager.myAnimeList.isLoggedIn) {
  139. trackerManager.myAnimeList.logout()
  140. context.toast(MR.strings.myanimelist_relogin)
  141. }
  142. }
  143. if (oldVersion < 57) {
  144. // Migrate DNS over HTTPS setting
  145. val wasDohEnabled = prefs.getBoolean("enable_doh", false)
  146. if (wasDohEnabled) {
  147. prefs.edit {
  148. putInt(networkPreferences.dohProvider().key(), PREF_DOH_CLOUDFLARE)
  149. remove("enable_doh")
  150. }
  151. }
  152. }
  153. if (oldVersion < 59) {
  154. // Reset rotation to Free after replacing Lock
  155. if (prefs.contains("pref_rotation_type_key")) {
  156. prefs.edit {
  157. putInt("pref_rotation_type_key", 1)
  158. }
  159. }
  160. }
  161. if (oldVersion < 60) {
  162. // Migrate Rotation and Viewer values to default values for viewer_flags
  163. val newOrientation = when (prefs.getInt("pref_rotation_type_key", 1)) {
  164. 1 -> ReaderOrientation.FREE.flagValue
  165. 2 -> ReaderOrientation.PORTRAIT.flagValue
  166. 3 -> ReaderOrientation.LANDSCAPE.flagValue
  167. 4 -> ReaderOrientation.LOCKED_PORTRAIT.flagValue
  168. 5 -> ReaderOrientation.LOCKED_LANDSCAPE.flagValue
  169. else -> ReaderOrientation.FREE.flagValue
  170. }
  171. // Reading mode flag and prefValue is the same value
  172. val newReadingMode = prefs.getInt("pref_default_viewer_key", 1)
  173. prefs.edit {
  174. putInt("pref_default_orientation_type_key", newOrientation)
  175. remove("pref_rotation_type_key")
  176. putInt("pref_default_reading_mode_key", newReadingMode)
  177. remove("pref_default_viewer_key")
  178. }
  179. }
  180. if (oldVersion < 61) {
  181. // Handle removed every 1 or 2 hour library updates
  182. val updateInterval = libraryPreferences.autoUpdateInterval().get()
  183. if (updateInterval == 1 || updateInterval == 2) {
  184. libraryPreferences.autoUpdateInterval().set(3)
  185. LibraryUpdateJob.setupTask(context, 3)
  186. }
  187. }
  188. if (oldVersion < 64) {
  189. val oldSortingMode = prefs.getInt(libraryPreferences.sortingMode().key(), 0)
  190. val oldSortingDirection = prefs.getBoolean("library_sorting_ascending", true)
  191. val newSortingMode = when (oldSortingMode) {
  192. 0 -> "ALPHABETICAL"
  193. 1 -> "LAST_READ"
  194. 2 -> "LAST_CHECKED"
  195. 3 -> "UNREAD"
  196. 4 -> "TOTAL_CHAPTERS"
  197. 6 -> "LATEST_CHAPTER"
  198. 8 -> "DATE_FETCHED"
  199. 7 -> "DATE_ADDED"
  200. else -> "ALPHABETICAL"
  201. }
  202. val newSortingDirection = when (oldSortingDirection) {
  203. true -> "ASCENDING"
  204. else -> "DESCENDING"
  205. }
  206. prefs.edit(commit = true) {
  207. remove(libraryPreferences.sortingMode().key())
  208. remove("library_sorting_ascending")
  209. }
  210. prefs.edit {
  211. putString(libraryPreferences.sortingMode().key(), newSortingMode)
  212. putString("library_sorting_ascending", newSortingDirection)
  213. }
  214. }
  215. if (oldVersion < 70) {
  216. if (sourcePreferences.enabledLanguages().isSet()) {
  217. sourcePreferences.enabledLanguages() += "all"
  218. }
  219. }
  220. if (oldVersion < 71) {
  221. // Handle removed every 3, 4, 6, and 8 hour library updates
  222. val updateInterval = libraryPreferences.autoUpdateInterval().get()
  223. if (updateInterval in listOf(3, 4, 6, 8)) {
  224. libraryPreferences.autoUpdateInterval().set(12)
  225. LibraryUpdateJob.setupTask(context, 12)
  226. }
  227. }
  228. if (oldVersion < 72) {
  229. val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true)
  230. if (!oldUpdateOngoingOnly) {
  231. libraryPreferences.autoUpdateMangaRestrictions() -= MANGA_NON_COMPLETED
  232. }
  233. }
  234. if (oldVersion < 75) {
  235. val oldSecureScreen = prefs.getBoolean("secure_screen", false)
  236. if (oldSecureScreen) {
  237. securityPreferences.secureScreen().set(SecurityPreferences.SecureScreenMode.ALWAYS)
  238. }
  239. if (
  240. DeviceUtil.isMiui &&
  241. basePreferences.extensionInstaller().get() == BasePreferences.ExtensionInstaller.PACKAGEINSTALLER
  242. ) {
  243. basePreferences.extensionInstaller().set(BasePreferences.ExtensionInstaller.LEGACY)
  244. }
  245. }
  246. if (oldVersion < 76) {
  247. BackupCreateJob.setupTask(context)
  248. }
  249. if (oldVersion < 77) {
  250. val oldReaderTap = prefs.getBoolean("reader_tap", false)
  251. if (!oldReaderTap) {
  252. readerPreferences.navigationModePager().set(5)
  253. readerPreferences.navigationModeWebtoon().set(5)
  254. }
  255. }
  256. if (oldVersion < 81) {
  257. // Handle renamed enum values
  258. prefs.edit {
  259. val newSortingMode = when (
  260. val oldSortingMode = prefs.getString(
  261. libraryPreferences.sortingMode().key(),
  262. "ALPHABETICAL",
  263. )
  264. ) {
  265. "LAST_CHECKED" -> "LAST_MANGA_UPDATE"
  266. "UNREAD" -> "UNREAD_COUNT"
  267. "DATE_FETCHED" -> "CHAPTER_FETCH_DATE"
  268. else -> oldSortingMode
  269. }
  270. putString(libraryPreferences.sortingMode().key(), newSortingMode)
  271. }
  272. }
  273. if (oldVersion < 82) {
  274. prefs.edit {
  275. val sort = prefs.getString(libraryPreferences.sortingMode().key(), null) ?: return@edit
  276. val direction = prefs.getString("library_sorting_ascending", "ASCENDING")!!
  277. putString(libraryPreferences.sortingMode().key(), "$sort,$direction")
  278. remove("library_sorting_ascending")
  279. }
  280. }
  281. if (oldVersion < 84) {
  282. if (backupPreferences.backupInterval().get() == 0) {
  283. backupPreferences.backupInterval().set(12)
  284. BackupCreateJob.setupTask(context)
  285. }
  286. }
  287. if (oldVersion < 85) {
  288. val preferences = listOf(
  289. libraryPreferences.filterChapterByRead(),
  290. libraryPreferences.filterChapterByDownloaded(),
  291. libraryPreferences.filterChapterByBookmarked(),
  292. libraryPreferences.sortChapterBySourceOrNumber(),
  293. libraryPreferences.displayChapterByNameOrNumber(),
  294. libraryPreferences.sortChapterByAscendingOrDescending(),
  295. )
  296. prefs.edit {
  297. preferences.forEach { preference ->
  298. val key = preference.key()
  299. val value = prefs.getInt(key, Int.MIN_VALUE)
  300. if (value == Int.MIN_VALUE) return@forEach
  301. remove(key)
  302. putLong(key, value.toLong())
  303. }
  304. }
  305. }
  306. if (oldVersion < 86) {
  307. if (uiPreferences.themeMode().isSet()) {
  308. prefs.edit {
  309. val themeMode = prefs.getString(uiPreferences.themeMode().key(), null) ?: return@edit
  310. putString(uiPreferences.themeMode().key(), themeMode.uppercase())
  311. }
  312. }
  313. }
  314. if (oldVersion < 92) {
  315. val trackingQueuePref = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
  316. trackingQueuePref.all.forEach {
  317. val (_, lastChapterRead) = it.value.toString().split(":")
  318. trackingQueuePref.edit {
  319. remove(it.key)
  320. putFloat(it.key, lastChapterRead.toFloat())
  321. }
  322. }
  323. }
  324. if (oldVersion < 96) {
  325. LibraryUpdateJob.cancelAllWorks(context)
  326. LibraryUpdateJob.setupTask(context)
  327. }
  328. if (oldVersion < 97) {
  329. // Removed background jobs
  330. context.workManager.cancelAllWorkByTag("UpdateChecker")
  331. context.workManager.cancelAllWorkByTag("ExtensionUpdate")
  332. prefs.edit {
  333. remove("automatic_ext_updates")
  334. }
  335. }
  336. if (oldVersion < 99) {
  337. val prefKeys = listOf(
  338. "pref_filter_library_downloaded",
  339. "pref_filter_library_unread",
  340. "pref_filter_library_started",
  341. "pref_filter_library_bookmarked",
  342. "pref_filter_library_completed",
  343. ) + trackerManager.trackers.map { "pref_filter_library_tracked_${it.id}" }
  344. prefKeys.forEach { key ->
  345. val pref = preferenceStore.getInt(key, 0)
  346. prefs.edit {
  347. remove(key)
  348. val newValue = when (pref.get()) {
  349. 1 -> TriState.ENABLED_IS
  350. 2 -> TriState.ENABLED_NOT
  351. else -> TriState.DISABLED
  352. }
  353. preferenceStore.getEnum("${key}_v2", TriState.DISABLED).set(newValue)
  354. }
  355. }
  356. }
  357. if (oldVersion < 100) {
  358. BackupCreateJob.setupTask(context)
  359. }
  360. if (oldVersion < 105) {
  361. val pref = libraryPreferences.autoUpdateDeviceRestrictions()
  362. if (pref.isSet() && "battery_not_low" in pref.get()) {
  363. pref.getAndSet { it - "battery_not_low" }
  364. }
  365. }
  366. if (oldVersion < 106) {
  367. val pref = preferenceStore.getInt("relative_time", 7)
  368. if (pref.get() == 0) {
  369. uiPreferences.relativeTime().set(false)
  370. }
  371. }
  372. if (oldVersion < 107) {
  373. replacePreferences(
  374. preferenceStore = preferenceStore,
  375. filterPredicate = { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") },
  376. newKey = { Preference.privateKey(it) },
  377. )
  378. }
  379. if (oldVersion < 108) {
  380. val prefsToReplace = listOf(
  381. "pref_download_only",
  382. "incognito_mode",
  383. "last_catalogue_source",
  384. "trusted_signatures",
  385. "last_app_closed",
  386. "library_update_last_timestamp",
  387. "library_unseen_updates_count",
  388. "last_used_category",
  389. )
  390. replacePreferences(
  391. preferenceStore = preferenceStore,
  392. filterPredicate = { it.key in prefsToReplace },
  393. newKey = { Preference.appStateKey(it) },
  394. )
  395. }
  396. return true
  397. }
  398. return false
  399. }
  400. }
  401. @Suppress("UNCHECKED_CAST")
  402. private fun replacePreferences(
  403. preferenceStore: PreferenceStore,
  404. filterPredicate: (Map.Entry<String, Any?>) -> Boolean,
  405. newKey: (String) -> String,
  406. ) {
  407. preferenceStore.getAll()
  408. .filter(filterPredicate)
  409. .forEach { (key, value) ->
  410. when (value) {
  411. is Int -> {
  412. preferenceStore.getInt(newKey(key)).set(value)
  413. preferenceStore.getInt(key).delete()
  414. }
  415. is Long -> {
  416. preferenceStore.getLong(newKey(key)).set(value)
  417. preferenceStore.getLong(key).delete()
  418. }
  419. is Float -> {
  420. preferenceStore.getFloat(newKey(key)).set(value)
  421. preferenceStore.getFloat(key).delete()
  422. }
  423. is String -> {
  424. preferenceStore.getString(newKey(key)).set(value)
  425. preferenceStore.getString(key).delete()
  426. }
  427. is Boolean -> {
  428. preferenceStore.getBoolean(newKey(key)).set(value)
  429. preferenceStore.getBoolean(key).delete()
  430. }
  431. is Set<*> -> (value as? Set<String>)?.let {
  432. preferenceStore.getStringSet(newKey(key)).set(value)
  433. preferenceStore.getStringSet(key).delete()
  434. }
  435. }
  436. }
  437. }