EMMA Coverage Report (generated Wed Jun 27 17:43:42 CEST 2012)
[all classes][aarddict.android]

COVERAGE SUMMARY FOR SOURCE FILE [DictionaryService.java]

nameclass, %method, %block, %line, %
DictionaryService.java86%  (6/7)81%  (30/37)71%  (803/1134)75%  (174,1/231)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class DictionaryService$40%   (0/1)0%   (0/2)0%   (0/33)0%   (0/4)
DictionaryService$4 (DictionaryService, Uri): void 0%   (0/1)0%   (0/9)0%   (0/1)
run (): void 0%   (0/1)0%   (0/24)0%   (0/3)
     
class DictionaryService$3100% (1/1)50%  (1/2)19%  (6/31)17%  (1/6)
onReceive (Context, Intent): void 0%   (0/1)0%   (0/25)0%   (0/5)
DictionaryService$3 (DictionaryService): void 100% (1/1)100% (6/6)100% (1/1)
     
class DictionaryService$DeleteObserver100% (1/1)67%  (2/3)39%  (38/98)50%  (8/16)
onEvent (int, String): void 0%   (0/1)0%   (0/60)0%   (0/8)
DictionaryService$DeleteObserver (DictionaryService, String): void 100% (1/1)100% (16/16)100% (5/5)
add (String): void 100% (1/1)100% (22/22)100% (3/3)
     
class DictionaryService100% (1/1)88%  (22/25)77%  (698/911)79%  (156,1/197)
access$000 (DictionaryService): Set 0%   (0/1)0%   (0/3)0%   (0/1)
onDestroy (): void 0%   (0/1)0%   (0/57)0%   (0/12)
open (File): Map 0%   (0/1)0%   (0/23)0%   (0/4)
onStart (Intent, int): void 100% (1/1)18%  (8/45)27%  (2,2/8)
open (List): Map 100% (1/1)75%  (158/211)73%  (33/45)
refresh (): void 100% (1/1)78%  (53/68)92%  (12/13)
saveDictFileList (): void 100% (1/1)83%  (30/36)78%  (7/9)
loadDictFileList (): void 100% (1/1)85%  (35/41)82%  (9/11)
scanDir (File): List 100% (1/1)90%  (114/127)92%  (24/26)
DictionaryService (): void 100% (1/1)100% (31/31)100% (6/6)
discover (): List 100% (1/1)100% (39/39)100% (10/10)
followLink (CharSequence, String): Iterator 100% (1/1)100% (7/7)100% (1/1)
getArticle (Entry): Article 100% (1/1)100% (5/5)100% (1/1)
getDeleteObserver (File): DictionaryService$DeleteObserver 100% (1/1)100% (30/30)100% (8/8)
getDictionaries (): Library 100% (1/1)100% (3/3)100% (1/1)
getDisplayTitle (String): CharSequence 100% (1/1)100% (6/6)100% (1/1)
getVolume (String): Volume 100% (1/1)100% (5/5)100% (1/1)
getVolumes (): Map 100% (1/1)100% (39/39)100% (8/8)
isSymlink (File): boolean 100% (1/1)100% (29/29)100% (8/8)
lookup (CharSequence): Iterator 100% (1/1)100% (6/6)100% (1/1)
onBind (Intent): IBinder 100% (1/1)100% (3/3)100% (1/1)
onCreate (): void 100% (1/1)100% (40/40)100% (11/11)
openDictionaries (): void 100% (1/1)100% (47/47)100% (8/8)
redirect (Article): Article 100% (1/1)100% (5/5)100% (1/1)
setPreferred (String): void 100% (1/1)100% (5/5)100% (2/2)
     
class DictionaryService$1100% (1/1)100% (1/1)100% (30/30)100% (8/8)
DictionaryService$1 (DictionaryService): void 100% (1/1)100% (30/30)100% (8/8)
     
class DictionaryService$2100% (1/1)100% (2/2)100% (22/22)100% (2/2)
DictionaryService$2 (DictionaryService): void 100% (1/1)100% (6/6)100% (1/1)
accept (File, String): boolean 100% (1/1)100% (16/16)100% (1/1)
     
