X-Git-Url: http://gitweb.fperrin.net/?a=blobdiff_plain;f=src%2Fcom%2Fhughes%2Fandroid%2Fdictionary%2FDictionaryApplication.java;h=39d3486c86e231233da324b91726465c43cd3259;hb=3f9b8540ee99ca4039e9357e021bdccb41b532ef;hp=ecc7856fa9d680e09b8e3590a60dd0003c348060;hpb=8921b3fa8419f957085ca738602f4b1e83ba4f91;p=Dictionary.git diff --git a/src/com/hughes/android/dictionary/DictionaryApplication.java b/src/com/hughes/android/dictionary/DictionaryApplication.java index ecc7856..39d3486 100644 --- a/src/com/hughes/android/dictionary/DictionaryApplication.java +++ b/src/com/hughes/android/dictionary/DictionaryApplication.java @@ -20,18 +20,23 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.net.Uri; +import android.os.Build; import android.os.Environment; import android.preference.PreferenceManager; +import android.support.v4.view.MenuItemCompat; import android.util.Log; import android.util.TypedValue; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; import android.view.View; import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView.ScaleType; +import android.widget.LinearLayout; +import android.widget.Toast; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuItem; -import com.actionbarsherlock.view.MenuItem.OnMenuItemClickListener; +import com.hughes.android.dictionary.CollatorWrapper; import com.hughes.android.dictionary.DictionaryInfo.IndexInfo; import com.hughes.android.dictionary.engine.Dictionary; import com.hughes.android.dictionary.engine.Language; @@ -39,7 +44,6 @@ import com.hughes.android.dictionary.engine.Language.LanguageResources; import com.hughes.android.dictionary.engine.TransliteratorManager; import com.hughes.android.util.PersistentObjectCache; import com.hughes.util.ListUtil; -import com.ibm.icu.text.Collator; import java.io.BufferedReader; import java.io.File; @@ -49,29 +53,86 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -public class DictionaryApplication extends Application { +public enum DictionaryApplication { + INSTANCE; + + private Context appContext; static final String LOG = "QuickDicApp"; + // If set to false, avoid use of ICU collator + // Works well enough for most european languages, + // gives faster startup and avoids crashes on some + // devices due to Dalvik bugs (e.g. ARMv6, S5570i, CM11) + // when using ICU4J. + // Leave it enabled by default for correctness except + // for my known broken development/performance test device config. + //static public final boolean USE_COLLATOR = !android.os.Build.FINGERPRINT.equals("Samsung/cm_tassve/tassve:4.4.4/KTU84Q/20150211:userdebug/release-keys"); + static public final boolean USE_COLLATOR = true; + + static public final TransliteratorManager.ThreadSetup threadBackground = new TransliteratorManager.ThreadSetup() { + @Override + public void onThreadStart() { + // THREAD_PRIORITY_BACKGROUND seemed like a good idea, but it + // can make Transliterator go from 20 seconds to 3 minutes (!) + android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE); + } + }; + // Static, determined by resources (and locale). // Unordered. static Map DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO = null; - static final class DictionaryConfig implements Serializable { + enum Theme { + DEFAULT(R.style.Theme_Default, + R.style.Theme_Default_TokenRow_Fg, + R.color.theme_default_token_row_fg, + R.drawable.theme_default_token_row_main_bg, + R.drawable.theme_default_token_row_other_bg, + R.drawable.theme_default_normal_row_bg), + + LIGHT(R.style.Theme_Light, + R.style.Theme_Light_TokenRow_Fg, + R.color.theme_light_token_row_fg, + R.drawable.theme_light_token_row_main_bg, + R.drawable.theme_light_token_row_other_bg, + R.drawable.theme_light_normal_row_bg); + + Theme(final int themeId, final int tokenRowFg, + final int tokenRowFgColor, + final int tokenRowMainBg, final int tokenRowOtherBg, + final int normalRowBg) { + this.themeId = themeId; + this.tokenRowFg = tokenRowFg; + this.tokenRowFgColor = tokenRowFgColor; + this.tokenRowMainBg = tokenRowMainBg; + this.tokenRowOtherBg = tokenRowOtherBg; + this.normalRowBg = normalRowBg; + } + + final int themeId; + final int tokenRowFg; + final int tokenRowFgColor; + final int tokenRowMainBg; + final int tokenRowOtherBg; + final int normalRowBg; + } + + public static final class DictionaryConfig implements Serializable { private static final long serialVersionUID = -1444177164708201263L; // User-ordered list, persisted, just the ones that are/have been // present. final List dictionaryFilesOrdered = new ArrayList(); - final Map uncompressedFilenameToDictionaryInfo = new LinkedHashMap(); - + final Map uncompressedFilenameToDictionaryInfo = new HashMap(); + /** - * Sometimes a deserialized version of this datastructure isn't valid. + * Sometimes a deserialized version of this data structure isn't valid. * @return */ boolean isValid() { @@ -81,46 +142,51 @@ public class DictionaryApplication extends Application { DictionaryConfig dictionaryConfig = null; - int languageButtonPixels = -1; + public int languageButtonPixels = -1; static synchronized void staticInit(final Context context) { if (DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO != null) { return; } - DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO = new LinkedHashMap(); + DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO = new HashMap(); final BufferedReader reader = new BufferedReader( - new InputStreamReader(context.getResources().openRawResource(R.raw.dictionary_info))); + new InputStreamReader(context.getResources().openRawResource(R.raw.dictionary_info))); try { String line; while ((line = reader.readLine()) != null) { - if (line.startsWith("#") || line.length() == 0) { + if (line.length() == 0 || line.charAt(0) == '#') { continue; } final DictionaryInfo dictionaryInfo = new DictionaryInfo(line); DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO.put( - dictionaryInfo.uncompressedFilename, dictionaryInfo); + dictionaryInfo.uncompressedFilename, dictionaryInfo); } - reader.close(); } catch (IOException e) { Log.e(LOG, "Failed to load downloadable dictionary lists.", e); } + try { + reader.close(); + } catch (IOException e) {} } private File dictDir; - @Override - public void onCreate() { - super.onCreate(); + public synchronized void init(Context c) { + if (appContext != null) { + assert c == appContext; + return; + } + appContext = c; Log.d("QuickDic", "Application: onCreate"); - TransliteratorManager.init(null); - staticInit(getApplicationContext()); + TransliteratorManager.init(null, threadBackground); + staticInit(appContext); languageButtonPixels = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 60, getResources().getDisplayMetrics()); + TypedValue.COMPLEX_UNIT_DIP, 60, appContext.getResources().getDisplayMetrics()); // Load the dictionaries we know about. - dictionaryConfig = PersistentObjectCache.init(getApplicationContext()).read( - C.DICTIONARY_CONFIGS, DictionaryConfig.class); + dictionaryConfig = PersistentObjectCache.init(appContext).read( + C.DICTIONARY_CONFIGS, DictionaryConfig.class); if (dictionaryConfig == null) { dictionaryConfig = new DictionaryConfig(); } @@ -129,90 +195,137 @@ public class DictionaryApplication extends Application { } // Theme stuff. - setTheme(getSelectedTheme().themeId); - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + appContext.setTheme(getSelectedTheme().themeId); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext); prefs.registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, - String key) { + String key) { Log.d("QuickDic", "prefs changed: " + key); - if (key.equals(getString(R.string.themeKey))) { - setTheme(getSelectedTheme().themeId); + if (key.equals(appContext.getString(R.string.themeKey))) { + appContext.setTheme(getSelectedTheme().themeId); } } }); } - public void onCreateGlobalOptionsMenu( - final Context context, final Menu menu) { - final MenuItem about = menu.add(getString(R.string.about)); - about.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + public static void onCreateGlobalOptionsMenu( + final Context context, final Menu menu) { + final Context c = context.getApplicationContext(); + final MenuItem about = menu.add(c.getString(R.string.about)); + MenuItemCompat.setShowAsAction(about, MenuItem.SHOW_AS_ACTION_NEVER); about.setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(final MenuItem menuItem) { - final Intent intent = new Intent().setClassName(AboutActivity.class - .getPackage().getName(), AboutActivity.class.getCanonicalName()); + final Intent intent = new Intent(c, AboutActivity.class); context.startActivity(intent); return false; } }); - final MenuItem help = menu.add(getString(R.string.help)); - help.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + final MenuItem help = menu.add(c.getString(R.string.help)); + MenuItemCompat.setShowAsAction(help, MenuItem.SHOW_AS_ACTION_NEVER); help.setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(final MenuItem menuItem) { - context.startActivity(HtmlDisplayActivity.getHelpLaunchIntent()); + context.startActivity(HtmlDisplayActivity.getHelpLaunchIntent(c)); return false; } }); - final MenuItem preferences = menu.add(getString(R.string.settings)); - preferences.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + final MenuItem preferences = menu.add(c.getString(R.string.settings)); + MenuItemCompat.setShowAsAction(preferences, MenuItem.SHOW_AS_ACTION_NEVER); preferences.setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(final MenuItem menuItem) { PreferenceActivity.prefsMightHaveChanged = true; - final Intent intent = new Intent().setClassName(PreferenceActivity.class - .getPackage().getName(), PreferenceActivity.class.getCanonicalName()); + final Intent intent = new Intent(c, PreferenceActivity.class); context.startActivity(intent); return false; } }); - final MenuItem reportIssue = menu.add(getString(R.string.reportIssue)); - reportIssue.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + final MenuItem reportIssue = menu.add(c.getString(R.string.reportIssue)); + MenuItemCompat.setShowAsAction(reportIssue, MenuItem.SHOW_AS_ACTION_NEVER); reportIssue.setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(final MenuItem menuItem) { final Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri - .parse("http://code.google.com/p/quickdic-dictionary/issues/entry")); + .parse("http://github.com/rdoeffinger/Dictionary/issues")); context.startActivity(intent); return false; } }); } + private String selectDefaultDir() { + final File defaultDictDir = new File(Environment.getExternalStorageDirectory(), "quickDic"); + String dir = defaultDictDir.getAbsolutePath(); + File dictDir = new File(dir); + String[] fileList = dictDir.isDirectory() ? dictDir.list() : null; + if (fileList != null && fileList.length > 0) { + return dir; + } + File efd = null; + try { + efd = appContext.getExternalFilesDir(null); + } catch (Exception e) { + } + if (efd != null) { + efd.mkdirs(); + if (!dictDir.isDirectory() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + appContext.getExternalFilesDirs(null); + } + if (efd.isDirectory() && efd.canWrite() && checkFileCreate(efd)) { + return efd.getAbsolutePath(); + } + } + if (!dictDir.isDirectory() && !dictDir.mkdirs()) { + return appContext.getFilesDir().getAbsolutePath(); + } + return dir; + } + public synchronized File getDictDir() { // This metaphor doesn't work, because we've already reset // prefsMightHaveChanged. - // if (dictDir == null || PreferenceActivity.prefsMightHaveChanged) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - final File defaultDictDir = new File(Environment.getExternalStorageDirectory(), "quickDic"); - String dir = prefs.getString(getString(R.string.quickdicDirectoryKey), - defaultDictDir.getAbsolutePath()); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext); + String dir = prefs.getString(appContext.getString(R.string.quickdicDirectoryKey), ""); if (dir.isEmpty()) { - dir = defaultDictDir.getAbsolutePath(); + dir = selectDefaultDir(); } dictDir = new File(dir); dictDir.mkdirs(); + if (!dictDir.isDirectory() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + appContext.getExternalFilesDirs(null); + } return dictDir; } - public C.Theme getSelectedTheme() { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - final String theme = prefs.getString(getString(R.string.themeKey), "themeLight"); + static public boolean checkFileCreate(File dir) { + boolean res = false; + File testfile = new File(dir, "quickdic_writetest"); + try { + testfile.delete(); + res = testfile.createNewFile() & testfile.delete(); + } catch (Exception e) { + } + return res; + } + + public File getWordListFile() { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext); + String file = prefs.getString(appContext.getString(R.string.wordListFileKey), ""); + if (file.isEmpty()) { + return new File(getDictDir(), "wordList.txt"); + } + return new File(file); + } + + public Theme getSelectedTheme() { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext); + final String theme = prefs.getString(appContext.getString(R.string.themeKey), "themeLight"); if (theme.equals("themeLight")) { - return C.Theme.LIGHT; + return Theme.LIGHT; } else { - return C.Theme.DEFAULT; + return Theme.DEFAULT; } } @@ -222,15 +335,7 @@ public class DictionaryApplication extends Application { String defaultLangISO2 = Locale.getDefault().getLanguage().toLowerCase(); String defaultLangName = null; - final Map fileToNameCache = new LinkedHashMap(); - - public String isoCodeToLocalizedLanguageName(final String isoCode) { - final Language.LanguageResources languageResources = Language.isoCodeToResources - .get(isoCode); - final String lang = languageResources != null ? getApplicationContext().getString( - languageResources.nameId) : isoCode; - return lang; - } + final Map fileToNameCache = new HashMap(); public List sortedIndexInfos(List indexInfos) { // Hack to put the default locale first in the name. @@ -251,7 +356,7 @@ public class DictionaryApplication extends Application { defaultLangName = null; } if (defaultLangName == null) { - defaultLangName = isoCodeToLocalizedLanguageName(defaultLangISO2); + defaultLangName = IsoUtils.INSTANCE.isoCodeToLocalizedLanguageName(appContext, defaultLangISO2); } String name = fileToNameCache.get(uncompressedFilename); @@ -260,7 +365,7 @@ public class DictionaryApplication extends Application { } final DictionaryInfo dictionaryInfo = DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO - .get(uncompressedFilename); + .get(uncompressedFilename); if (dictionaryInfo != null) { final StringBuilder nameBuilder = new StringBuilder(); @@ -270,7 +375,7 @@ public class DictionaryApplication extends Application { nameBuilder.append("-"); } nameBuilder - .append(isoCodeToLocalizedLanguageName(sortedIndexInfos.get(i).shortName)); + .append(IsoUtils.INSTANCE.isoCodeToLocalizedLanguageName(appContext, sortedIndexInfos.get(i).shortName)); } name = nameBuilder.toString(); } else { @@ -280,45 +385,27 @@ public class DictionaryApplication extends Application { return name; } - public View createButton(final Context context, final DictionaryInfo dictionaryInfo, - final IndexInfo indexInfo) { - LanguageResources languageResources = Language.isoCodeToResources.get(indexInfo.shortName); - View result; - - if (languageResources == null || languageResources.flagId <= 0) { - Button button = new Button(context); - button.setText(indexInfo.shortName); - result = button; - } else { - ImageButton button = new ImageButton(context); - button.setImageResource(languageResources.flagId); - button.setScaleType(ScaleType.FIT_CENTER); - result = button; - } - result.setMinimumWidth(languageButtonPixels); - result.setMinimumHeight(languageButtonPixels * 2 / 3); - // result.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, - // LayoutParams.WRAP_CONTENT)); - return result; - } - public synchronized void moveDictionaryToTop(final DictionaryInfo dictionaryInfo) { dictionaryConfig.dictionaryFilesOrdered.remove(dictionaryInfo.uncompressedFilename); dictionaryConfig.dictionaryFilesOrdered.add(0, dictionaryInfo.uncompressedFilename); PersistentObjectCache.getInstance().write(C.DICTIONARY_CONFIGS, dictionaryConfig); } + public synchronized void sortDictionaries() { + Collections.sort(dictionaryConfig.dictionaryFilesOrdered, uncompressedFilenameComparator); + PersistentObjectCache.getInstance().write(C.DICTIONARY_CONFIGS, dictionaryConfig); + } + public synchronized void deleteDictionary(final DictionaryInfo dictionaryInfo) { while (dictionaryConfig.dictionaryFilesOrdered.remove(dictionaryInfo.uncompressedFilename)) { } - ; dictionaryConfig.uncompressedFilenameToDictionaryInfo - .remove(dictionaryInfo.uncompressedFilename); + .remove(dictionaryInfo.uncompressedFilename); getPath(dictionaryInfo.uncompressedFilename).delete(); PersistentObjectCache.getInstance().write(C.DICTIONARY_CONFIGS, dictionaryConfig); } - final Collator collator = Collator.getInstance(); + final Comparator collator = USE_COLLATOR ? CollatorWrapper.getInstance() : String.CASE_INSENSITIVE_ORDER; final Comparator uncompressedFilenameComparator = new Comparator() { @Override public int compare(String uncompressedFilename1, String uncompressedFilename2) { @@ -329,7 +416,7 @@ public class DictionaryApplication extends Application { && !name2.startsWith(defaultLangName + "-")) { return -1; } else if (name2.startsWith(defaultLangName + "-") - && !name1.startsWith(defaultLangName + "-")) { + && !name1.startsWith(defaultLangName + "-")) { return 1; } } @@ -353,18 +440,18 @@ public class DictionaryApplication extends Application { @Override public void run() { final DictionaryConfig oldDictionaryConfig = new DictionaryConfig(); - synchronized (this) { + synchronized (DictionaryApplication.this) { oldDictionaryConfig.dictionaryFilesOrdered - .addAll(dictionaryConfig.dictionaryFilesOrdered); + .addAll(dictionaryConfig.dictionaryFilesOrdered); } final DictionaryConfig newDictionaryConfig = new DictionaryConfig(); for (final String uncompressedFilename : oldDictionaryConfig.dictionaryFilesOrdered) { final File dictFile = getPath(uncompressedFilename); final DictionaryInfo dictionaryInfo = Dictionary.getDictionaryInfo(dictFile); - if (dictionaryInfo != null) { + if (dictionaryInfo.isValid() || dictFile.exists()) { newDictionaryConfig.dictionaryFilesOrdered.add(uncompressedFilename); newDictionaryConfig.uncompressedFilenameToDictionaryInfo.put( - uncompressedFilename, dictionaryInfo); + uncompressedFilename, dictionaryInfo); } } @@ -390,26 +477,30 @@ public class DictionaryApplication extends Application { continue; } final DictionaryInfo dictionaryInfo = Dictionary.getDictionaryInfo(file); - if (dictionaryInfo == null) { + if (!dictionaryInfo.isValid()) { Log.e(LOG, "Unable to parse dictionary: " + file.getPath()); - continue; } toAddSorted.add(file.getName()); newDictionaryConfig.uncompressedFilenameToDictionaryInfo.put( - file.getName(), dictionaryInfo); + file.getName(), dictionaryInfo); } } else { - Log.w(LOG, "dictDir is not a diretory: " + getDictDir().getPath()); + Log.w(LOG, "dictDir is not a directory: " + getDictDir().getPath()); } if (!toAddSorted.isEmpty()) { Collections.sort(toAddSorted, uncompressedFilenameComparator); newDictionaryConfig.dictionaryFilesOrdered.addAll(toAddSorted); } - PersistentObjectCache.getInstance() - .write(C.DICTIONARY_CONFIGS, newDictionaryConfig); - synchronized (this) { + try { + PersistentObjectCache.getInstance() + .write(C.DICTIONARY_CONFIGS, newDictionaryConfig); + } catch (Exception e) { + Log.e(LOG, "Failed persisting dictionary configs", e); + } + + synchronized (DictionaryApplication.this) { dictionaryConfig = newDictionaryConfig; } @@ -428,7 +519,7 @@ public class DictionaryApplication extends Application { } for (final String filter : filters) { if (!getDictionaryName(dictionaryInfo.uncompressedFilename).toLowerCase().contains( - filter)) { + filter)) { return false; } } @@ -437,10 +528,10 @@ public class DictionaryApplication extends Application { public synchronized List getDictionariesOnDevice(String[] filters) { final List result = new ArrayList( - dictionaryConfig.dictionaryFilesOrdered.size()); + dictionaryConfig.dictionaryFilesOrdered.size()); for (final String uncompressedFilename : dictionaryConfig.dictionaryFilesOrdered) { final DictionaryInfo dictionaryInfo = dictionaryConfig.uncompressedFilenameToDictionaryInfo - .get(uncompressedFilename); + .get(uncompressedFilename); if (dictionaryInfo != null && matchesFilters(dictionaryInfo, filters)) { result.add(dictionaryInfo); } @@ -450,10 +541,10 @@ public class DictionaryApplication extends Application { public List getDownloadableDictionaries(String[] filters) { final List result = new ArrayList( - dictionaryConfig.dictionaryFilesOrdered.size()); + dictionaryConfig.dictionaryFilesOrdered.size()); - final Map remaining = new LinkedHashMap( - DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO); + final Map remaining = new HashMap( + DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO); remaining.keySet().removeAll(dictionaryConfig.dictionaryFilesOrdered); for (final DictionaryInfo dictionaryInfo : remaining.values()) { if (matchesFilters(dictionaryInfo, filters)) { @@ -470,15 +561,15 @@ public class DictionaryApplication extends Application { public boolean updateAvailable(final DictionaryInfo dictionaryInfo) { final DictionaryInfo downloadable = - DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO.get( - dictionaryInfo.uncompressedFilename); + DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO.get( + dictionaryInfo.uncompressedFilename); return downloadable != null && - downloadable.creationMillis > dictionaryInfo.creationMillis; + downloadable.creationMillis > dictionaryInfo.creationMillis; } public DictionaryInfo getDownloadable(final String uncompressedFilename) { final DictionaryInfo downloadable = DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO - .get(uncompressedFilename); + .get(uncompressedFilename); return downloadable; }