浏览代码

Select categories for global update

len 8 年之前
父节点
当前提交
91829b0e7d

+ 9 - 24
app/src/main/AndroidManifest.xml

@@ -59,6 +59,15 @@
         <service android:name=".data.mangasync.UpdateMangaSyncService"
             android:exported="false"/>
 
+        <service
+            android:name=".data.library.LibraryUpdateTrigger"
+            android:exported="true"
+            android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE">
+            <intent-filter>
+                <action android:name="com.google.android.gms.gcm.ACTION_TASK_READY" />
+            </intent-filter>
+        </service>
+
         <service
             android:name=".data.updater.UpdateCheckerService"
             android:exported="true"
@@ -73,34 +82,10 @@
 
         <receiver android:name=".data.updater.UpdateNotificationReceiver"/>
 
-        <receiver
-            android:name=".data.library.LibraryUpdateService$SyncOnConnectionAvailable"
-            android:enabled="false">
-            <intent-filter>
-                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
-            </intent-filter>
-        </receiver>
-
-        <receiver
-            android:name=".data.library.LibraryUpdateService$SyncOnPowerConnected"
-            android:enabled="false">
-            <intent-filter>
-                <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
-            </intent-filter>
-        </receiver>
-
         <receiver
             android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver">
         </receiver>
 
-        <receiver
-            android:name=".data.library.LibraryUpdateAlarm">
-            <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED"/>
-                <action android:name="eu.kanade.UPDATE_LIBRARY" />
-            </intent-filter>
-        </receiver>
-
         <meta-data
             android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
             android:value="GlideModule" />

+ 29 - 91
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt

@@ -8,8 +8,6 @@ import android.content.Intent
 import android.os.IBinder
 import android.os.PowerManager
 import android.support.v4.app.NotificationCompat
-import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus
-import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
 import eu.kanade.tachiyomi.Constants
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
@@ -17,10 +15,14 @@ import eu.kanade.tachiyomi.data.database.models.Category
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
 import eu.kanade.tachiyomi.data.source.SourceManager
 import eu.kanade.tachiyomi.data.source.online.OnlineSource
 import eu.kanade.tachiyomi.ui.main.MainActivity
-import eu.kanade.tachiyomi.util.*
+import eu.kanade.tachiyomi.util.AndroidComponentUtil
+import eu.kanade.tachiyomi.util.notification
+import eu.kanade.tachiyomi.util.notificationManager
+import eu.kanade.tachiyomi.util.syncChaptersWithSource
 import rx.Observable
 import rx.Subscription
 import rx.schedulers.Schedulers
