package com.door43.translationstudio.tasks;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Looper;
import android.preference.PreferenceManager;
import com.door43.tools.reporting.Logger;
import com.door43.translationstudio.MainApplication;
import com.door43.translationstudio.R;
import com.door43.translationstudio.SettingsActivity;
import com.door43.translationstudio.AppContext;
import com.door43.translationstudio.core.Library;
import com.door43.translationstudio.core.TargetTranslation;
import com.door43.translationstudio.core.TargetTranslationMigrator;
import com.door43.util.tasks.ManagedTask;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFileFilter;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
/**
* This tasks performs any upgrades that need to occur between app versions
*/
public class UpdateAppTask extends ManagedTask {
public static final String TASK_ID = "update_app";
private final Context mContext;
private String mError = null;
public UpdateAppTask(Context context) {
mContext = context;
}
/**
* Returns any error messages
* @return
*/
public String error() {
return mError;
}
@Override
public void start() {
SharedPreferences settings = AppContext.context().getSharedPreferences(MainApplication.PREFERENCES_TAG, AppContext.context().MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
int lastVersionCode = settings.getInt("last_version_code", 0);
PackageInfo pInfo = null;
try {
pInfo = AppContext.context().getPackageManager().getPackageInfo(AppContext.context().getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
// TRICKY: we always migrate target translations because uninstalling the app removes our notion of a previous install
updateTargetTranslations();
updateBuildNumbers();
if (pInfo != null) {
// use current version if fresh install
lastVersionCode = lastVersionCode == 0 ? pInfo.versionCode : lastVersionCode;
// record latest version
editor.putInt("last_version_code", pInfo.versionCode);
editor.apply();
// check if update is possible
if (pInfo.versionCode > lastVersionCode) {
performUpdates(lastVersionCode, pInfo.versionCode);
}
}
}
/**
* Performs required updates between the two app versions
* @param lastVersion
* @param currentVersion
*/
private void performUpdates(int lastVersion, int currentVersion) {
// perform migrations
if(lastVersion < 87) {
upgradePre87();
}
if(lastVersion < 103) {
upgradePre103();
}
if(lastVersion < 110) {
upgradePre110();
}
if(lastVersion < 111) {
upgradePre111();
}
if(lastVersion < 122) {
Looper.prepare();
PreferenceManager.setDefaultValues(AppContext.context(), R.xml.general_preferences, true);
}
if(lastVersion < 138) {
AppContext.context().deleteDatabase(Library.DATABASE_NAME);
}
}
/**
* Updates the target translations
* NOTE: we used to do this manually but now we run this every time so we don't have to manually
* add a new migration path each time
*/
private void updateTargetTranslations() {
// TRICKY: we manually list the target translations because they won't be viewable until updated
File translatorDir = AppContext.getTranslator().getPath();
File[] dirs = translatorDir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory() && !pathname.getName().equals("cache");
}
});
if(dirs != null) {
for (File tt : dirs) {
if (TargetTranslationMigrator.migrate(tt) == null) {
Logger.w(this.getClass().getName(), "Failed to migrate the target translation " + tt.getName());
}
}
}
// commit migration changes
TargetTranslation[] translations = AppContext.getTranslator().getTargetTranslations();
for(TargetTranslation tt:translations) {
try {
tt.commitSync();
} catch (Exception e) {
Logger.e(this.getClass().getName(), "Failed to commit migration changes to target translation " + tt.getId());
}
}
}
/**
* Updates the generator information for the target translations
*/
private void updateBuildNumbers() {
TargetTranslation[] targetTranslations = AppContext.getTranslator().getTargetTranslations();
for(TargetTranslation tt:targetTranslations) {
try {
TargetTranslation.updateGenerator(mContext, tt);
} catch (Exception e) {
Logger.e(this.getClass().getName(), "Failed to update the generator in the target translation " + tt.getId());
}
}
}
/**
* We moved the target translations to the public files directory so that they persist when the
* app is uninstalled
*/
private void upgradePre111() {
File legacyTranslationsDir = new File(mContext.getFilesDir(), "translations");
File translationsDir = AppContext.getTranslator().getPath();
if(legacyTranslationsDir.exists()) {
translationsDir.mkdirs();
File[] oldFiles = legacyTranslationsDir.listFiles();
boolean errors = false;
for(File file:oldFiles) {
File newFile = new File(translationsDir, file.getName());
try {
if(file.isDirectory()) {
FileUtils.copyDirectory(file, newFile);
} else {
FileUtils.copyFile(file, newFile);
}
} catch (IOException e) {
e.printStackTrace();
Logger.e(this.getClass().getName(), "Failed to move targetTranslation", e);
errors = true;
}
}
// remove old files if there were no errors
if(!errors) {
FileUtils.deleteQuietly(legacyTranslationsDir);
}
}
}
/**
* We need to migrate chunks in targetTranslations because some no longer match up to the source.
*/
private void upgradePre110() {
AppContext.context().deleteDatabase(Library.DATABASE_NAME);
// TRICKY: we deploy the new library in a different task but since we are using it in the migration we need to do so now
try {
AppContext.deployDefaultLibrary();
} catch (Exception e) {
Logger.e(this.getClass().getName(), "Failed to deploy the default index", e);
}
}
/**
* Major changes.
* Moved to the new object management system.
*/
private void upgradePre103() {
publishProgress(-1, "Updating translations");
Logger.i(this.getClass().getName(), "Upgrading source data management from pre 103");
// migrate target translations and profile
File oldTranslationsDir = new File(AppContext.context().getFilesDir(), "git");
File newTranslationsDir = AppContext.getTranslator().getPath();
newTranslationsDir.mkdirs();
File oldProfileDir = new File(oldTranslationsDir, "profile");
File newProfileDir = new File(AppContext.context().getFilesDir(), AppContext.PROFILES_DIR + "/profile");
newProfileDir.getParentFile().mkdirs();
try {
if(oldProfileDir.exists()) {
FileUtils.deleteQuietly(newProfileDir);
FileUtils.moveDirectory(oldProfileDir, newProfileDir);
}
} catch (IOException e) {
Logger.e(this.getClass().getName(), "Failed to migrate the profile", e);
}
try {
if(oldTranslationsDir.exists() && oldTranslationsDir.list().length > 0) {
FileUtils.deleteQuietly(newTranslationsDir);
FileUtils.moveDirectory(oldTranslationsDir, newTranslationsDir);
} else if(oldTranslationsDir.exists()) {
FileUtils.deleteQuietly(oldTranslationsDir);
}
} catch (IOException e) {
Logger.e(this.getClass().getName(), "Failed to migrate the target translations", e);
}
// remove old source
File oldSourceDir = new File(AppContext.context().getFilesDir(), "assets");
File oldTempSourceDir = new File(AppContext.context().getCacheDir(), "assets");
File oldIndexDir = new File(AppContext.context().getCacheDir(), "index");
FileUtils.deleteQuietly(oldSourceDir);
FileUtils.deleteQuietly(oldTempSourceDir);
FileUtils.deleteQuietly(oldIndexDir);
// remove old caches
File oldP2PDir = new File(AppContext.context().getExternalCacheDir(), "transferred");
File oldExportDir = new File(AppContext.context().getCacheDir(), "exported");
File oldImportDir = new File(AppContext.context().getCacheDir(), "imported");
File oldSharingDir = new File(AppContext.context().getCacheDir(), "sharing");
FileUtils.deleteQuietly(oldP2PDir);
FileUtils.deleteQuietly(oldExportDir);
FileUtils.deleteQuietly(oldImportDir);
FileUtils.deleteQuietly(oldSharingDir);
// clear old logs and crash reports
Logger.flush();
File stacktraceDir = new File(AppContext.context().getExternalCacheDir(), AppContext.context().STACKTRACE_DIR);
FileUtils.deleteQuietly(stacktraceDir);
}
/**
* Change default font to noto because most of the others do not work
*/
private void upgradePre87() {
publishProgress(-1, "Updating fonts");
Logger.i(this.getClass().getName(), "Upgrading fonts from pre 87");
SharedPreferences.Editor editor = AppContext.context().getUserPreferences().edit();
editor.putString(SettingsActivity.KEY_PREF_TRANSLATION_TYPEFACE, AppContext.context().getString(R.string.pref_default_translation_typeface));
editor.apply();
}
@Override
public int maxProgress() {
return 100;
}
}