aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/org/kelar/inputmethod/dictionarypack/ActionBatch.java
diff options
context:
space:
mode:
authorAmin Bandali <bandali@kelar.org>2024-12-16 21:45:41 -0500
committerAmin Bandali <bandali@kelar.org>2025-01-11 14:17:35 -0500
commite9a0e66716dab4dd3184d009d8920de1961efdfa (patch)
tree02dcc096643d74645bf28459c2834c3d4a2ad7f2 /java/src/org/kelar/inputmethod/dictionarypack/ActionBatch.java
parentfb3b9360d70596d7e921de8bf7d3ca99564a077e (diff)
downloadlatinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.tar.gz
latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.tar.xz
latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.zip
Rename to Kelar Keyboard (org.kelar.inputmethod.latin)
Diffstat (limited to 'java/src/org/kelar/inputmethod/dictionarypack/ActionBatch.java')
-rw-r--r--java/src/org/kelar/inputmethod/dictionarypack/ActionBatch.java625
1 files changed, 625 insertions, 0 deletions
diff --git a/java/src/org/kelar/inputmethod/dictionarypack/ActionBatch.java b/java/src/org/kelar/inputmethod/dictionarypack/ActionBatch.java
new file mode 100644
index 000000000..06bebc8da
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/dictionarypack/ActionBatch.java
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.kelar.inputmethod.dictionarypack;
+
+import android.app.DownloadManager.Request;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.kelar.inputmethod.latin.BinaryDictionaryFileDumper;
+import org.kelar.inputmethod.latin.R;
+import org.kelar.inputmethod.latin.common.LocaleUtils;
+import org.kelar.inputmethod.latin.utils.ApplicationUtils;
+import org.kelar.inputmethod.latin.utils.DebugLogUtils;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Object representing an upgrade from one state to another.
+ *
+ * This implementation basically encapsulates a list of Runnable objects. In the future
+ * it may manage dependencies between them. Concretely, it does not use Runnable because the
+ * actions need an argument.
+ */
+/*
+
+The state of a word list follows the following scheme.
+
+ | ^
+ MakeAvailable |
+ | .------------Forget--------'
+ V |
+ STATUS_AVAILABLE <-------------------------.
+ | |
+StartDownloadAction FinishDeleteAction
+ | |
+ V |
+STATUS_DOWNLOADING EnableAction-- STATUS_DELETING
+ | | ^
+InstallAfterDownloadAction | |
+ | .---------------' StartDeleteAction
+ | | |
+ V V |
+ STATUS_INSTALLED <--EnableAction-- STATUS_DISABLED
+ --DisableAction-->
+
+ It may also be possible that DisableAction or StartDeleteAction or
+ DownloadAction run when the file is still downloading. This cancels
+ the download and returns to STATUS_AVAILABLE.
+ Also, an UpdateDataAction may apply in any state. It does not affect
+ the state in any way (nor type, local filename, id or version) but
+ may update other attributes like description or remote filename.
+
+ Forget is an DB maintenance action that removes the entry if it is not installed or disabled.
+ This happens when the word list information disappeared from the server, or when a new version
+ is available and we should forget about the old one.
+*/
+public final class ActionBatch {
+ /**
+ * A piece of update.
+ *
+ * Action is basically like a Runnable that takes an argument.
+ */
+ public interface Action {
+ /**
+ * Execute this action NOW.
+ * @param context the context to get system services, resources, databases
+ */
+ void execute(final Context context);
+ }
+
+ /**
+ * An action that starts downloading an available word list.
+ */
+ public static final class StartDownloadAction implements Action {
+ static final String TAG = "DictionaryProvider:" + StartDownloadAction.class.getSimpleName();
+
+ private final String mClientId;
+ // The data to download. May not be null.
+ final WordListMetadata mWordList;
+ public StartDownloadAction(final String clientId, final WordListMetadata wordList) {
+ DebugLogUtils.l("New download action for client ", clientId, " : ", wordList);
+ mClientId = clientId;
+ mWordList = wordList;
+ }
+
+ @Override
+ public void execute(final Context context) {
+ if (null == mWordList) { // This should never happen
+ Log.e(TAG, "UpdateAction with a null parameter!");
+ return;
+ }
+ DebugLogUtils.l("Downloading word list");
+ final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
+ final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
+ mWordList.mId, mWordList.mVersion);
+ final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
+ final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
+ if (MetadataDbHelper.STATUS_DOWNLOADING == status) {
+ // The word list is still downloading. Cancel the download and revert the
+ // word list status to "available".
+ manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
+ MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion);
+ } else if (MetadataDbHelper.STATUS_AVAILABLE != status
+ && MetadataDbHelper.STATUS_RETRYING != status) {
+ // Should never happen
+ Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' : " + status
+ + " for an upgrade action. Fall back to download.");
+ }
+ // Download it.
+ DebugLogUtils.l("Upgrade word list, downloading", mWordList.mRemoteFilename);
+
+ // This is an upgraded word list: we should download it.
+ // Adding a disambiguator to circumvent a bug in older versions of DownloadManager.
+ // DownloadManager also stupidly cuts the extension to replace with its own that it
+ // gets from the content-type. We need to circumvent this.
+ final String disambiguator = "#" + System.currentTimeMillis()
+ + ApplicationUtils.getVersionName(context) + ".dict";
+ final Uri uri = Uri.parse(mWordList.mRemoteFilename + disambiguator);
+ final Request request = new Request(uri);
+
+ final Resources res = context.getResources();
+ request.setAllowedNetworkTypes(Request.NETWORK_WIFI | Request.NETWORK_MOBILE);
+ request.setTitle(mWordList.mDescription);
+ request.setNotificationVisibility(Request.VISIBILITY_HIDDEN);
+ request.setVisibleInDownloadsUi(
+ res.getBoolean(R.bool.dict_downloads_visible_in_download_UI));
+
+ final long downloadId = UpdateHandler.registerDownloadRequest(manager, request, db,
+ mWordList.mId, mWordList.mVersion);
+ Log.i(TAG, String.format("Starting the dictionary download with version:"
+ + " %d and Url: %s", mWordList.mVersion, uri));
+ DebugLogUtils.l("Starting download of", uri, "with id", downloadId);
+ PrivateLog.log("Starting download of " + uri + ", id : " + downloadId);
+ }
+ }
+
+ /**
+ * An action that updates the database to reflect the status of a newly installed word list.
+ */
+ public static final class InstallAfterDownloadAction implements Action {
+ static final String TAG = "DictionaryProvider:"
+ + InstallAfterDownloadAction.class.getSimpleName();
+ private final String mClientId;
+ // The state to upgrade from. May not be null.
+ final ContentValues mWordListValues;
+
+ public InstallAfterDownloadAction(final String clientId,
+ final ContentValues wordListValues) {
+ DebugLogUtils.l("New InstallAfterDownloadAction for client ", clientId, " : ",
+ wordListValues);
+ mClientId = clientId;
+ mWordListValues = wordListValues;
+ }
+
+ @Override
+ public void execute(final Context context) {
+ if (null == mWordListValues) {
+ Log.e(TAG, "InstallAfterDownloadAction with a null parameter!");
+ return;
+ }
+ final int status = mWordListValues.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
+ if (MetadataDbHelper.STATUS_DOWNLOADING != status) {
+ final String id = mWordListValues.getAsString(MetadataDbHelper.WORDLISTID_COLUMN);
+ Log.e(TAG, "Unexpected state of the word list '" + id + "' : " + status
+ + " for an InstallAfterDownload action. Bailing out.");
+ return;
+ }
+
+ DebugLogUtils.l("Setting word list as installed");
+ final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
+ MetadataDbHelper.markEntryAsFinishedDownloadingAndInstalled(db, mWordListValues);
+
+ // Install the downloaded file by un-compressing and moving it to the staging
+ // directory. Ideally, we should do this before updating the DB, but the
+ // installDictToStagingFromContentProvider() relies on the db being updated.
+ final String localeString = mWordListValues.getAsString(MetadataDbHelper.LOCALE_COLUMN);
+ BinaryDictionaryFileDumper.installDictToStagingFromContentProvider(
+ LocaleUtils.constructLocaleFromString(localeString), context, false);
+ }
+ }
+
+ /**
+ * An action that enables an existing word list.
+ */
+ public static final class EnableAction implements Action {
+ static final String TAG = "DictionaryProvider:" + EnableAction.class.getSimpleName();
+ private final String mClientId;
+ // The state to upgrade from. May not be null.
+ final WordListMetadata mWordList;
+
+ public EnableAction(final String clientId, final WordListMetadata wordList) {
+ DebugLogUtils.l("New EnableAction for client ", clientId, " : ", wordList);
+ mClientId = clientId;
+ mWordList = wordList;
+ }
+
+ @Override
+ public void execute(final Context context) {
+ if (null == mWordList) {
+ Log.e(TAG, "EnableAction with a null parameter!");
+ return;
+ }
+ DebugLogUtils.l("Enabling word list");
+ final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
+ final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
+ mWordList.mId, mWordList.mVersion);
+ final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
+ if (MetadataDbHelper.STATUS_DISABLED != status
+ && MetadataDbHelper.STATUS_DELETING != status) {
+ Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + " : " + status
+ + " for an enable action. Cancelling");
+ return;
+ }
+ MetadataDbHelper.markEntryAsEnabled(db, mWordList.mId, mWordList.mVersion);
+ }
+ }
+
+ /**
+ * An action that disables a word list.
+ */
+ public static final class DisableAction implements Action {
+ static final String TAG = "DictionaryProvider:" + DisableAction.class.getSimpleName();
+ private final String mClientId;
+ // The word list to disable. May not be null.
+ final WordListMetadata mWordList;
+ public DisableAction(final String clientId, final WordListMetadata wordlist) {
+ DebugLogUtils.l("New Disable action for client ", clientId, " : ", wordlist);
+ mClientId = clientId;
+ mWordList = wordlist;
+ }
+
+ @Override
+ public void execute(final Context context) {
+ if (null == mWordList) { // This should never happen
+ Log.e(TAG, "DisableAction with a null word list!");
+ return;
+ }
+ DebugLogUtils.l("Disabling word list : " + mWordList);
+ final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
+ final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
+ mWordList.mId, mWordList.mVersion);
+ final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
+ if (MetadataDbHelper.STATUS_INSTALLED == status) {
+ // Disabling an installed word list
+ MetadataDbHelper.markEntryAsDisabled(db, mWordList.mId, mWordList.mVersion);
+ } else {
+ if (MetadataDbHelper.STATUS_DOWNLOADING != status) {
+ Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' : "
+ + status + " for a disable action. Fall back to marking as available.");
+ }
+ // The word list is still downloading. Cancel the download and revert the
+ // word list status to "available".
+ final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
+ manager.remove(values.getAsLong(MetadataDbHelper.PENDINGID_COLUMN));
+ MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion);
+ }
+ }
+ }
+
+ /**
+ * An action that makes a word list available.
+ */
+ public static final class MakeAvailableAction implements Action {
+ static final String TAG = "DictionaryProvider:" + MakeAvailableAction.class.getSimpleName();
+ private final String mClientId;
+ // The word list to make available. May not be null.
+ final WordListMetadata mWordList;
+ public MakeAvailableAction(final String clientId, final WordListMetadata wordlist) {
+ DebugLogUtils.l("New MakeAvailable action", clientId, " : ", wordlist);
+ mClientId = clientId;
+ mWordList = wordlist;
+ }
+
+ @Override
+ public void execute(final Context context) {
+ if (null == mWordList) { // This should never happen
+ Log.e(TAG, "MakeAvailableAction with a null word list!");
+ return;
+ }
+ final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
+ if (null != MetadataDbHelper.getContentValuesByWordListId(db,
+ mWordList.mId, mWordList.mVersion)) {
+ Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' "
+ + " for a makeavailable action. Marking as available anyway.");
+ }
+ DebugLogUtils.l("Making word list available : " + mWordList);
+ // If mLocalFilename is null, then it's a remote file that hasn't been downloaded
+ // yet, so we set the local filename to the empty string.
+ final ContentValues values = MetadataDbHelper.makeContentValues(0,
+ MetadataDbHelper.TYPE_BULK, MetadataDbHelper.STATUS_AVAILABLE,
+ mWordList.mId, mWordList.mLocale, mWordList.mDescription,
+ null == mWordList.mLocalFilename ? "" : mWordList.mLocalFilename,
+ mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
+ mWordList.mChecksum, mWordList.mRetryCount, mWordList.mFileSize,
+ mWordList.mVersion, mWordList.mFormatVersion);
+ PrivateLog.log("Insert 'available' record for " + mWordList.mDescription
+ + " and locale " + mWordList.mLocale);
+ db.insert(MetadataDbHelper.METADATA_TABLE_NAME, null, values);
+ }
+ }
+
+ /**
+ * An action that marks a word list as pre-installed.
+ *
+ * This is almost the same as MakeAvailableAction, as it only inserts a line with parameters
+ * received from outside.
+ * Unlike MakeAvailableAction, the parameters are not received from a downloaded metadata file
+ * but from the client directly; it marks a word list as being "installed" and not "available".
+ * It also explicitly sets the filename to the empty string, so that we don't try to open
+ * it on our side.
+ */
+ public static final class MarkPreInstalledAction implements Action {
+ static final String TAG = "DictionaryProvider:"
+ + MarkPreInstalledAction.class.getSimpleName();
+ private final String mClientId;
+ // The word list to mark pre-installed. May not be null.
+ final WordListMetadata mWordList;
+ public MarkPreInstalledAction(final String clientId, final WordListMetadata wordlist) {
+ DebugLogUtils.l("New MarkPreInstalled action", clientId, " : ", wordlist);
+ mClientId = clientId;
+ mWordList = wordlist;
+ }
+
+ @Override
+ public void execute(final Context context) {
+ if (null == mWordList) { // This should never happen
+ Log.e(TAG, "MarkPreInstalledAction with a null word list!");
+ return;
+ }
+ final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
+ if (null != MetadataDbHelper.getContentValuesByWordListId(db,
+ mWordList.mId, mWordList.mVersion)) {
+ Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' "
+ + " for a markpreinstalled action. Marking as preinstalled anyway.");
+ }
+ DebugLogUtils.l("Marking word list preinstalled : " + mWordList);
+ // This word list is pre-installed : we don't have its file. We should reset
+ // the local file name to the empty string so that we don't try to open it
+ // accidentally. The remote filename may be set by the application if it so wishes.
+ final ContentValues values = MetadataDbHelper.makeContentValues(0,
+ MetadataDbHelper.TYPE_BULK, MetadataDbHelper.STATUS_INSTALLED,
+ mWordList.mId, mWordList.mLocale, mWordList.mDescription,
+ TextUtils.isEmpty(mWordList.mLocalFilename) ? "" : mWordList.mLocalFilename,
+ mWordList.mRemoteFilename, mWordList.mLastUpdate,
+ mWordList.mRawChecksum, mWordList.mChecksum, mWordList.mRetryCount,
+ mWordList.mFileSize, mWordList.mVersion, mWordList.mFormatVersion);
+ PrivateLog.log("Insert 'preinstalled' record for " + mWordList.mDescription
+ + " and locale " + mWordList.mLocale);
+ db.insert(MetadataDbHelper.METADATA_TABLE_NAME, null, values);
+ }
+ }
+
+ /**
+ * An action that updates information about a word list - description, locale etc
+ */
+ public static final class UpdateDataAction implements Action {
+ static final String TAG = "DictionaryProvider:" + UpdateDataAction.class.getSimpleName();
+ private final String mClientId;
+ final WordListMetadata mWordList;
+ public UpdateDataAction(final String clientId, final WordListMetadata wordlist) {
+ DebugLogUtils.l("New UpdateData action for client ", clientId, " : ", wordlist);
+ mClientId = clientId;
+ mWordList = wordlist;
+ }
+
+ @Override
+ public void execute(final Context context) {
+ if (null == mWordList) { // This should never happen
+ Log.e(TAG, "UpdateDataAction with a null word list!");
+ return;
+ }
+ final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
+ ContentValues oldValues = MetadataDbHelper.getContentValuesByWordListId(db,
+ mWordList.mId, mWordList.mVersion);
+ if (null == oldValues) {
+ Log.e(TAG, "Trying to update data about a non-existing word list. Bailing out.");
+ return;
+ }
+ DebugLogUtils.l("Updating data about a word list : " + mWordList);
+ final ContentValues values = MetadataDbHelper.makeContentValues(
+ oldValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN),
+ oldValues.getAsInteger(MetadataDbHelper.TYPE_COLUMN),
+ oldValues.getAsInteger(MetadataDbHelper.STATUS_COLUMN),
+ mWordList.mId, mWordList.mLocale, mWordList.mDescription,
+ oldValues.getAsString(MetadataDbHelper.LOCAL_FILENAME_COLUMN),
+ mWordList.mRemoteFilename, mWordList.mLastUpdate, mWordList.mRawChecksum,
+ mWordList.mChecksum, mWordList.mRetryCount, mWordList.mFileSize,
+ mWordList.mVersion, mWordList.mFormatVersion);
+ PrivateLog.log("Updating record for " + mWordList.mDescription
+ + " and locale " + mWordList.mLocale);
+ db.update(MetadataDbHelper.METADATA_TABLE_NAME, values,
+ MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND "
+ + MetadataDbHelper.VERSION_COLUMN + " = ?",
+ new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) });
+ }
+ }
+
+ /**
+ * An action that deletes the metadata about a word list if possible.
+ *
+ * This is triggered when a specific word list disappeared from the server, or when a fresher
+ * word list is available and the old one was not installed.
+ * If the word list has not been installed, it's possible to delete its associated metadata.
+ * Otherwise, the settings are retained so that the user can still administrate it.
+ */
+ public static final class ForgetAction implements Action {
+ static final String TAG = "DictionaryProvider:" + ForgetAction.class.getSimpleName();
+ private final String mClientId;
+ // The word list to remove. May not be null.
+ final WordListMetadata mWordList;
+ final boolean mHasNewerVersion;
+ public ForgetAction(final String clientId, final WordListMetadata wordlist,
+ final boolean hasNewerVersion) {
+ DebugLogUtils.l("New TryRemove action for client ", clientId, " : ", wordlist);
+ mClientId = clientId;
+ mWordList = wordlist;
+ mHasNewerVersion = hasNewerVersion;
+ }
+
+ @Override
+ public void execute(final Context context) {
+ if (null == mWordList) { // This should never happen
+ Log.e(TAG, "TryRemoveAction with a null word list!");
+ return;
+ }
+ DebugLogUtils.l("Trying to remove word list : " + mWordList);
+ final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
+ final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
+ mWordList.mId, mWordList.mVersion);
+ if (null == values) {
+ Log.e(TAG, "Trying to update the metadata of a non-existing wordlist. Cancelling.");
+ return;
+ }
+ final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
+ if (mHasNewerVersion && MetadataDbHelper.STATUS_AVAILABLE != status) {
+ // If we have a newer version of this word list, we should be here ONLY if it was
+ // not installed - else we should be upgrading it.
+ Log.e(TAG, "Unexpected status for forgetting a word list info : " + status
+ + ", removing URL to prevent re-download");
+ }
+ if (MetadataDbHelper.STATUS_INSTALLED == status
+ || MetadataDbHelper.STATUS_DISABLED == status
+ || MetadataDbHelper.STATUS_DELETING == status) {
+ // If it is installed or disabled, we need to mark it as deleted so that LatinIME
+ // will remove it next time it enquires for dictionaries.
+ // If it is deleting and we don't have a new version, then we have to wait until
+ // LatinIME actually has deleted it before we can remove its metadata.
+ // In both cases, remove the URI from the database since it is not supposed to
+ // be accessible any more.
+ values.put(MetadataDbHelper.REMOTE_FILENAME_COLUMN, "");
+ values.put(MetadataDbHelper.STATUS_COLUMN, MetadataDbHelper.STATUS_DELETING);
+ db.update(MetadataDbHelper.METADATA_TABLE_NAME, values,
+ MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND "
+ + MetadataDbHelper.VERSION_COLUMN + " = ?",
+ new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) });
+ } else {
+ // If it's AVAILABLE or DOWNLOADING or even UNKNOWN, delete the entry.
+ db.delete(MetadataDbHelper.METADATA_TABLE_NAME,
+ MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND "
+ + MetadataDbHelper.VERSION_COLUMN + " = ?",
+ new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) });
+ }
+ }
+ }
+
+ /**
+ * An action that sets the word list for deletion as soon as possible.
+ *
+ * This is triggered when the user requests deletion of a word list. This will mark it as
+ * deleted in the database, and fire an intent for Kelar Keyboard to take notice and
+ * reload its dictionaries right away if it is up. If it is not up now, then it will
+ * delete the actual file the next time it gets up.
+ * A file marked as deleted causes the content provider to supply a zero-sized file to
+ * Kelar Keyboard, which will overwrite any existing file and provide no words for this
+ * word list. This is not exactly a "deletion", since there is an actual file which takes up
+ * a few bytes on the disk, but this allows to override a default dictionary with an empty
+ * dictionary. This way, there is no need for the user to make a distinction between
+ * dictionaries installed by default and add-on dictionaries.
+ */
+ public static final class StartDeleteAction implements Action {
+ static final String TAG = "DictionaryProvider:" + StartDeleteAction.class.getSimpleName();
+ private final String mClientId;
+ // The word list to delete. May not be null.
+ final WordListMetadata mWordList;
+ public StartDeleteAction(final String clientId, final WordListMetadata wordlist) {
+ DebugLogUtils.l("New StartDelete action for client ", clientId, " : ", wordlist);
+ mClientId = clientId;
+ mWordList = wordlist;
+ }
+
+ @Override
+ public void execute(final Context context) {
+ if (null == mWordList) { // This should never happen
+ Log.e(TAG, "StartDeleteAction with a null word list!");
+ return;
+ }
+ DebugLogUtils.l("Trying to delete word list : " + mWordList);
+ final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
+ final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
+ mWordList.mId, mWordList.mVersion);
+ if (null == values) {
+ Log.e(TAG, "Trying to set a non-existing wordlist for removal. Cancelling.");
+ return;
+ }
+ final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
+ if (MetadataDbHelper.STATUS_DISABLED != status) {
+ Log.e(TAG, "Unexpected status for deleting a word list info : " + status);
+ }
+ MetadataDbHelper.markEntryAsDeleting(db, mWordList.mId, mWordList.mVersion);
+ }
+ }
+
+ /**
+ * An action that validates a word list as deleted.
+ *
+ * This will restore the word list as available if it still is, or remove the entry if
+ * it is not any more.
+ */
+ public static final class FinishDeleteAction implements Action {
+ static final String TAG = "DictionaryProvider:" + FinishDeleteAction.class.getSimpleName();
+ private final String mClientId;
+ // The word list to delete. May not be null.
+ final WordListMetadata mWordList;
+ public FinishDeleteAction(final String clientId, final WordListMetadata wordlist) {
+ DebugLogUtils.l("New FinishDelete action for client", clientId, " : ", wordlist);
+ mClientId = clientId;
+ mWordList = wordlist;
+ }
+
+ @Override
+ public void execute(final Context context) {
+ if (null == mWordList) { // This should never happen
+ Log.e(TAG, "FinishDeleteAction with a null word list!");
+ return;
+ }
+ DebugLogUtils.l("Trying to delete word list : " + mWordList);
+ final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
+ final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
+ mWordList.mId, mWordList.mVersion);
+ if (null == values) {
+ Log.e(TAG, "Trying to set a non-existing wordlist for removal. Cancelling.");
+ return;
+ }
+ final int status = values.getAsInteger(MetadataDbHelper.STATUS_COLUMN);
+ if (MetadataDbHelper.STATUS_DELETING != status) {
+ Log.e(TAG, "Unexpected status for finish-deleting a word list info : " + status);
+ }
+ final String remoteFilename =
+ values.getAsString(MetadataDbHelper.REMOTE_FILENAME_COLUMN);
+ // If there isn't a remote filename any more, then we don't know where to get the file
+ // from any more, so we remove the entry entirely. As a matter of fact, if the file was
+ // marked DELETING but disappeared from the metadata on the server, it ended up
+ // this way.
+ if (TextUtils.isEmpty(remoteFilename)) {
+ db.delete(MetadataDbHelper.METADATA_TABLE_NAME,
+ MetadataDbHelper.WORDLISTID_COLUMN + " = ? AND "
+ + MetadataDbHelper.VERSION_COLUMN + " = ?",
+ new String[] { mWordList.mId, Integer.toString(mWordList.mVersion) });
+ } else {
+ MetadataDbHelper.markEntryAsAvailable(db, mWordList.mId, mWordList.mVersion);
+ }
+ }
+ }
+
+ // An action batch consists of an ordered queue of Actions that can execute.
+ private final Queue<Action> mActions;
+
+ public ActionBatch() {
+ mActions = new LinkedList<>();
+ }
+
+ public void add(final Action a) {
+ mActions.add(a);
+ }
+
+ /**
+ * Append all the actions of another action batch.
+ * @param that the upgrade to merge into this one.
+ */
+ public void append(final ActionBatch that) {
+ for (final Action a : that.mActions) {
+ add(a);
+ }
+ }
+
+ /**
+ * Execute this batch.
+ *
+ * @param context the context for getting resources, databases, system services.
+ * @param reporter a Reporter to send errors to.
+ */
+ public void execute(final Context context, final ProblemReporter reporter) {
+ DebugLogUtils.l("Executing a batch of actions");
+ Queue<Action> remainingActions = mActions;
+ while (!remainingActions.isEmpty()) {
+ final Action a = remainingActions.poll();
+ try {
+ a.execute(context);
+ } catch (Exception e) {
+ if (null != reporter)
+ reporter.report(e);
+ }
+ }
+ }
+}