diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index d22c131cb026..44f4b2d57429 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -298,6 +298,7 @@ dependencies { implementation 'com.drakeet.drawer:drawer:1.0.3' implementation 'uk.co.samuelwall:material-tap-target-prompt:3.3.2' implementation 'com.github.mrudultora:Colorpicker:1.2.0' + implementation "androidx.work:work-runtime-ktx:2.7.1" // Cannot use debugImplementation since classes need to be imported in AnkiDroidApp // and there's no no-op version for release build. Usage has been disabled for release @@ -328,11 +329,5 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test:rules:1.4.0' - - // GSON - implementation 'com.google.code.gson:gson:2.9.0' - - // Work Manager - implementation "androidx.work:work-runtime-ktx:2.7.1" } apply from: "./kotlinMigration.gradle" diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java index a36719d2caeb..ae05edd4be76 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java @@ -426,9 +426,9 @@ public static String getWebViewErrorMessage() { * This function setups the work manager which run periodically. **/ private void setupDeckMetaDataWorker() { - Timber.tag("META").e("Setting up deck meta data worker..."); + Timber.d("Setting up deck meta data worker..."); - PeriodicWorkRequest deckMetaDataWorker = new PeriodicWorkRequest.Builder(DeckMetaDataWorker.class, 2, TimeUnit.MINUTES) + PeriodicWorkRequest deckMetaDataWorker = new PeriodicWorkRequest.Builder(DeckMetaDataWorker.class, 30, TimeUnit.MINUTES) .addTag(DeckMetaDataWorker.DECK_META_WORKER) .build(); @@ -438,6 +438,10 @@ private void setupDeckMetaDataWorker() { ExistingPeriodicWorkPolicy.REPLACE, deckMetaDataWorker ); + + // Does the required work in setting up new Worker. + String time = CollectionHelper.getInstance().getTimeSafe(this).getCurrentDate().toString(); + DeckMetaDataWorker.Companion.setupNewWorker(this, time); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckMetaDataPreference.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckMetaDataPreference.kt index b10cea04c7b5..7cdbaa2e5633 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckMetaDataPreference.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckMetaDataPreference.kt @@ -18,33 +18,93 @@ package com.ichi2.anki import android.content.Context import android.content.SharedPreferences -import com.google.gson.Gson +import androidx.core.content.edit import com.ichi2.anki.worker.DeckMetaDataWorker +import com.ichi2.utils.JSONException +import com.ichi2.utils.JSONObject import timber.log.Timber +import java.lang.NumberFormatException class DeckMetaDataPreference(context: Context) { private val sharedPreferences: SharedPreferences = context.getSharedPreferences("DeckMetaData", Context.MODE_PRIVATE) + /** + * Use this to store data in shared preference. + * @param key The Key of value. Used in fetching the data. + * @param value Value that needs to be stored (VALUE MUST BE STRING). + * */ fun putString(key: String, value: String) { - sharedPreferences.edit() - .putString(key, value) - .apply() + sharedPreferences.edit { + putString(key, value) + apply() + } } + /** + * Use this to fetch string from shared preference. + * @prams The Key of deck whose data you want to fetch. + * @return Value that needs to be fetched (VALUE WILL BE STRING). + * */ fun getString(key: String, default: String): String { return sharedPreferences.getString(key, default)!! } + /** + * Use this to store int in shared preference. + * @param key The Key of value. Created while storing the data. + * @param value Value that needs to be stored (VALUE MUST BE INTEGER). + * */ + fun putInt(key: String, value: Int) { + sharedPreferences.edit { + putInt(key, value) + apply() + } + } + + /** + * Use this to fetch integer from shared preference. + * @prams The Key of deck whose data you want to fetch. + * @return Value that needs to be fetched (VALUE WILL BE INTEGER). + * */ + fun getInt(key: String, default: Int): Int { + return sharedPreferences.getInt(key, default) + } + + /** + * Use this to store meta data in shared preference. + * @param key The Key of deck whose data you want to fetch. (deck id is the for deck) + * @param value Object of Metadata Model + * */ fun setMetaData(key: String, value: DeckMetaDataWorker.Meta) { - val json: String = Gson().toJson(value) - sharedPreferences.edit() - .putString(key, json) - .apply() - Timber.tag("META").e(json) + val jsonObject = JSONObject() + + try { + jsonObject.put("did", value.did) + jsonObject.put("deckName", value.deckName) + jsonObject.put("new", value.new) + jsonObject.put("lrn", value.lrn) + jsonObject.put("rev", value.rev) + jsonObject.put("eta", value.eta) + } catch (e: JSONException) { + Timber.d(e) + return + } + + val json = jsonObject.toString() + + sharedPreferences.edit { + putString(key, json) + apply() + } } + /** + * Use this to fetch meta data from shared preference if Deck ID (did) is known. + * @prams The Key of deck whose data you want to fetch. (deck id is the for deck) + * @return Object of Metadata Model. + * */ fun getMetaData(key: String): DeckMetaDataWorker.Meta? { val jsonData = sharedPreferences .getString(key, null) @@ -52,7 +112,35 @@ class DeckMetaDataPreference(context: Context) { return if (jsonData == null) { null } else { - Gson().fromJson(jsonData, DeckMetaDataWorker.Meta::class.java) + val jsonObject = JSONObject(jsonData) + return DeckMetaDataWorker.Meta( + jsonObject.getLong("did"), + jsonObject.getString("deckName"), + jsonObject.getInt("new"), + jsonObject.getInt("lrn"), + jsonObject.getInt("rev"), + jsonObject.getInt("eta"), + ) + } + } + + /** + * Use this to fetch all meta data from shared preference. + * @return List of all Meta Data Object. + * */ + fun getAllMetaData(): List { + val listMeta = mutableListOf() + val dids = sharedPreferences.all.keys.filter { data -> data.isLong() } + dids.forEach { + listMeta.add(getMetaData(it)!!) } + return listMeta + } + + private fun String.isLong(): Boolean = try { + this.toLong() + true + } catch (e: NumberFormatException) { + false } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/worker/DeckMetaDataWorker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/worker/DeckMetaDataWorker.kt index fb4a53bca815..a7d593c1ace1 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/worker/DeckMetaDataWorker.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/worker/DeckMetaDataWorker.kt @@ -21,7 +21,9 @@ import androidx.work.Worker import androidx.work.WorkerParameters import com.ichi2.anki.CollectionHelper import com.ichi2.anki.DeckMetaDataPreference +import com.ichi2.libanki.Collection import com.ichi2.libanki.sched.Counts +import com.ichi2.libanki.sched.DeckDueTreeNode import timber.log.Timber /** @@ -31,34 +33,42 @@ import timber.log.Timber * */ class DeckMetaDataWorker(val context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) { - private lateinit var colHelper: CollectionHelper + // Lambda to check the collection is not null. + private val mCollection: (context: Context) -> Collection? = { + val collection = CollectionHelper.getInstance() + if (collection.getColSafe(it) != null) { + collection.getCol(it) + } else { + null + } + } override fun doWork(): Result { - colHelper = CollectionHelper.getInstance() + val date = CollectionHelper.getInstance().getTimeSafe(context).currentDate - Timber.tag("META").e("Deck Meta Data Worker started at: ${colHelper.getTimeSafe(context).currentDate}") + Timber.d("Deck Meta Data Worker started at: $date") // Update the data of Deck Metadata from HERE - val col = colHelper.getCol(context) - val deckList = col.sched.deckDueList() + val sched = mCollection(context)?.sched ?: return Result.failure() + val deckList: List = sched.deckDueList() deckList.forEach { - val new = it?.newCount ?: -1 - val lrn = it?.lrnCount ?: -1 - val rev = it?.revCount ?: -1 + val new = it.newCount + val lrn = it.lrnCount + val rev = it.revCount val data = Meta( - it?.did ?: -1, - it?.fullDeckName ?: "Deck Name", + it.did, + it.fullDeckName, new, lrn, rev, - col.sched.eta(Counts(new, lrn, rev), false) + sched.eta(Counts(new, lrn, rev), false) ) storeInPreference(data) } - updateLastFetch(colHelper.getTimeSafe(context).toString()) + updateLastFetch(date.toString()) return Result.success() // Done work successfully... } @@ -66,6 +76,8 @@ class DeckMetaDataWorker(val context: Context, workerParameters: WorkerParameter private fun updateLastFetch(time: String) { val metaPreference = DeckMetaDataPreference(context) metaPreference.putString(LAST_FETCH_TIME, time) + val prevData = metaPreference.getInt(TIMES_FETCHED, 0) + metaPreference.putInt(TIMES_FETCHED, prevData + 1) } /** @@ -88,5 +100,14 @@ class DeckMetaDataWorker(val context: Context, workerParameters: WorkerParameter companion object { const val DECK_META_WORKER = "DeckMetaData" const val LAST_FETCH_TIME = "LAST_FETCH" + const val TIMES_FETCHED = "TIMES_FETCHED" + const val WORKER_CREATED = "WORKER_CREATED" + + fun setupNewWorker(context: Context, time: String) { + Timber.d("Setting up Preference for new Worker.") + val metaDataPreference = DeckMetaDataPreference(context) + metaDataPreference.putString(WORKER_CREATED, time) + metaDataPreference.putInt(TIMES_FETCHED, 0) + } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.kt index ca6e2cc19614..da25a91dd3ee 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.kt @@ -181,7 +181,7 @@ abstract class AbstractSched { /** * @return [deckname, did, rev, lrn, new] */ - abstract fun deckDueList(): List + abstract fun deckDueList(): List /** * @param cancelListener A task that is potentially cancelled