]> gitweb.fperrin.net Git - Dictionary.git/blob - src/com/hughes/android/dictionary/DictionaryApplication.java
Random word, report issue, what's new.
[Dictionary.git] / src / com / hughes / android / dictionary / DictionaryApplication.java
1 // Copyright 2011 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package com.hughes.android.dictionary;
16
17 import java.io.BufferedReader;
18 import java.io.File;
19 import java.io.IOException;
20 import java.io.InputStreamReader;
21 import java.io.Serializable;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.Comparator;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28
29 import android.app.Application;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.SharedPreferences;
33 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
34 import android.net.Uri;
35 import android.preference.PreferenceManager;
36 import android.util.Log;
37 import android.view.Menu;
38 import android.view.MenuItem;
39 import android.view.MenuItem.OnMenuItemClickListener;
40
41 import com.hughes.android.dictionary.engine.Dictionary;
42 import com.hughes.android.dictionary.engine.Language;
43 import com.hughes.android.dictionary.engine.TransliteratorManager;
44 import com.hughes.android.util.PersistentObjectCache;
45 import com.ibm.icu.text.Collator;
46
47 public class DictionaryApplication extends Application {
48   
49   static final String LOG = "QuickDicApp";
50   
51   // Static, determined by resources (and locale).
52   // Unordered.
53   static Map<String,DictionaryInfo> DOWNLOADABLE_NAME_TO_INFO = null;
54   
55   static final class DictionaryConfig implements Serializable {
56     private static final long serialVersionUID = -1444177164708201263L;
57     // User-ordered list, persisted, just the ones that are/have been present.
58     final List<String> dictionaryFilesOrdered = new ArrayList<String>();
59     final Map<String, DictionaryInfo> dictionaryInfoCache = new LinkedHashMap<String, DictionaryInfo>();
60   }
61   DictionaryConfig dictionaryConfig = null;
62
63   static final class DictionaryHistory implements Serializable {
64     private static final long serialVersionUID = -4842995032541390284L;
65     // User-ordered list, persisted, just the ones that are/have been present.
66     final List<DictionaryLink> dictionaryLinks = new ArrayList<DictionaryLink>();
67   }
68   DictionaryHistory dictionaryHistory = null;
69   
70   private File dictDir;
71
72   static synchronized void staticInit(final Context context) {
73     if (DOWNLOADABLE_NAME_TO_INFO != null) {
74       return;
75     }
76     DOWNLOADABLE_NAME_TO_INFO = new LinkedHashMap<String,DictionaryInfo>();
77     final BufferedReader reader = new BufferedReader(new InputStreamReader(context.getResources().openRawResource(R.raw.dictionary_info)));
78     try {
79       String line;
80       while ((line = reader.readLine()) != null) {
81         if (line.startsWith("#") || line.length() == 0) {
82           continue;
83         }
84         final DictionaryInfo dictionaryInfo = new DictionaryInfo(line);
85         DOWNLOADABLE_NAME_TO_INFO.put(dictionaryInfo.uncompressedFilename, dictionaryInfo);
86       }
87       reader.close();
88     } catch (IOException e) {
89       Log.e(LOG, "Failed to load downloadable dictionary lists.", e);
90     }
91   }
92
93   
94   @Override
95   public void onCreate() {
96     super.onCreate();
97     Log.d("QuickDic", "Application: onCreate");
98     TransliteratorManager.init(null);
99     staticInit(getApplicationContext());
100     
101     // Load the dictionaries we know about.
102     dictionaryConfig = PersistentObjectCache.init(getApplicationContext()).read(C.DICTIONARY_CONFIGS, DictionaryConfig.class);
103     if (dictionaryConfig == null) {
104       dictionaryConfig = new DictionaryConfig();
105     }
106     
107     // Theme stuff.
108     setTheme(getSelectedTheme().themeId);
109     final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
110     prefs.registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener() {
111       @Override
112       public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
113           String key) {
114         Log.d("THAD", "prefs changed: " + key);
115         if (key.equals(getString(R.string.themeKey))) {
116           setTheme(getSelectedTheme().themeId);
117         }
118       }
119     });
120   }
121   
122   public void onCreateGlobalOptionsMenu(
123       final Context context, final Menu menu) {
124     final MenuItem about = menu.add(getString(R.string.about));
125     about.setOnMenuItemClickListener(new OnMenuItemClickListener() {
126       public boolean onMenuItemClick(final MenuItem menuItem) {
127         final Intent intent = new Intent().setClassName(AboutActivity.class
128             .getPackage().getName(), AboutActivity.class.getCanonicalName());
129         context.startActivity(intent);
130         return false;
131       }
132     });
133
134     final MenuItem help = menu.add(getString(R.string.help));
135     help.setOnMenuItemClickListener(new OnMenuItemClickListener() {
136       public boolean onMenuItemClick(final MenuItem menuItem) {
137         context.startActivity(HelpActivity.getLaunchIntent());
138         return false;
139       }
140     });
141
142     final MenuItem preferences = menu.add(getString(R.string.preferences));
143     preferences.setOnMenuItemClickListener(new OnMenuItemClickListener() {
144       public boolean onMenuItemClick(final MenuItem menuItem) {
145         PreferenceActivity.prefsMightHaveChanged = true;
146         final Intent intent = new Intent().setClassName(PreferenceActivity.class
147             .getPackage().getName(), PreferenceActivity.class.getCanonicalName());
148         context.startActivity(intent);
149         return false;
150       }
151     });
152     
153     
154     final MenuItem reportIssue = menu.add(getString(R.string.reportIssue));
155     reportIssue.setOnMenuItemClickListener(new OnMenuItemClickListener() {
156       public boolean onMenuItemClick(final MenuItem menuItem) {
157         final Intent intent = new Intent(Intent.ACTION_VIEW);
158         intent.setData(Uri.parse("http://code.google.com/p/quickdic-dictionary/issues/entry"));
159         context.startActivity(intent);
160         return false;
161       }
162     });
163   }
164   
165   public synchronized File getDictDir() {
166     // This metaphore doesn't work, because we've already reset prefsMightHaveChanged.
167 //    if (dictDir == null || PreferenceActivity.prefsMightHaveChanged) {
168       final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
169       final String dir = prefs.getString(getString(R.string.quickdicDirectoryKey), getString(R.string.quickdicDirectoryDefault));
170       dictDir = new File(dir);
171       dictDir.mkdirs();
172 //    }
173     return dictDir;
174   }
175   
176   public C.Theme getSelectedTheme() {
177     final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
178     final String theme = prefs.getString(getString(R.string.themeKey), "themeLight");
179     if (theme.equals("themeLight")) {
180       return C.Theme.LIGHT;
181     } else {
182       return C.Theme.DEFAULT;
183     }
184   }
185     
186   public File getPath(String uncompressedFilename) {
187     return new File(getDictDir(), uncompressedFilename);
188   }
189   
190   
191   final Map<String, String> fileToNameCache = new LinkedHashMap<String, String>();
192
193   public String getLanguageName(final String isoCode) {
194     final Language.LanguageResources languageResources = Language.isoCodeToResources.get(isoCode); 
195     final String lang = languageResources != null ? getApplicationContext().getString(languageResources.nameId) : isoCode;
196     return lang;
197   }
198
199   public synchronized String getDictionaryName(final String uncompressedFilename) {
200     String name = fileToNameCache.get(uncompressedFilename);
201     if (name != null) {
202       return name;
203     }
204     
205     final DictionaryInfo dictionaryInfo = DOWNLOADABLE_NAME_TO_INFO.get(uncompressedFilename);
206     if (dictionaryInfo != null) {
207       final StringBuilder nameBuilder = new StringBuilder();
208       for (int i = 0; i < dictionaryInfo.indexInfos.size(); ++i) {
209         if (i > 0) {
210           nameBuilder.append("-");
211         }
212         nameBuilder.append(getLanguageName(dictionaryInfo.indexInfos.get(i).shortName));
213       }
214       name = nameBuilder.toString();
215     } else {
216       name = uncompressedFilename.replace(".quickdic", "");
217     }
218     fileToNameCache.put(uncompressedFilename, name);
219     return name;
220   }
221
222   public synchronized void moveDictionaryToTop(final DictionaryInfo dictionaryInfo) {
223     dictionaryConfig.dictionaryFilesOrdered.remove(dictionaryInfo.uncompressedFilename);
224     dictionaryConfig.dictionaryFilesOrdered.add(0, dictionaryInfo.uncompressedFilename);
225     PersistentObjectCache.getInstance().write(C.DICTIONARY_CONFIGS, dictionaryConfig);
226   }
227
228   public synchronized void deleteDictionary(final DictionaryInfo dictionaryInfo) {
229     while (dictionaryConfig.dictionaryFilesOrdered.remove(dictionaryInfo.uncompressedFilename)) {};
230     dictionaryConfig.dictionaryInfoCache.remove(dictionaryInfo.uncompressedFilename);
231     getPath(dictionaryInfo.uncompressedFilename).delete();
232     PersistentObjectCache.getInstance().write(C.DICTIONARY_CONFIGS, dictionaryConfig);
233   }
234
235   final Collator collator = Collator.getInstance();
236   final Comparator<String> uncompressedFilenameComparator = new Comparator<String>() {
237     @Override
238     public int compare(String uncompressedFilename1, String uncompressedFilename2) {
239       return collator.compare(getDictionaryName(uncompressedFilename1), getDictionaryName(uncompressedFilename2));
240     }
241   };
242   final Comparator<DictionaryInfo> dictionaryInfoComparator = new Comparator<DictionaryInfo>() {
243     @Override
244     public int compare(DictionaryInfo d1, DictionaryInfo d2) {
245       return uncompressedFilenameComparator.compare(d1.uncompressedFilename, d2.uncompressedFilename);
246     }
247   };
248   
249   public void backgroundUpdateDictionaries(final Runnable onUpdateFinished) {
250     new Thread(new Runnable() {
251       @Override
252       public void run() {
253         final DictionaryConfig oldDictionaryConfig = new DictionaryConfig();
254         synchronized(this) {
255           oldDictionaryConfig.dictionaryFilesOrdered.addAll(dictionaryConfig.dictionaryFilesOrdered);
256         }
257         final DictionaryConfig newDictionaryConfig = new DictionaryConfig();
258         for (final String uncompressedFilename : oldDictionaryConfig.dictionaryFilesOrdered) {
259           final File dictFile = getPath(uncompressedFilename);
260           final DictionaryInfo dictionaryInfo = Dictionary.getDictionaryInfo(dictFile);
261           if (dictionaryInfo != null) {
262             newDictionaryConfig.dictionaryFilesOrdered.add(uncompressedFilename);
263             newDictionaryConfig.dictionaryInfoCache.put(uncompressedFilename, dictionaryInfo);
264           }
265         }
266         
267         // Are there dictionaries on the device that we didn't know about already?
268         // Pick them up and put them at the end of the list.
269         final List<String> toAddSorted = new ArrayList<String>();
270         final File[] dictDirFiles = getDictDir().listFiles();
271         if (dictDirFiles != null) {
272           for (final File file : dictDirFiles) {
273             if (file.getName().endsWith(".zip")) {
274               if (DOWNLOADABLE_NAME_TO_INFO.containsKey(file.getName().replace(".zip", ""))) {
275                 file.delete();
276               }
277             }
278             if (!file.getName().endsWith(".quickdic")) {
279               continue;
280             }
281             if (newDictionaryConfig.dictionaryInfoCache.containsKey(file.getName())) {
282               // We have it in our list already.
283               continue;
284             }
285             final DictionaryInfo dictionaryInfo = Dictionary.getDictionaryInfo(file);
286             if (dictionaryInfo == null) {
287               Log.e(LOG, "Unable to parse dictionary: " + file.getPath());
288               continue;
289             }
290             
291             toAddSorted.add(file.getName());
292             newDictionaryConfig.dictionaryInfoCache.put(file.getName(), dictionaryInfo);
293           }
294         } else {
295           Log.w(LOG, "dictDir is not a diretory: " + getDictDir().getPath());
296         }
297         if (!toAddSorted.isEmpty()) {
298           Collections.sort(toAddSorted, uncompressedFilenameComparator);
299           newDictionaryConfig.dictionaryFilesOrdered.addAll(toAddSorted);
300         }
301
302         PersistentObjectCache.getInstance().write(C.DICTIONARY_CONFIGS, newDictionaryConfig);
303         synchronized (this) {
304           dictionaryConfig = newDictionaryConfig;
305         }
306         
307         try {
308           onUpdateFinished.run();
309         } catch (Exception e) {
310           Log.e(LOG, "Exception running callback.", e);
311         }
312       }}).start();
313   }
314
315   public synchronized List<DictionaryInfo> getUsableDicts() {
316     final List<DictionaryInfo> result = new ArrayList<DictionaryInfo>(dictionaryConfig.dictionaryFilesOrdered.size());
317     for (final String uncompressedFilename : dictionaryConfig.dictionaryFilesOrdered) {
318       final DictionaryInfo dictionaryInfo = dictionaryConfig.dictionaryInfoCache.get(uncompressedFilename);
319       if (dictionaryInfo != null) {
320         result.add(dictionaryInfo);
321       }
322     }
323     return result;
324   }
325
326   public synchronized List<DictionaryInfo> getAllDictionaries() {
327     final List<DictionaryInfo> result = getUsableDicts();
328     
329     // The downloadable ones.
330     final Map<String,DictionaryInfo> remaining = new LinkedHashMap<String, DictionaryInfo>(DOWNLOADABLE_NAME_TO_INFO);
331     remaining.keySet().removeAll(dictionaryConfig.dictionaryFilesOrdered);
332     final List<DictionaryInfo> toAddSorted = new ArrayList<DictionaryInfo>(remaining.values());
333     Collections.sort(toAddSorted, dictionaryInfoComparator);
334     result.addAll(toAddSorted);
335     
336     return result;
337   }
338
339   public synchronized boolean isDictionaryOnDevice(String uncompressedFilename) {
340     return dictionaryConfig.dictionaryInfoCache.get(uncompressedFilename) != null;
341   }
342
343   public boolean updateAvailable(final DictionaryInfo dictionaryInfo) {
344     final DictionaryInfo downloadable = DOWNLOADABLE_NAME_TO_INFO.get(dictionaryInfo.uncompressedFilename);
345     return downloadable != null && downloadable.creationMillis > dictionaryInfo.creationMillis;
346   }
347
348   public DictionaryInfo getDownloadable(final String uncompressedFilename) {
349     final DictionaryInfo downloadable = DOWNLOADABLE_NAME_TO_INFO.get(uncompressedFilename);
350     return downloadable;
351   }
352
353 }