From f7e11c744578635058aca9345ed6c05a93161656 Mon Sep 17 00:00:00 2001 From: sspanak Date: Mon, 6 Jan 2025 17:32:19 +0200 Subject: [PATCH] fixed the delete dictionary buttons not staying locked when navigating from and to the Languages screen, while deletion is in progress --- .../io/github/sspanak/tt9/db/DataStore.java | 23 ---- .../tt9/db/wordPairs/WordPairStore.java | 11 -- .../tt9/db/words/DictionaryDeleter.java | 106 ++++++++++++++++++ .../sspanak/tt9/db/words/WordStore.java | 7 -- .../screens/languages/ItemTruncateAll.java | 33 ++++-- .../languages/ItemTruncateUnselected.java | 6 +- .../screens/languages/LanguagesScreen.java | 25 +++-- 7 files changed, 152 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/io/github/sspanak/tt9/db/words/DictionaryDeleter.java diff --git a/app/src/main/java/io/github/sspanak/tt9/db/DataStore.java b/app/src/main/java/io/github/sspanak/tt9/db/DataStore.java index 899aba30..1251273d 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/DataStore.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/DataStore.java @@ -11,7 +11,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import io.github.sspanak.tt9.db.entities.AddWordResult; import io.github.sspanak.tt9.db.wordPairs.WordPairStore; @@ -21,7 +20,6 @@ import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.ConsumerCompat; import io.github.sspanak.tt9.util.Logger; -import io.github.sspanak.tt9.util.Timer; public class DataStore { private final static String LOG_TAG = DataStore.class.getSimpleName(); @@ -80,27 +78,6 @@ public class DataStore { } - public static void deleteLanguages(Runnable notification, @NonNull ArrayList languages) { - Timer.start(LOG_TAG); - - AtomicInteger progress = new AtomicInteger(languages.size()); - Runnable onDeleted = () -> { - if (progress.decrementAndGet() == 0) { - Logger.d(LOG_TAG, "Deleted " + languages.size() + " languages. Time: " + Timer.stop(LOG_TAG) + " ms"); - notification.run(); - } - }; - - for (Language language : languages) { - runInTransaction( - () -> { words.remove(language); pairs.remove(language); }, - onDeleted, - "Failed deleting languages." - ); - } - } - - public static void put(ConsumerCompat statusHandler, Language language, String word) { runInThread(() -> statusHandler.accept(words.put(language, word))); } diff --git a/app/src/main/java/io/github/sspanak/tt9/db/wordPairs/WordPairStore.java b/app/src/main/java/io/github/sspanak/tt9/db/wordPairs/WordPairStore.java index 15ed4302..5b383e87 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/wordPairs/WordPairStore.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/wordPairs/WordPairStore.java @@ -165,17 +165,6 @@ public class WordPairStore extends BaseSyncStore { } - public void remove(@NonNull Language language) { - if (!checkOrNotify()) { - return; - } - - DeleteOps.deleteWordPairs(sqlite.getDb(), language.getId()); - slowestLoadTime = 0; - slowestSaveTime = 0; - } - - public void remove(@NonNull ArrayList languages) { if (!checkOrNotify()) { return; diff --git a/app/src/main/java/io/github/sspanak/tt9/db/words/DictionaryDeleter.java b/app/src/main/java/io/github/sspanak/tt9/db/words/DictionaryDeleter.java new file mode 100644 index 00000000..02609658 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/db/words/DictionaryDeleter.java @@ -0,0 +1,106 @@ +package io.github.sspanak.tt9.db.words; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import io.github.sspanak.tt9.db.BaseSyncStore; +import io.github.sspanak.tt9.db.sqlite.DeleteOps; +import io.github.sspanak.tt9.languages.Language; +import io.github.sspanak.tt9.languages.NaturalLanguage; +import io.github.sspanak.tt9.util.Logger; +import io.github.sspanak.tt9.util.Timer; + +public class DictionaryDeleter extends BaseSyncStore { + private static final String LOG_TAG = DictionaryDeleter.class.getSimpleName(); + private static DictionaryDeleter self; + + @NonNull private final ExecutorService executor = Executors.newSingleThreadExecutor(); + @Nullable Future deleteTask; + @Nullable private Runnable notification; + + + protected DictionaryDeleter(Context context) { + super(context); + } + + + public static DictionaryDeleter getInstance(Context context) { + if (self == null) { + self = new DictionaryDeleter(context); + } + return self; + } + + + public void deleteLanguages(@NonNull ArrayList languages) { + if (!checkOrNotify()) { + onFinish(); + return; + } + + Timer.start(LOG_TAG); + deleteTask = executor.submit(() -> deleteLanguagesSync(languages)); + } + + + private void deleteLanguagesSync(@NonNull ArrayList languages) { + for (Language language : languages) { + if (!deleteLanguage(language)) { + break; + } + } + + deleteTask = null; + + if (notification != null) { + notification.run(); + } + + Logger.d(LOG_TAG, "Deleted " + languages.size() + " languages. Time: " + Timer.stop(LOG_TAG) + " ms"); + } + + + private boolean deleteLanguage(Language language) { + if (!(language instanceof NaturalLanguage)) { + Logger.w(LOG_TAG, "Invalid language type to delete: " + language.getClass().getSimpleName() + ". Skipping."); + return true; + } + + try { + sqlite.beginTransaction(); + DeleteOps.delete(sqlite.getDb(), language.getId()); + DeleteOps.deleteWordPairs(sqlite.getDb(), language.getId()); + sqlite.finishTransaction(); + } catch (Exception e) { + sqlite.failTransaction(); + Logger.e(LOG_TAG, "Failed deleting language: " + language.getId() + ". " + e.getMessage()); + return false; + } + + return true; + } + + + private void onFinish() { + if (notification != null) { + notification.run(); + } + } + + + public boolean isRunning() { + return deleteTask != null && !deleteTask.isDone(); + } + + + public void setOnFinish(Runnable notification) { + this.notification = notification; + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java b/app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java index 504691ca..4bcfffc2 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java @@ -118,13 +118,6 @@ public class WordStore extends BaseSyncStore { } - public void remove(Language language) { - if (checkOrNotify() && readOps.exists(sqlite.getDb(), language.getId())) { - DeleteOps.delete(sqlite.getDb(), language.getId()); - } - } - - public void removeCustomWord(Language language, String word) { if (language == null || language instanceof NullLanguage || !checkOrNotify()) { return; diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateAll.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateAll.java index 656302e3..8fdcfad4 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateAll.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateAll.java @@ -3,7 +3,7 @@ package io.github.sspanak.tt9.preferences.screens.languages; import androidx.preference.Preference; import io.github.sspanak.tt9.R; -import io.github.sspanak.tt9.db.DataStore; +import io.github.sspanak.tt9.db.words.DictionaryDeleter; import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.preferences.PreferencesActivity; import io.github.sspanak.tt9.preferences.items.ItemClickable; @@ -14,6 +14,7 @@ class ItemTruncateAll extends ItemClickable { public static final String NAME = "dictionary_truncate"; protected final PreferencesActivity activity; + protected final DictionaryDeleter deleter; private final Runnable onStart; private final Runnable onFinish; @@ -21,31 +22,49 @@ class ItemTruncateAll extends ItemClickable { ItemTruncateAll(Preference item, PreferencesActivity activity, Runnable onStart, Runnable onFinish) { super(item); this.activity = activity; + this.deleter = DictionaryDeleter.getInstance(activity); this.onStart = onStart; this.onFinish = onFinish; + + refreshStatus(); } @Override protected boolean onClick(Preference p) { - onStartDeleting(); - DataStore.deleteLanguages(this::onFinishDeleting, LanguageCollection.getAll(false)); + onStart.run(); + setBusy(); + deleter.deleteLanguages(LanguageCollection.getAll(false)); return true; } - protected void onStartDeleting() { - onStart.run(); - disable(); + void refreshStatus() { + if (deleter.isRunning()) { + setBusy(); + } else { + enable(); + } + } + + + protected void setBusy() { + deleter.setOnFinish(this::onFinishDeleting); item.setSummary(R.string.dictionary_truncating); + disable(); + } + + + public void enable() { + super.enable(); + item.setSummary(""); } protected void onFinishDeleting() { activity.runOnUiThread(() -> { onFinish.run(); - item.setSummary(""); enable(); UI.toastFromAsync(activity, R.string.dictionary_truncated); }); diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateUnselected.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateUnselected.java index f1639bdb..b8f7ced5 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateUnselected.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateUnselected.java @@ -6,7 +6,6 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.Set; -import io.github.sspanak.tt9.db.DataStore; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.preferences.PreferencesActivity; @@ -31,8 +30,9 @@ class ItemTruncateUnselected extends ItemTruncateAll { } } - onStartDeleting(); - DataStore.deleteLanguages(this::onFinishDeleting, unselectedLanguages); + setBusy(); + deleter.setOnFinish(this::onFinishDeleting); + deleter.deleteLanguages(unselectedLanguages); return true; } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/LanguagesScreen.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/LanguagesScreen.java index 3073b925..6b1e2af7 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/LanguagesScreen.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/LanguagesScreen.java @@ -11,6 +11,7 @@ import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.db.customWords.CustomWordsExporter; import io.github.sspanak.tt9.db.customWords.CustomWordsImporter; import io.github.sspanak.tt9.db.customWords.DictionaryExporter; +import io.github.sspanak.tt9.db.words.DictionaryDeleter; import io.github.sspanak.tt9.db.words.DictionaryLoader; import io.github.sspanak.tt9.preferences.PreferencesActivity; import io.github.sspanak.tt9.preferences.items.ItemClickable; @@ -19,12 +20,14 @@ import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment; public class LanguagesScreen extends BaseScreenFragment { public static final String NAME = "Languages"; - private final ArrayList clickables = new ArrayList<>(); + private static final ArrayList clickables = new ArrayList<>(); private ItemLoadDictionary loadItem; private ItemImportCustomWords importCustomWordsItem; private ItemExportDictionary exportDictionaryItem; private ItemExportCustomWords exportCustomWordsItem; + private ItemTruncateAll truncateAllItem; + private ItemTruncateUnselected truncateUnselectedItem; public LanguagesScreen() { init(); } public LanguagesScreen(PreferencesActivity activity) { init(activity); } @@ -56,22 +59,25 @@ public class LanguagesScreen extends BaseScreenFragment { this::onActionFinish ); - clickables.add(loadItem); - clickables.add(exportDictionaryItem); - - clickables.add(new ItemTruncateUnselected( + truncateUnselectedItem = new ItemTruncateUnselected( findPreference(ItemTruncateUnselected.NAME), activity, this::onActionStart, this::onActionFinish - )); + ); - clickables.add(new ItemTruncateAll( + truncateAllItem = new ItemTruncateAll( findPreference(ItemTruncateAll.NAME), activity, this::onActionStart, this::onActionFinish - )); + ); + + clickables.clear(); + clickables.add(loadItem); + clickables.add(exportDictionaryItem); + clickables.add(truncateUnselectedItem); + clickables.add(truncateAllItem); clickables.add(new ItemDeleteCustomWords(findPreference(ItemDeleteCustomWords.NAME))); @@ -112,6 +118,8 @@ public class LanguagesScreen extends BaseScreenFragment { exportDictionaryItem.refreshStatus(); exportCustomWordsItem.refreshStatus(); importCustomWordsItem.refreshStatus(); + truncateUnselectedItem.refreshStatus(); + truncateAllItem.refreshStatus(); if (DictionaryLoader.getInstance(activity).isRunning()) { loadItem.refreshStatus(); @@ -120,6 +128,7 @@ public class LanguagesScreen extends BaseScreenFragment { CustomWordsExporter.getInstance().isRunning() || DictionaryExporter.getInstance().isRunning() || CustomWordsImporter.getInstance(activity).isRunning() + || DictionaryDeleter.getInstance(activity).isRunning() ) { onActionStart(); } else {