@@ -97,7 +99,7 @@ class LibraryUpdateService : Service() {
          *
          * @param context the application context.
          * @param isManual whether the update has been manually triggered.
-         * @param category a specific category to update, or null for all in the library.
+         * @param category a specific category to update, or null for global update.
          */
         fun start(context: Context, isManual: Boolean = false, category: Category? = null) {
             if (!isRunning(context)) {
@@ -156,45 +158,7 @@ class LibraryUpdateService : Service() {
      * @return the start value of the command.
      */
     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
-
-        // Get connectivity status
-        val connection = ReactiveNetwork().getConnectivityStatus(this, true)
-
-        // Get library update restrictions
-        val restrictions = preferences.libraryUpdateRestriction()
-
-        // Check if users updates library manual
-        val isManualUpdate = intent?.getBooleanExtra(UPDATE_IS_MANUAL, false) ?: false
-
-        // Whether to cancel the update.
-        var cancelUpdate = false
-
-        // Check if device has internet connection
-        // Check if device has wifi connection if only wifi is enabled
-        if (connection == ConnectivityStatus.OFFLINE || (!isManualUpdate && "wifi" in restrictions
-                && connection != ConnectivityStatus.WIFI_CONNECTED_HAS_INTERNET)) {
-
-            if (isManualUpdate) {
-                toast(R.string.notification_no_connection_title)
-            }
-
-            // Enable library update when connection available
-            AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable::class.java, true)
-            cancelUpdate = true
-        }
-        if (!isManualUpdate && "ac" in restrictions && !DeviceUtil.isPowerConnected(this)) {
-            AndroidComponentUtil.toggleComponent(this, SyncOnPowerConnected::class.java, true)
-            cancelUpdate = true
-        }
-
-        if (cancelUpdate) {
-            stopSelf(startId)
-            return Service.START_NOT_STICKY
-        }
-
-        // Stop enabled components.
-        AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable::class.java, false)
-        AndroidComponentUtil.toggleComponent(this, SyncOnPowerConnected::class.java, false)
+        if (intent == null) return Service.START_NOT_STICKY
 
         // Unsubscribe from any previous subscription if needed.
         subscription?.unsubscribe()
@@ -202,15 +166,17 @@ class LibraryUpdateService : Service() {
         // Update favorite manga. Destroy service when completed or in case of an error.
         subscription = Observable.defer { updateMangaList(getMangaToUpdate(intent)) }
                 .subscribeOn(Schedulers.io())
-                .subscribe({},
-                        {
-                            showNotification(getString(R.string.notification_update_error), "")
-                            stopSelf(startId)
-                        }, {
+                .subscribe({
+                }, {
+                    showNotification(getString(R.string.notification_update_error), "")
+                    LibraryUpdateTrigger.setupTask(this)
+                    stopSelf(startId)
+                }, {
+                    LibraryUpdateTrigger.setupTask(this)
                     stopSelf(startId)
                 })
 
-        return Service.START_STICKY
+        return Service.START_REDELIVER_INTENT
     }
 
     /**
@@ -219,19 +185,26 @@ class LibraryUpdateService : Service() {
      * @param intent the update intent.
      * @return a list of manga to update
      */
-    fun getMangaToUpdate(intent: Intent?): List<Manga> {
-        val categoryId = intent?.getIntExtra(UPDATE_CATEGORY, -1) ?: -1
+    fun getMangaToUpdate(intent: Intent): List<Manga> {
+        val categoryId = intent.getIntExtra(UPDATE_CATEGORY, -1)
 
-        var toUpdate = if (categoryId != -1)
+        var listToUpdate = if (categoryId != -1)
             db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
-        else
-            db.getFavoriteMangas().executeAsBlocking()
+        else {
+            val categoriesToUpdate = preferences.libraryUpdateCategories().getOrDefault().map { it.toInt() }
+            if (categoriesToUpdate.isNotEmpty())
+                db.getLibraryMangas().executeAsBlocking()
+                        .filter { it.category in categoriesToUpdate }
+                        .distinctBy { it.id }
+            else
+                db.getFavoriteMangas().executeAsBlocking().distinctBy { it.id }
+        }
 
         if (preferences.updateOnlyNonCompleted()) {
-            toUpdate = toUpdate.filter { it.status != Manga.COMPLETED }
+            listToUpdate = listToUpdate.filter { it.status != Manga.COMPLETED }
         }
 
-        return toUpdate
+        return listToUpdate
     }
 
     /**
@@ -410,41 +383,6 @@ class LibraryUpdateService : Service() {
             return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
         }
 
-    /**
-     * Class that triggers the library to update when a connection is available. It receives
-     * network changes.
-     */
-    class SyncOnConnectionAvailable : BroadcastReceiver() {
-        /**
-         * Method called when a network change occurs.
-         *
-         * @param context the application context.
-         * @param intent the intent received.
-         */
-        override fun onReceive(context: Context, intent: Intent) {
-            if (DeviceUtil.isNetworkConnected(context)) {
-                AndroidComponentUtil.toggleComponent(context, this.javaClass, false)
-                start(context)
-            }
-        }
-    }
-
-    /**
-     * Class that triggers the library to update when connected to power.
-     */
-    class SyncOnPowerConnected: BroadcastReceiver() {
-        /**
-         * Method called when AC is connected.
-         *
-         * @param context the application context.
-         * @param intent the intent received.
-         */
-        override fun onReceive(context: Context, intent: Intent) {
-            AndroidComponentUtil.toggleComponent(context, this.javaClass, false)
-            start(context)
-        }
-    }
-
     /**
      * Class that stops updating the library.
      */

+ 52 - 0
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateTrigger.kt

@@ -0,0 +1,52 @@
+package eu.kanade.tachiyomi.data.library
+
+import android.content.Context
+import com.google.android.gms.gcm.*
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+
+class LibraryUpdateTrigger : GcmTaskService() {
+
+    override fun onInitializeTasks() {
+        setupTask(this)
+    }
+
+    override fun onRunTask(params: TaskParams): Int {
+        LibraryUpdateService.start(this)
+        return GcmNetworkManager.RESULT_SUCCESS
+    }
+
+    companion object {
+        fun setupTask(context: Context) {
+            val preferences = Injekt.get<PreferencesHelper>()
+            val interval = preferences.libraryUpdateInterval().getOrDefault()
+            if (interval > 0) {
+                val restrictions = preferences.libraryUpdateRestriction()
+                val acRestriction = "ac" in restrictions
+                val wifiRestriction = if ("wifi" in restrictions)
+                    Task.NETWORK_STATE_UNMETERED
+                else
+                    Task.NETWORK_STATE_ANY
+
+                val task = PeriodicTask.Builder()
+                        .setService(LibraryUpdateTrigger::class.java)
+                        .setTag("Library periodic update")
+                        .setPeriod(interval * 60 * 60L)
+                        .setFlex(5 * 60)
+                        .setRequiredNetwork(wifiRestriction)
+                        .setRequiresCharging(acRestriction)
+                        .setUpdateCurrent(true)
+                        .setPersisted(true)
+                        .build()
+
+                GcmNetworkManager.getInstance(context).schedule(task)
+            }
+        }
+
+        fun cancelTask(context: Context) {
+            GcmNetworkManager.getInstance(context).cancelAllTasks(LibraryUpdateTrigger::class.java)
+        }
+    }
+}

+ 2 - 0
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt

@@ -74,6 +74,8 @@ class PreferenceKeys(context: Context) {
 
     val libraryUpdateRestriction = context.getString(R.string.pref_library_update_restriction_key)
 
+    val libraryUpdateCategories = context.getString(R.string.pref_library_update_categories_key)
+
     val filterDownloaded = context.getString(R.string.pref_filter_downloaded_key)
 
     val filterUnread = context.getString(R.string.pref_filter_unread_key)

+ 2 - 0
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -124,6 +124,8 @@ class PreferencesHelper(context: Context) {
 
     fun libraryUpdateRestriction() = prefs.getStringSet(keys.libraryUpdateRestriction, emptySet())
 
+    fun libraryUpdateCategories() = rxPrefs.getStringSet(keys.libraryUpdateCategories, emptySet())
+
     fun libraryAsList() = rxPrefs.getBoolean(keys.libraryAsList, false)
 
     fun filterDownloaded() = rxPrefs.getBoolean(keys.filterDownloaded, false)

+ 30 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt

@@ -6,7 +6,8 @@ import android.support.v7.preference.PreferenceFragmentCompat
 import android.support.v7.preference.XpPreferenceFragment
 import android.view.View
 import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.library.LibraryUpdateAlarm
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+import eu.kanade.tachiyomi.data.library.LibraryUpdateTrigger
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.util.plusAssign
 import eu.kanade.tachiyomi.widget.preference.IntListPreference
@@ -14,6 +15,7 @@ import eu.kanade.tachiyomi.widget.preference.LibraryColumnsDialog
 import eu.kanade.tachiyomi.widget.preference.SimpleDialogPreference
 import net.xpece.android.support.preference.MultiSelectListPreference
 import rx.Observable
+import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
 
 class SettingsGeneralFragment : SettingsFragment(),
@@ -30,6 +32,8 @@ class SettingsGeneralFragment : SettingsFragment(),
 
     private val preferences: PreferencesHelper by injectLazy()
 
+    private val db: DatabaseHelper by injectLazy()
+
 
     val columnsPreference by lazy {
         findPreference(getString(R.string.pref_library_columns_dialog_key)) as SimpleDialogPreference
@@ -47,6 +51,10 @@ class SettingsGeneralFragment : SettingsFragment(),
         findPreference(getString(R.string.pref_theme_key)) as IntListPreference
     }
 
+    val categoryUpdate by lazy {
+        findPreference(getString(R.string.pref_library_update_categories_key)) as MultiSelectListPreference
+    }
+
     override fun onViewCreated(view: View, savedState: Bundle?) {
         super.onViewCreated(view, savedState)
 
@@ -60,10 +68,30 @@ class SettingsGeneralFragment : SettingsFragment(),
                 .subscribe { updateColumnsSummary(it.first, it.second) }
 
         updateInterval.setOnPreferenceChangeListener { preference, newValue ->
-            LibraryUpdateAlarm.startAlarm(activity, (newValue as String).toInt())
+            val enabled = (newValue as String).toInt() > 0
+            if (enabled)
+                LibraryUpdateTrigger.setupTask(context)
+            else
+                LibraryUpdateTrigger.cancelTask(context)
+
             true
         }
 
+        val dbCategories = db.getCategories().executeAsBlocking()
+        categoryUpdate.apply {
+            entries = dbCategories.map { it.name }.toTypedArray()
+            entryValues = dbCategories.map { it.id.toString() }.toTypedArray()
+        }
+
+        subscriptions += preferences.libraryUpdateCategories().asObservable()
+                .subscribe {
+                    Timber.e(it.joinToString())
+                    categoryUpdate.summary = it
+                            .mapNotNull { id -> dbCategories.find { it.id == id.toInt() } }
+                            .sortedBy { it.order }
+                            .joinToString { it.name }
+                }
+
         themePreference.setOnPreferenceChangeListener { preference, newValue ->
             (activity as SettingsActivity).parentFlags = SettingsActivity.FLAG_THEME_CHANGED
             activity.recreate()

+ 1 - 0
app/src/main/res/values/keys.xml

@@ -14,6 +14,7 @@
     <string name="pref_library_columns_portrait_key">pref_library_columns_portrait_key</string>
     <string name="pref_library_columns_landscape_key">pref_library_columns_landscape_key</string>
     <string name="pref_library_update_interval_key">pref_library_update_interval_key</string>
+    <string name="pref_library_update_categories_key">library_update_categories</string>
     <string name="pref_update_only_non_completed_key">pref_update_only_non_completed_key</string>
     <string name="pref_auto_update_manga_sync_key">pref_auto_update_manga_sync_key</string>
     <string name="pref_ask_update_manga_sync_key">pref_ask_update_manga_sync_key</string>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -85,6 +85,7 @@
     <string name="update_12hour">Every 12 hours</string>
     <string name="update_24hour">Daily</string>
     <string name="update_48hour">Every 2 days</string>
+    <string name="pref_library_update_categories">Categories to include in global update</string>
     <string name="pref_library_update_restriction">Library update restrictions</string>
     <string name="pref_library_update_restriction_summary">Update only when the conditions are met</string>
     <string name="wifi">Wi-Fi</string>

+ 4 - 0
app/src/main/res/xml/pref_general.xml

@@ -36,6 +36,10 @@
             android:summary="%s"
             android:title="@string/pref_library_update_interval"/>
 
+        <MultiSelectListPreference
+            android:key="@string/pref_library_update_categories_key"
+            android:title="@string/pref_library_update_categories"/>
+
         <MultiSelectListPreference
             android:entries="@array/library_update_restrictions"
             android:entryValues="@array/library_update_restrictions_values"

+ 0 - 124
app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateAlarmTest.kt

@@ -1,124 +0,0 @@
-package eu.kanade.tachiyomi.data.library
-
-import android.app.AlarmManager
-import android.content.Context
-import android.content.Intent
-import android.os.Build
-import android.os.SystemClock
-import eu.kanade.tachiyomi.BuildConfig
-import eu.kanade.tachiyomi.CustomRobolectricGradleTestRunner
-import eu.kanade.tachiyomi.data.preference.PreferencesHelper
-import org.assertj.core.api.Assertions.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.spy
-import org.robolectric.Shadows.shadowOf
-import org.robolectric.annotation.Config
-import org.robolectric.shadows.ShadowAlarmManager
-import org.robolectric.shadows.ShadowApplication
-
-@Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP))
-@RunWith(CustomRobolectricGradleTestRunner::class)
-class LibraryUpdateAlarmTest {
-
-    lateinit var app: ShadowApplication
-    lateinit var context: Context
-    lateinit var alarmManager: ShadowAlarmManager
-
-    @Before
-    fun setup() {
-        app = ShadowApplication.getInstance()
-        context = spy(app.applicationContext)
-
-        alarmManager = shadowOf(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager)
-    }
-
-    @Test
-    fun testLibraryIntentHandling() {
-        val intent = Intent(LibraryUpdateAlarm.LIBRARY_UPDATE_ACTION)
-        assertThat(app.hasReceiverForIntent(intent)).isTrue()
-    }
-
-    @Test
-    fun testAlarmIsNotStarted() {
-        assertThat(alarmManager.nextScheduledAlarm).isNull()
-    }
-
-    @Test
-    fun testAlarmIsNotStartedWhenBootReceivedAndSettingZero() {
-        val alarm = LibraryUpdateAlarm()
-        alarm.onReceive(context, Intent(Intent.ACTION_BOOT_COMPLETED))
-
-        assertThat(alarmManager.nextScheduledAlarm).isNull()
-    }
-
-    @Test
-    fun testAlarmIsStartedWhenBootReceivedAndSettingNotZero() {
-        val prefs = PreferencesHelper(context)
-        prefs.libraryUpdateInterval().set(1)
-
-        val alarm = LibraryUpdateAlarm()
-        alarm.onReceive(context, Intent(Intent.ACTION_BOOT_COMPLETED))
-
-        assertThat(alarmManager.nextScheduledAlarm).isNotNull()
-    }
-
-    @Test
-    fun testOnlyOneAlarmExists() {
-        val prefs = PreferencesHelper(context)
-        prefs.libraryUpdateInterval().set(1)
-
-        LibraryUpdateAlarm.startAlarm(context)
-        LibraryUpdateAlarm.startAlarm(context)
-        LibraryUpdateAlarm.startAlarm(context)
-
-        assertThat(alarmManager.scheduledAlarms).hasSize(1)
-    }
-
-    @Test
-    fun testLibraryWillBeUpdatedWhenAlarmFired() {
-        val prefs = PreferencesHelper(context)
-        prefs.libraryUpdateInterval().set(1)
-
-        val expectedIntent = Intent(context, LibraryUpdateAlarm::class.java)
-        expectedIntent.action = LibraryUpdateAlarm.LIBRARY_UPDATE_ACTION
-
-        LibraryUpdateAlarm.startAlarm(context)
-
-        val scheduledAlarm = alarmManager.nextScheduledAlarm
-        val pendingIntent = shadowOf(scheduledAlarm.operation)
-        assertThat(pendingIntent.isBroadcastIntent).isTrue()
-        assertThat(pendingIntent.savedIntents).hasSize(1)
-        assertThat(expectedIntent.component).isEqualTo(pendingIntent.savedIntents[0].component)
-        assertThat(expectedIntent.action).isEqualTo(pendingIntent.savedIntents[0].action)
-    }
-
-    @Test
-    fun testReceiverDoesntReactToNullActions() {
-        val prefs = PreferencesHelper(context)
-        prefs.libraryUpdateInterval().set(1)
-
-        val intent = Intent(context, LibraryUpdateService::class.java)
-
-        val alarm = LibraryUpdateAlarm()
-        alarm.onReceive(context, Intent())
-
-        assertThat(app.nextStartedService).isNotEqualTo(intent)
-        assertThat(alarmManager.scheduledAlarms).hasSize(0)
-    }
-
-    @Test
-    fun testAlarmFiresCloseToDesiredTime() {
-        val hours = 2
-        LibraryUpdateAlarm.startAlarm(context, hours)
-
-        val shouldRunAt = SystemClock.elapsedRealtime() + hours * 60 * 60 * 1000
-
-        // Margin error of 3 seconds
-        assertThat(alarmManager.nextScheduledAlarm.triggerAtTime)
-                .isGreaterThan(shouldRunAt - 3000)
-                .isLessThan(shouldRunAt + 3000)
-    }
-
-}

+ 3 - 1
app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt

@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.library
 
 import android.app.Application
 import android.content.Context
+import android.content.Intent
 import android.os.Build
 import eu.kanade.tachiyomi.BuildConfig
 import eu.kanade.tachiyomi.CustomRobolectricGradleTestRunner
@@ -93,7 +94,8 @@ class LibraryUpdateServiceTest {
         `when`(source.fetchChapterList(favManga[1])).thenReturn(Observable.error<List<Chapter>>(Exception()))
         `when`(source.fetchChapterList(favManga[2])).thenReturn(Observable.just(chapters3))
 
-        service.updateMangaList(service.getMangaToUpdate(null)).subscribe()
+        val intent = Intent()
+        service.updateMangaList(service.getMangaToUpdate(intent)).subscribe()
 
         // There are 3 network attempts and 2 insertions (1 request failed)
         assertThat(service.db.getChapters(favManga[0]).executeAsBlocking()).hasSize(2)