blob: 42833c4c9c0581803b8e9f0548d6d75084c4308e [file] [log] [blame]
// Copyright 2010-2014, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package org.mozc.android.inputmethod.japanese.userdictionary;
import org.mozc.android.inputmethod.japanese.MozcLog;
import org.mozc.android.inputmethod.japanese.MozcUtil;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionary.Entry;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionary.PosType;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionaryCommand;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionaryCommand.Builder;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionaryCommand.CommandType;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionaryCommandStatus;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionaryCommandStatus.Status;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionaryStorage;
import org.mozc.android.inputmethod.japanese.session.SessionExecutor;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import android.content.res.Resources;
import android.net.Uri;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.AbstractList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
*/
public class UserDictionaryToolModel {
private final SessionExecutor sessionExecutor;
@VisibleForTesting long sessionId;
/** User dictionary storage which only contains dictionary id / name. */
@VisibleForTesting UserDictionaryStorage storage = null;
@VisibleForTesting long selectedDictionaryId = 0;
// TODO(hidehiko): Move this bit into the server side.
@VisibleForTesting boolean dirty = false;
// Unfortunately our target API level 7 doesn't support to passing any arguments to the dialog.
// Thus, as a workaround, we relay the edit target entry's index by this model.
private int editTargetIndex;
// Pending status of import data.
private Uri importUri;
private String importData;
private ZipFile zipFile;
// List "view" by proxying dictionary names in the storage.
private final List<String> dictionaryNameList = new AbstractList<String>() {
@Override
public String get(int index) {
return storage.getDictionaries(index).getName();
}
@Override
public int size() {
return (storage == null) ? 0 : storage.getDictionariesCount();
}
};
public UserDictionaryToolModel(SessionExecutor sessionExecutor) {
this.sessionExecutor = sessionExecutor;
}
/**
* Returns {@code true} if the storage is edited after loading.
*/
public boolean isDirty() {
return dirty;
}
/**
* Creates a session to communicate with the native server about user dictionary editing.
* This method should be called before any other methods.
*/
public void createSession() {
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.CREATE_SESSION)
.build();
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() != Status.USER_DICTIONARY_COMMAND_SUCCESS) {
throw new IllegalStateException("UserDictionaryCommand session should be created always.");
}
sessionId = status.getSessionId();
}
/**
* Deletes the current session.
*/
public void deleteSession() {
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.DELETE_SESSION)
.setSessionId(sessionId)
.build();
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() != Status.USER_DICTIONARY_COMMAND_SUCCESS) {
MozcLog.e("Failed to delete user dictionary command session.");
}
sessionId = 0;
}
/**
* Resumes the current session by loading the data from storage,
* and updates storage and selected id.
* @params defautlDictionaryName the name of the default dictionary, which is created when,
* e.g., LOAD operation is failing, or the storage gets empty because of the deletion of
* the last dictionary.
*/
public Status resumeSession(String defaultDictionaryName) {
ensureSession();
// Set default dictionary name.
{
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.SET_DEFAULT_DICTIONARY_NAME)
.setSessionId(sessionId)
.setDictionaryName(defaultDictionaryName)
.build();
// Ignore any errors.
sessionExecutor.sendUserDictionaryCommand(command);
}
// Load the dictionary.
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.LOAD)
.setSessionId(sessionId)
.setEnsureNonEmptyStorage(true)
.build();
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(command);
// Update the dictionary list regardless of the result of the LOAD command.
// Read new dictionary list.
{
Status updateStatus = updateStorage();
if (updateStatus == Status.USER_DICTIONARY_COMMAND_SUCCESS) {
selectedDictionaryId = storage.getDictionaries(0).getId();
}
}
// Especially on the first time, there are no user dictionary file, so "file not found"
// is (a kind of) expected behavior. In order not to show "error" message to users,
// ignore the error here.
if (status.getStatus() == Status.FILE_NOT_FOUND) {
return Status.USER_DICTIONARY_COMMAND_SUCCESS;
}
return status.getStatus();
}
private void ensureSession() {
// First ping to the mozc server.
{
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.NO_OPERATION)
.setSessionId(sessionId)
.build();
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() == Status.USER_DICTIONARY_COMMAND_SUCCESS) {
return;
}
}
// The session is somehow broken. Actually, because of Android architecture,
// we can have multiple Activity instances, but due to resource limitation the current
// limit of the number of sessions is only 1. So kick the other session out, and recreate
// our session again.
createSession();
}
/**
* Treis to save if necessary, and also tries to reload the server.
*/
public Status pauseSession() {
checkSession();
// Save the dictionary if necessary.
if (dirty) {
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.SAVE)
.setSessionId(sessionId)
.build();
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() != Status.USER_DICTIONARY_COMMAND_SUCCESS) {
return status.getStatus();
}
// When the save is succeeded, we need to reload the mozc server.
sessionExecutor.reload();
}
return Status.USER_DICTIONARY_COMMAND_SUCCESS;
}
/**
* Returns the list "view" of dictionary names in the storage.
* The contents of the returned instance should be automatically updated when the storage
* is updated.
*/
public List<String> getDictionaryNameList() {
return dictionaryNameList;
}
/**
* Sets the current selected dictionary based on the given index.
*/
public void setSelectedDictionaryByIndex(int index) {
if (storage == null || index < 0 || storage.getDictionariesCount() <= index) {
// Invalid state.
selectedDictionaryId = 0;
return;
}
selectedDictionaryId = storage.getDictionaries(index).getId();
}
/**
* Returns the index of the current selected dictionary.
*/
public int getSelectedDictionaryIndex() {
int index = getSelectedDictionaryIndexInternal();
if (index < 0) {
return 0;
}
return index;
}
/**
* Returns the name of the current selected dictionary.
*/
public String getSelectedDictionaryName() {
int index = getSelectedDictionaryIndexInternal();
if (index < -1) {
return null;
}
return storage.getDictionaries(index).getName();
}
private int getSelectedDictionaryIndexInternal() {
if (storage != null && selectedDictionaryId != 0) {
for (int i = 0; i < storage.getDictionariesCount(); ++i) {
if (storage.getDictionaries(i).getId() == selectedDictionaryId) {
return i;
}
}
}
// Not found.
return -1;
}
private void checkSession() {
if (sessionId == 0) {
throw new IllegalStateException("Session is not yet created.");
}
}
/**
* Checks if we can add another dictionary to the storage.
*/
public Status checkNewDictionaryAvailability() {
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.CHECK_NEW_DICTIONARY_AVAILABILITY)
.setSessionId(sessionId)
.build();
return sessionExecutor.sendUserDictionaryCommand(command).getStatus();
}
public Status checkUndoability() {
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.CHECK_UNDOABILITY)
.setSessionId(sessionId)
.build();
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(command);
return status.getStatus();
}
public Status undo() {
int index = getSelectedDictionaryIndex();
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.UNDO)
.setSessionId(sessionId)
.build();
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() == Status.USER_DICTIONARY_COMMAND_SUCCESS) {
dirty = true;
Status updateStatus = updateStorage();
if (updateStatus != Status.USER_DICTIONARY_COMMAND_SUCCESS) {
return updateStatus;
}
boolean found = false;
for (int i = 0; i < storage.getDictionariesCount(); ++i) {
if (storage.getDictionaries(i).getId() == selectedDictionaryId) {
found = true;
break;
}
}
if (!found) {
setSelectedDictionaryByIndex(Math.min(index, storage.getDictionariesCount() - 1));
}
}
return status.getStatus();
}
/**
* Creates a new empty dictionary with the given dictionary name.
* Also update the selected dictionary to the created one.
*/
public Status createDictionary(String dictionaryName) {
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.CREATE_DICTIONARY)
.setSessionId(sessionId)
.setDictionaryName(dictionaryName)
.build();
UserDictionaryCommandStatus status =
sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() == Status.USER_DICTIONARY_COMMAND_SUCCESS) {
dirty = true;
Status updateStatus = updateStorage();
if (updateStatus != Status.USER_DICTIONARY_COMMAND_SUCCESS) {
return updateStatus;
}
selectedDictionaryId = status.getDictionaryId();
}
return status.getStatus();
}
/**
* Renames the currently selected dictionary to the given dictionary name.
*/
public Status renameSelectedDictionary(String dictionaryName) {
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.RENAME_DICTIONARY)
.setSessionId(sessionId)
.setDictionaryId(selectedDictionaryId)
.setDictionaryName(dictionaryName)
.build();
UserDictionaryCommandStatus status =
sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() == Status.USER_DICTIONARY_COMMAND_SUCCESS) {
dirty = true;
Status updateStatus = updateStorage();
if (updateStatus != Status.USER_DICTIONARY_COMMAND_SUCCESS) {
return updateStatus;
}
}
return status.getStatus();
}
/**
* Deletes the current selected dictionary.
* The new selected dictionary should be the "same" position of the dictionaries in the storage.
* If the dictionary gets empty, no dictionary is selected.
* If the deleted dictionary is at the end of the storage,
* the new selected dictionary will be the one at the end of the storage after
* deleting operation.
*/
public Status deleteSelectedDictionary() {
int index = getSelectedDictionaryIndex();
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.DELETE_DICTIONARY)
.setSessionId(sessionId)
.setDictionaryId(selectedDictionaryId)
.setEnsureNonEmptyStorage(true)
.build();
UserDictionaryCommandStatus status =
sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() == Status.USER_DICTIONARY_COMMAND_SUCCESS) {
dirty = true;
Status updateStatus = updateStorage();
if (updateStatus != Status.USER_DICTIONARY_COMMAND_SUCCESS) {
return updateStatus;
}
selectedDictionaryId =
storage.getDictionaries(Math.min(index, storage.getDictionariesCount() - 1)).getId();
}
return status.getStatus();
}
public Optional<File> createExportFile(
Resources resources, String dictionaryName, File tempDirectory) {
Preconditions.checkNotNull(resources);
Preconditions.checkNotNull(dictionaryName);
Preconditions.checkNotNull(tempDirectory);
Preconditions.checkArgument(dictionaryName.length() > 0);
Preconditions.checkArgument(tempDirectory.isDirectory());
File exportFile = null;
ZipOutputStream zipStream = null;
try {
exportFile = File.createTempFile("export_temp_", ".zip", tempDirectory);
exportFile.deleteOnExit();
zipStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(exportFile)));
zipStream.putNextEntry(new ZipEntry(dictionaryName + ".txt"));
int entrySize = getEntrySize();
int fetchSize = 1000;
int queryNum = (int) Math.ceil((double) entrySize / fetchSize);
for (int i = 0; i < queryNum; ++i) {
String exportString = getSelectedDictionaryAsString(
resources, i * fetchSize, Math.min((i + 1) * fetchSize, entrySize));
// getBytes() always returns UTF-8 encoded byte stream because the default charset on
// Android is always UTF-8 and is immutable.
zipStream.write(exportString.getBytes());
}
zipStream.close();
} catch (IOException e) {
MozcLog.e("Failed to prepare export data. " + e.getMessage());
if (zipStream != null){
MozcUtil.closeIgnoringIOException(zipStream);
}
if (exportFile != null) {
exportFile.delete();
}
return Optional.absent();
}
return Optional.of(exportFile);
}
/**
* Dump dictionary entries of which the index is in [beginIndex, endIndex) as string.
*/
private String getSelectedDictionaryAsString(Resources resources, int beginIndex, int endIndex) {
int entrySize = getEntrySize();
Preconditions.checkElementIndex(beginIndex, endIndex);
Preconditions.checkArgument(endIndex <= entrySize);
StringBuilder builder = new StringBuilder();
Map<PosType, String> posNameCache = new EnumMap<PosType, String>(PosType.class);
for (Entry entry : getEntriesInternal(beginIndex, endIndex)) {
String posName = posNameCache.get(entry.getPos());
if (posName == null) {
posName = resources.getString(
UserDictionaryUtil.getPosStringResourceIdForDictionaryExport(entry.getPos()));
posNameCache.put(entry.getPos(), posName);
}
builder.append(entry.getKey());
builder.append('\t');
builder.append(entry.getValue());
builder.append('\t');
builder.append(posName);
builder.append('\t');
builder.append(entry.getComment());
builder.append('\n');
}
return builder.toString();
}
private Status updateStorage() {
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.GET_USER_DICTIONARY_NAME_LIST)
.setSessionId(sessionId)
.build();
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() == Status.USER_DICTIONARY_COMMAND_SUCCESS) {
storage = status.getStorage();
} else {
MozcLog.e("Failed to get dictionary name list: " + status.getStatus());
}
return status.getStatus();
}
/**
* Returns the list "view" of entries in the current selected dictionary.
* The contents of the returned instance should be automatically updated when the selected
* dictionary is updated.
*/
public List<Entry> getEntryList() {
return new AbstractList<Entry>() {
@Override
public Entry get(int index) {
return getEntryInternal(index);
}
@Override
public int size() {
return getEntrySize();
}
};
}
public void setEditTargetIndex(int editTargetIndex) {
this.editTargetIndex = editTargetIndex;
}
public int getEditTargetIndex() {
return this.editTargetIndex;
}
/**
* Returns the entry at current editTargetIndex in the selected dictionary.
*/
public Entry getEditTargetEntry() {
return getEntryInternal(editTargetIndex);
}
private int getEntrySize() {
if (selectedDictionaryId == 0) {
return 0;
}
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.GET_ENTRY_SIZE)
.setSessionId(sessionId)
.setDictionaryId(selectedDictionaryId)
.build();
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() != Status.USER_DICTIONARY_COMMAND_SUCCESS) {
MozcLog.e("Unknown failure: " + status.getStatus());
throw new RuntimeException("Unknown failure");
}
return status.getEntrySize();
}
private Entry getEntryInternal(int index) {
return getEntriesInternal(index, index + 1).get(0);
}
/**
* Returns dictionary entries of which the index is in [beginIndex, endIndex).
*/
private List<Entry> getEntriesInternal(int beginIndex, int endIndex) {
Builder builder = UserDictionaryCommand.newBuilder()
.setType(CommandType.GET_ENTRIES)
.setSessionId(sessionId)
.setDictionaryId(selectedDictionaryId);
for (int i = beginIndex; i < endIndex; ++i) {
builder.addEntryIndex(i);
}
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(builder.build());
if (status.getStatus() != Status.USER_DICTIONARY_COMMAND_SUCCESS) {
MozcLog.e("Unknown failure: " + status.getStatus());
throw new RuntimeException("Unknown failure");
}
return status.getEntriesList();
}
/**
* Checks if we can add another entry to the selected dictionary.
*/
public Status checkNewEntryAvailability() {
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.CHECK_NEW_ENTRY_AVAILABILITY)
.setSessionId(sessionId)
.setDictionaryId(selectedDictionaryId)
.build();
return sessionExecutor.sendUserDictionaryCommand(command).getStatus();
}
/**
* Adds an entry to the selected dictionary.
*/
public Status addEntry(String word, String reading, PosType pos) {
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.ADD_ENTRY)
.setSessionId(sessionId)
.setDictionaryId(selectedDictionaryId)
.setEntry(Entry.newBuilder()
.setKey(reading)
.setValue(word)
.setPos(pos))
.build();
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() == Status.USER_DICTIONARY_COMMAND_SUCCESS) {
dirty = true;
}
return status.getStatus();
}
/**
* Edits the entry, which is specified by editTargetIndex, in the selected dictionary.
*/
public Status editEntry(String word, String reading, PosType pos) {
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.EDIT_ENTRY)
.setSessionId(sessionId)
.setDictionaryId(selectedDictionaryId)
.addEntryIndex(editTargetIndex)
.setEntry(Entry.newBuilder()
.setKey(reading)
.setValue(word)
.setPos(pos))
.build();
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() == Status.USER_DICTIONARY_COMMAND_SUCCESS) {
dirty = true;
}
return status.getStatus();
}
/**
* Deletes the entries specified by the given {@code indexList} in the selected dictionary.
*/
public Status deleteEntry(List<Integer> indexList) {
UserDictionaryCommand command = UserDictionaryCommand.newBuilder()
.setType(CommandType.DELETE_ENTRY)
.setSessionId(sessionId)
.setDictionaryId(selectedDictionaryId)
.addAllEntryIndex(indexList)
.build();
UserDictionaryCommandStatus status = sessionExecutor.sendUserDictionaryCommand(command);
if (status.getStatus() == Status.USER_DICTIONARY_COMMAND_SUCCESS) {
dirty = true;
}
return status.getStatus();
}
public Uri getImportUri() {
return importUri;
}
public void setImportUri(Uri uri) {
this.importUri = uri;
}
public String getImportData() {
return importData;
}
public void setImportData(String data) {
this.importData = data;
}
public void resetImportState() {
importUri = null;
importData = null;
clearZipFile();
}
public ZipFile getZipFile() {
return zipFile;
}
/**
* By the method, this instance takes the ownership of resource management of {@code zipFile}.
* (i.e., it must not to invoke close for the zipFile by the caller).
*/
public void setZipFile(ZipFile zipFile) {
clearZipFile();
this.zipFile = zipFile;
}
/**
* By the method, this instance releases the ownership of resource management of the
* current ZipFile. I.e., the caller needs to invoke close method if the returned ZipFile
* instance.
*/
public ZipFile releaseZipFile() {
ZipFile zipFile = this.zipFile;
this.zipFile = null;
return zipFile;
}
private void clearZipFile() {
if (zipFile != null) {
MozcUtil.closeIgnoringIOException(zipFile);
zipFile = null;
}
}
/**
* Invokes to import the data into a dictionary.
* After all operations, regardless of whether the operation is successfully done or not,
* resets the pending import state.
* @param dictionaryIndex is a position of the import destination dictionary in the storage.
* if it is set to -1, this method tries to create a new dictionary with guessing a
* dictionary name.
*/
public Status importData(int dictionaryIndex) {
try {
// Both importData and importUri should be set before this method's invocation.
if (importData == null || importUri == null) {
throw new NullPointerException();
}
UserDictionaryCommand.Builder builder = UserDictionaryCommand.newBuilder()
.setType(CommandType.IMPORT_DATA)
.setSessionId(sessionId)
.setData(importData);
if (dictionaryIndex < 0) {
builder.setDictionaryName(
UserDictionaryUtil.generateDictionaryNameByUri(importUri, dictionaryNameList));
} else {
builder.setDictionaryId(storage.getDictionaries(dictionaryIndex).getId());
}
UserDictionaryCommandStatus status =
sessionExecutor.sendUserDictionaryCommand(builder.build());
// Regardless of the status, set dirty flag to be conservative,
// because even if the status is not SUCCESS, some entries might be imported.
dirty = true;
if (status.hasDictionaryId()) {
// Update the view.
Status updateStatus = updateStorage();
if (updateStatus != Status.USER_DICTIONARY_COMMAND_SUCCESS) {
return updateStatus;
}
selectedDictionaryId = status.getDictionaryId();
}
return status.getStatus();
} finally {
// Reset the importing state regardless of whether the import operation is succeeded or not.
resetImportState();
}
}
}