class DictionaryService$LocalBinder100% (1/1)100% (2/2)100% (9/9)100% (2/2)
DictionaryService$LocalBinder (DictionaryService): void 100% (1/1)100% (6/6)100% (1/1)
getService (): DictionaryService 100% (1/1)100% (3/3)100% (1/1)

1/* This file is part of Aard Dictionary for Android <http://aarddict.org>.
2 * 
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 3
5 * as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 * GNU General Public License <http://www.gnu.org/licenses/gpl-3.0.txt>
11 * for more details.
12 * 
13 * Copyright (C) 2010 Igor Tkach
14*/
15 
16package aarddict.android;
17 
18import java.io.File;
19import java.io.FileInputStream;
20import java.io.FileOutputStream;
21import java.io.FilenameFilter;
22import java.io.IOException;
23import java.io.ObjectInputStream;
24import java.io.ObjectOutputStream;
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.Collections;
28import java.util.HashMap;
29import java.util.HashSet;
30import java.util.Iterator;
31import java.util.LinkedHashMap;
32import java.util.LinkedHashSet;
33import java.util.List;
34import java.util.Map;
35import java.util.Set;
36import java.util.UUID;
37 
38import aarddict.Article;
39import aarddict.ArticleNotFound;
40import aarddict.Entry;
41import aarddict.Header;
42import aarddict.Library;
43import aarddict.Metadata;
44import aarddict.RedirectTooManyLevels;
45import aarddict.Volume;
46import android.app.Service;
47import android.content.BroadcastReceiver;
48import android.content.Context;
49import android.content.Intent;
50import android.content.IntentFilter;
51import android.net.Uri;
52import android.os.Binder;
53import android.os.FileObserver;
54import android.os.IBinder;
55import android.util.Log;
56 
57public final class DictionaryService extends Service {
58                
59    public class LocalBinder extends Binder {
60            DictionaryService getService() {
61            return DictionaryService.this;
62        }
63    }        
64        
65        private final static String TAG = "aarddict.android.DictionaryService";
66        
67        public final static String DISCOVERY_STARTED = TAG + ".DISCOVERY_STARTED";
68        public final static String DISCOVERY_FINISHED = TAG + ".DISCOVERY_FINISHED";
69        public final static String OPEN_STARTED = TAG + ".OPEN_STARTED";
70        public final static String OPENED_DICT = TAG + ".OPENED_DICT";
71        public final static String CLOSED_DICT = TAG + ".CLOSED_DICT";
72        public final static String DICT_OPEN_FAILED = TAG + ".DICT_OPEN_FAILED";
73        public final static String OPEN_FINISHED = TAG + ".OPEN_FINISHED";
74 
75    private Library             library;
76 
77    private Set<String>         excludedScanDirs   = new HashSet<String>() {
78                                                       {
79                                                           add("/proc");
80                                                           add("/dev");
81                                                           add("/etc");
82                                                           add("/sys");
83                                                           add("/acct");
84                                                           add("/cache");
85                                                       }
86                                                   };
87 
88    private FilenameFilter fileFilter = new FilenameFilter() {
89        public boolean accept(File dir, String filename) {
90            return filename.toLowerCase().endsWith(
91                    ".aar") || new File(dir, filename).isDirectory();
92        }
93    };
94        
95    @Override
96    public IBinder onBind(Intent intent) {
97        return binder;
98    }
99    
100    private final IBinder binder = new LocalBinder();
101 
102    private BroadcastReceiver broadcastReceiver;    
103 
104        @Override
105        public void onCreate() {
106                Log.d(TAG, "On create");
107                library = new Library();
108                loadDictFileList();
109                broadcastReceiver = new BroadcastReceiver() {
110            @Override
111            public void onReceive(Context context, Intent intent) {
112                String action = intent.getAction();
113                Uri path = intent.getData();
114                Log.d(TAG, String.format("action: %s, path: %s", action, path));
115                stopSelf();
116            }                    
117                };
118        IntentFilter intentFilter = new IntentFilter();
119        intentFilter.addDataScheme("file");
120        intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
121        intentFilter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL);
122        intentFilter.addAction(Intent.ACTION_MEDIA_REMOVED);
123                registerReceiver(broadcastReceiver, intentFilter);
124        }
125 
126        @Override
127        public void onStart(Intent intent, int startId) {
128                String action = intent == null ? null : intent.getAction();
129                if (action != null && action.equals(Intent.ACTION_VIEW)) {
130                        final Uri data = intent.getData();
131                        Log.d(TAG, "Path: " + data.getPath());              
132                        if (data != null && data.getPath() != null) {
133                                Runnable r = new Runnable() {                   
134                                        public void run() {
135                                                Log.d(TAG, "opening: " + data.getPath());
136                                                open(new File(data.getPath()));                       
137                                        }
138                                };
139                                new Thread(r).start();                  
140                        }
141                }        
142        }
143                
144        synchronized public void openDictionaries() {
145                Log.d(TAG, "opening dictionaries");
146                long t0 = System.currentTimeMillis();                        
147                List<File> candidates = new ArrayList<File>();
148                for (String path : dictionaryFileNames) {
149                        candidates.add(new File(path));
150                }
151                open(candidates);
152                Log.d(TAG, "dictionaries opened in " + (System.currentTimeMillis() - t0));
153        }        
154        
155        
156    synchronized public void refresh() {
157            Log.d(TAG, "starting dictionary discovery");             
158            long t0 = System.currentTimeMillis();
159            List<File> candidates = discover();            
160            Map<File, Exception> errors = open(candidates);            
161            for (File file : candidates) {
162                    String absolutePath = file.getAbsolutePath();
163                    if (!errors.containsKey(file)) {                        
164                            dictionaryFileNames.add(absolutePath);                            
165                    }
166                    else {
167                            Log.w(TAG, "Failed to open file " + absolutePath, errors.get(file));
168                    }
169            }
170            saveDictFileList();                                    
171            Log.d(TAG, "dictionary discovery took " + (System.currentTimeMillis() - t0));            
172    }           
173                
174        private Set<String> dictionaryFileNames = new LinkedHashSet<String>();
175        
176        synchronized public Map<File, Exception> open(File file) {                                        
177                Map<File, Exception> errors = open(Arrays.asList(new File[]{file}));
178                if (errors.size()  == 0 && 
179                                !dictionaryFileNames.contains(file.getAbsoluteFile())) {
180                        saveDictFileList();
181                }
182                return errors;
183        }
184        
185        private final class DeleteObserver extends FileObserver {
186            
187            private Set<String> dictFilesToWatch;
188        private String dir;
189            
190            DeleteObserver(String dir) {
191                super(dir, DELETE);
192                dictFilesToWatch = new HashSet<String>();
193                this.dir = dir;
194            }
195            
196            public void add(String pathToWatch) {
197                Log.d(TAG, String.format("Watch file %s in %s", pathToWatch, dir));
198                dictFilesToWatch.add(pathToWatch);
199            }
200            
201            @Override
202            public void onEvent(int event, String path) {
203            if ((event & FileObserver.DELETE) != 0) {
204                Log.d(TAG, String.format("Received file event %s: %s", event, path));
205                if (dictFilesToWatch.contains(path)) {
206                    Log.d(TAG, String.format("Dictionary file %s in %s has been deleted, stopping service", path, dir));
207                    if (dictionaryFileNames.remove(new File(dir, path).getAbsolutePath()))                    
208                        saveDictFileList();
209                    stopSelf();
210                }                
211            }                                
212            }
213            
214        }
215        
216        private Map<String, DeleteObserver> deleteObservers = new HashMap<String, DeleteObserver>();
217        
218        private DeleteObserver getDeleteObserver(File file) {
219            File parent = file.getParentFile();
220            String dir = parent.getAbsolutePath();
221            DeleteObserver observer = deleteObservers.get(dir);
222            if (observer == null) {
223                observer = new DeleteObserver(dir);
224                observer.startWatching();
225                deleteObservers.put(dir, observer);
226            }
227            return observer;
228        }
229        
230    synchronized private Map<File, Exception> open(List<File> files) {
231        Map<File, Exception> errors = new HashMap<File, Exception>();
232        if (files.size() == 0) {
233            return errors;
234        }
235            Intent notifyOpenStarted = new Intent(OPEN_STARTED);
236            notifyOpenStarted.putExtra("count", files.size());
237            sendBroadcast(notifyOpenStarted);
238            Thread.yield();
239            
240            File cacheDir = getCacheDir();
241            File metaCacheDir = new File(cacheDir, "metadata");
242            if (!metaCacheDir.exists()) {
243                    if (!metaCacheDir.mkdir()) {
244                            Log.w(TAG, "Failed to create metadata cache directory");
245                    }
246            }
247            
248        Map<UUID, Metadata> knownMeta = new HashMap<UUID, Metadata>();                            
249        for (int i = 0;  i < files.size(); i++) {
250                File file = files.get(i);
251                Volume d = null;
252            try {
253                    Log.d(TAG, "Opening " + file.getName());
254                d = new Volume(file, metaCacheDir, knownMeta);
255                Volume existing = library.getVolume(d.getId());
256                if (existing == null) {
257                        Log.d(TAG, "Dictionary " + d.getId() + " is not in current collection");
258                        library.add(d);
259                        DeleteObserver observer = getDeleteObserver(file);
260                        observer.add(file.getName());
261                }
262                else {
263                        Log.d(TAG, "Dictionary " + d.getId() + " is already open");
264                }
265                Intent notifyOpened = new Intent(OPENED_DICT);
266                notifyOpened.putExtra("title", d.getDisplayTitle());
267                notifyOpened.putExtra("count", files.size());
268                notifyOpened.putExtra("i", i);
269                sendBroadcast(notifyOpened);
270                Thread.yield();
271            }
272            catch (Exception e) {
273                Log.e(TAG, "Failed to open " + file, e);
274                Intent notifyFailed = new Intent(DICT_OPEN_FAILED);
275                notifyFailed.putExtra("file", file.getAbsolutePath());
276                notifyFailed.putExtra("count", files.size());
277                notifyFailed.putExtra("i", i);
278                sendBroadcast(notifyFailed);  
279                Thread.yield();
280                errors.put(file, e);
281            }
282        }        
283            sendBroadcast(new Intent(OPEN_FINISHED));
284            Thread.yield();
285        return errors;
286    }        
287        
288        @Override
289        public void onDestroy() {
290                super.onDestroy();                
291                unregisterReceiver(broadcastReceiver);
292        for (Volume d : library) {
293            try {
294                d.close();
295            }
296            catch (IOException e) {
297                Log.e(TAG, "Failed to close " + d, e);
298            }
299        }
300        library.clear();
301        for (DeleteObserver observer : deleteObservers.values()) {
302            observer.stopWatching();
303        }
304        Log.i(TAG, "destroyed");
305        }
306        
307    public List<File> discover() {
308                sendBroadcast(new Intent(DISCOVERY_STARTED));
309                Thread.yield();
310        File scanRoot = new File ("/");
311        List<File> result = new ArrayList<File>();
312        result.addAll(scanDir(scanRoot));
313        Intent intent = new Intent(DISCOVERY_FINISHED);
314        intent.putExtra("count", result.size());
315        sendBroadcast(intent);
316        Thread.yield();
317        return result;
318    }
319    
320    private List<File> scanDir(File dir) {
321        String absolutePath = dir.getAbsolutePath();
322        if (excludedScanDirs.contains(absolutePath)) {
323            Log.d(TAG, String.format("%s is excluded", absolutePath));
324            return Collections.EMPTY_LIST;
325        }
326        boolean symlink = false;
327        try {
328            symlink = isSymlink(dir);
329        } catch (IOException e) {
330            Log.e(TAG,
331                    String.format("Failed to check if %s is symlink",
332                            dir.getAbsolutePath()));
333        }
334 
335        if (symlink) {
336            Log.d(TAG, String.format("%s is a symlink", absolutePath));
337            return Collections.EMPTY_LIST;
338        }
339 
340        if (dir.isHidden()) {
341            Log.d(TAG, String.format("%s is hidden", absolutePath));
342            return Collections.EMPTY_LIST;
343        }
344        Log.d(TAG, "Scanning " + absolutePath);
345        List<File> candidates = new ArrayList<File>();
346        File[] files = dir.listFiles(fileFilter);
347        if (files != null) {
348            for (int i = 0; i < files.length; i++) {
349                File file = files[i];
350                if (file.isDirectory()) {
351                    candidates.addAll(scanDir(file));
352                } else {
353                    if (!file.isHidden() && file.isFile()) {
354                        candidates.add(file);
355                    }
356                }
357            }
358        }
359        return candidates;
360    }
361 
362    static boolean isSymlink(File file) throws IOException {
363        File fileInCanonicalDir = null;
364        if (file.getParent() == null) {
365            fileInCanonicalDir = file;
366        } else {
367            File canonicalDir = file.getParentFile().getCanonicalFile();
368            fileInCanonicalDir = new File(canonicalDir, file.getName());
369        }
370        if (fileInCanonicalDir.getCanonicalFile().equals(
371                fileInCanonicalDir.getAbsoluteFile())) {
372            return false;
373        } else {
374            return true;
375        }
376    }
377    
378    public void setPreferred(String volumeId) {
379            library.makeFirst(volumeId);            
380    }
381    
382    public Iterator<Entry> lookup(CharSequence word) {
383        return library.bestMatch(word.toString());
384    }
385    
386    public Iterator<Entry> followLink(CharSequence word, String fromVolumeId) throws ArticleNotFound {
387        return library.followLink(word.toString(), fromVolumeId);
388    }    
389 
390    public Article redirect(Article article) throws RedirectTooManyLevels, ArticleNotFound, IOException {
391        return library.redirect(article);
392    }
393 
394    public Volume getVolume(String volumeId) {
395        return library.getVolume(volumeId);
396    }
397    
398    public Library getDictionaries() {
399            return library;
400    }
401    
402    public CharSequence getDisplayTitle(String volumeId) {
403            return library.getVolume(volumeId).getDisplayTitle();
404    }
405    
406    @SuppressWarnings("unchecked")
407    public Map<UUID, List<Volume>> getVolumes() {
408            Map<UUID, List<Volume>> result = new LinkedHashMap();
409            for (Volume d : library) {
410                    UUID dictionaryId = d.getDictionaryId();
411                        if (!result.containsKey(dictionaryId)) {
412                                result.put(dictionaryId, new ArrayList<Volume>());
413                        }
414                        result.get(dictionaryId).add(d);
415            }                        
416            return result;
417    }
418 
419 
420        public Article getArticle(Entry entry) throws IOException {
421                return library.getArticle(entry);
422        }
423        
424    void saveDictFileList() {        
425        try {
426            File dir = getDir(DICTDIR, 0);
427            File file = new File(dir, DICTFILE);
428            FileOutputStream fout = new FileOutputStream(file);
429            ObjectOutputStream oout = new ObjectOutputStream(fout);
430            oout.writeObject(new ArrayList<String>(dictionaryFileNames));
431        }
432        catch (Exception e) {
433            Log.e(TAG, "Failed to save dictionary file list", e);
434        }        
435    }
436 
437    private final static String DICTDIR = "dicts";
438    private final static String DICTFILE = "dicts.list";
439    
440    @SuppressWarnings("unchecked")
441    void loadDictFileList() {
442        try {
443            File dir = getDir(DICTDIR, 0);
444            File file = new File(dir, DICTFILE);
445            if (file.exists()) {
446                FileInputStream fin = new FileInputStream(file);
447                ObjectInputStream oin = new ObjectInputStream(fin);
448                List<String> data  = (List<String>)oin.readObject();
449                dictionaryFileNames.addAll(data); 
450            }
451        }
452        catch (Exception e) {
453            Log.e(TAG, "Failed to load dictionary file list", e);
454        }        
455    }            
456}

[all classes][aarddict.android]
EMMA 0.0.0 (unsupported private build) (C) Vladimir Roubtsov