Fix scrolling issues in dictonary list
CHANGELOG.md
8 | 8 | ||
9 | 9 | * Display expected file size and entry count in dictionary list. | |
10 | 10 | ||
11 | + | ### Bug Fixes | |
12 | + | ||
13 | + | * Scrolling in dictionary view was slow because we computed the hash of downloaded | |
14 | + | files. We now download to a temporary file, so checking a download now is | |
15 | + | instantaneous and scrolling is smooth again. | |
16 | + | ||
11 | 17 | Changes In 0.2.2 | |
12 | 18 | ------------------- | |
13 | 19 |
app/src/main/java/eu/lepiller/nani/DictionaryDownloadActivity.java
11 | 11 | import androidx.appcompat.app.AppCompatActivity; | |
12 | 12 | import android.os.Bundle; | |
13 | 13 | import android.util.Log; | |
14 | + | import android.util.Pair; | |
14 | 15 | import android.view.View; | |
15 | 16 | import android.widget.ImageButton; | |
16 | 17 | import android.widget.ImageView; | |
… | |||
178 | 179 | ||
179 | 180 | Log.d(TAG, "Start downloading"); | |
180 | 181 | ||
181 | - | for (Map.Entry<String, File> e : d.getDownloads().entrySet()) { | |
182 | + | for (Map.Entry<String, Pair<File, File>> e : d.getDownloads().entrySet()) { | |
182 | 183 | try { | |
183 | 184 | String uri = e.getKey(); | |
184 | 185 | Log.d(TAG, "URL: " + uri); | |
185 | 186 | ||
187 | + | File temporaryFile = e.getValue().first; | |
188 | + | File cacheFile = e.getValue().second; | |
189 | + | ||
186 | 190 | // Download a .sha256 file if it exists | |
187 | 191 | URL url = new URL(uri + ".sha256"); | |
188 | - | byte data[] = new byte[4096]; | |
192 | + | byte[] data = new byte[4096]; | |
189 | 193 | int count; | |
190 | 194 | File file; | |
191 | 195 | ||
… | |||
193 | 197 | connection.connect(); | |
194 | 198 | if(connection.getResponseCode() == HttpURLConnection.HTTP_OK) { | |
195 | 199 | input = connection.getInputStream(); | |
196 | - | file = new File(e.getValue() + ".sha256"); | |
200 | + | file = new File(cacheFile + ".sha256"); | |
197 | 201 | file.getParentFile().mkdirs(); | |
198 | 202 | File old_file = new File(file + ".old"); | |
199 | 203 | if(old_file.exists()) { | |
… | |||
227 | 231 | ||
228 | 232 | // .sha256 file now downloaded | |
229 | 233 | url = new URL(uri); | |
230 | - | file = e.getValue(); | |
234 | + | file = temporaryFile; | |
235 | + | file.getParentFile().mkdirs(); | |
231 | 236 | long expectedLength = -1; | |
232 | 237 | boolean acceptRanges = false; | |
233 | 238 | if(file.exists()) { | |
… | |||
331 | 336 | download_bar.setProgress(100); | |
332 | 337 | removeProgress(); | |
333 | 338 | currentDownloadTask = null; | |
334 | - | updateLayout(d); | |
339 | + | d.switchToCacheFile(); | |
335 | 340 | if(!d.isDownloaded() && d.getExpectedFileSize() <= d.getSize()) { | |
336 | 341 | Snackbar.make(findViewById(R.id.name_view), getString(R.string.error_dico_checksum_fail), | |
337 | 342 | Snackbar.LENGTH_LONG).show(); | |
338 | 343 | d.remove(); | |
339 | 344 | } | |
345 | + | updateLayout(d); | |
340 | 346 | } | |
341 | 347 | } | |
342 | 348 | } |
app/src/main/java/eu/lepiller/nani/dictionary/DictionariesAdapter.java
62 | 62 | ||
63 | 63 | int size = dictionary.getExpectedFileSize(); | |
64 | 64 | if(size < 1500) | |
65 | - | size_view.setText(String.format(context.getString(R.string.dictionary_size_b), size)); | |
65 | + | size_view.setText(String.format(context.getString(R.string.dictionary_expected_size_b), size)); | |
66 | 66 | else if(size < 1500000) | |
67 | - | size_view.setText(String.format(context.getString(R.string.dictionary_size_kb), size/1000)); | |
67 | + | size_view.setText(String.format(context.getString(R.string.dictionary_expected_size_kb), size/1000)); | |
68 | 68 | else | |
69 | - | size_view.setText(String.format(context.getString(R.string.dictionary_size_mb), size/1000000)); | |
69 | + | size_view.setText(String.format(context.getString(R.string.dictionary_expected_size_mb), size/1000000)); | |
70 | 70 | ||
71 | 71 | int entries = dictionary.getExpectedEntries(); | |
72 | 72 | NumberFormat nf = NumberFormat.getInstance(); |
app/src/main/java/eu/lepiller/nani/dictionary/Dictionary.java
4 | 4 | import android.graphics.drawable.Drawable; | |
5 | 5 | import android.os.Build; | |
6 | 6 | import android.util.Log; | |
7 | + | import android.util.Pair; | |
7 | 8 | ||
8 | 9 | import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; | |
9 | 10 | ||
10 | 11 | import java.io.File; | |
12 | + | import java.io.FileInputStream; | |
11 | 13 | import java.io.FileNotFoundException; | |
12 | 14 | import java.io.FileReader; | |
13 | 15 | import java.io.IOException; | |
16 | + | import java.math.BigInteger; | |
14 | 17 | import java.net.URL; | |
18 | + | import java.security.MessageDigest; | |
19 | + | import java.security.NoSuchAlgorithmException; | |
15 | 20 | import java.util.Map; | |
16 | 21 | ||
17 | 22 | abstract public class Dictionary { | |
… | |||
20 | 25 | private int expectedFileSize; | |
21 | 26 | private int expectedEntries; | |
22 | 27 | private String sha256; | |
23 | - | File file; | |
28 | + | File file, temporaryFile; | |
24 | 29 | ||
25 | 30 | Dictionary(String n, String descr, String fullDescr, File cacheDir, int fileSize, int entries, String hash) { | |
26 | 31 | name = n; | |
… | |||
30 | 35 | expectedFileSize = fileSize; | |
31 | 36 | sha256 = hash; | |
32 | 37 | this.file = new File(cacheDir, "/dico/" + name); | |
38 | + | this.temporaryFile = new File(cacheDir, "/tmp/" + name); | |
33 | 39 | } | |
34 | 40 | ||
35 | 41 | public String getName() { | |
… | |||
52 | 58 | } | |
53 | 59 | abstract public boolean isUpToDate(); | |
54 | 60 | ||
55 | - | protected File getFile() { | |
61 | + | File getFile() { | |
56 | 62 | return file; | |
57 | 63 | } | |
64 | + | File getTemporaryFile() { | |
65 | + | return temporaryFile; | |
66 | + | } | |
58 | 67 | ||
59 | 68 | abstract public int getSize(); | |
60 | 69 | ||
61 | 70 | abstract public boolean isDownloaded(); | |
71 | + | abstract public void switchToCacheFile(); | |
62 | 72 | ||
63 | 73 | abstract public int size(); | |
64 | 74 | ||
65 | 75 | // Used for downloads: tells what size we currently have downloaded | |
66 | 76 | abstract int currentSize(); | |
67 | 77 | ||
68 | - | public abstract Map<String, File> getDownloads(); | |
78 | + | public abstract Map<String, Pair<File, File>> getDownloads(); | |
69 | 79 | ||
70 | 80 | public Drawable getDrawable(Context context) { | |
71 | 81 | Drawable drawable; | |
… | |||
93 | 103 | ||
94 | 104 | return sb.toString().trim(); | |
95 | 105 | } | |
106 | + | ||
107 | + | public static String sha256OfFile(File file) { | |
108 | + | MessageDigest digest=null; | |
109 | + | try { | |
110 | + | digest = MessageDigest.getInstance("SHA-256"); | |
111 | + | } catch (NoSuchAlgorithmException e) { | |
112 | + | e.printStackTrace(); | |
113 | + | } | |
114 | + | digest.reset(); | |
115 | + | try { | |
116 | + | byte data[] = new byte[4096]; | |
117 | + | FileInputStream fr = new FileInputStream(file); | |
118 | + | int count; | |
119 | + | while((count = fr.read(data)) != -1) { | |
120 | + | digest.update(data, 0, count); | |
121 | + | } | |
122 | + | } catch (FileNotFoundException e) { | |
123 | + | e.printStackTrace(); | |
124 | + | } catch (IOException e) { | |
125 | + | e.printStackTrace(); | |
126 | + | } | |
127 | + | byte[] result = digest.digest(); | |
128 | + | return String.format("%0" + (result.length*2) + "X", new BigInteger(1, result)).toLowerCase(); | |
129 | + | } | |
96 | 130 | } |
app/src/main/java/eu/lepiller/nani/dictionary/FileDictionary.java
1 | 1 | package eu.lepiller.nani.dictionary; | |
2 | 2 | ||
3 | 3 | import android.util.Log; | |
4 | + | import android.util.Pair; | |
4 | 5 | ||
5 | 6 | import java.io.File; | |
6 | - | import java.io.FileInputStream; | |
7 | 7 | import java.io.FileNotFoundException; | |
8 | - | import java.io.FileReader; | |
9 | 8 | import java.io.IOException; | |
10 | 9 | import java.io.RandomAccessFile; | |
11 | - | import java.math.BigInteger; | |
12 | - | import java.net.MalformedURLException; | |
13 | - | import java.net.URL; | |
14 | - | import java.security.DigestException; | |
15 | - | import java.security.MessageDigest; | |
16 | - | import java.security.NoSuchAlgorithmException; | |
17 | 10 | import java.util.ArrayList; | |
18 | 11 | import java.util.HashMap; | |
19 | 12 | import java.util.Map; | |
… | |||
47 | 40 | ||
48 | 41 | @Override | |
49 | 42 | public boolean isDownloaded() { | |
50 | - | File f = getFile(); | |
43 | + | return getFile().exists(); | |
44 | + | } | |
45 | + | ||
46 | + | @Override | |
47 | + | public void switchToCacheFile() { | |
48 | + | if(checkTemporaryFile()) { | |
49 | + | if(getFile().exists()) { | |
50 | + | getFile().delete(); | |
51 | + | } | |
52 | + | getTemporaryFile().renameTo(getFile()); | |
53 | + | } | |
54 | + | } | |
55 | + | ||
56 | + | private boolean checkTemporaryFile() { | |
57 | + | File f = getTemporaryFile(); | |
51 | 58 | if(f.exists()) { | |
52 | 59 | File sha256 = new File(getFile() + ".sha256"); | |
53 | 60 | if(sha256.exists()) { | |
54 | - | MessageDigest digest=null; | |
55 | - | try { | |
56 | - | digest = MessageDigest.getInstance("SHA-256"); | |
57 | - | } catch (NoSuchAlgorithmException e) { | |
58 | - | e.printStackTrace(); | |
59 | - | } | |
60 | - | digest.reset(); | |
61 | - | try { | |
62 | - | byte data[] = new byte[4096]; | |
63 | - | FileInputStream fr = new FileInputStream(f); | |
64 | - | int count; | |
65 | - | while((count = fr.read(data)) != -1) { | |
66 | - | digest.update(data, 0, count); | |
67 | - | } | |
68 | - | } catch (FileNotFoundException e) { | |
69 | - | e.printStackTrace(); | |
70 | - | } catch (IOException e) { | |
71 | - | e.printStackTrace(); | |
72 | - | } | |
73 | - | byte[] result = digest.digest(); | |
74 | - | String hash = String.format("%0" + (result.length*2) + "X", new BigInteger(1, result)).toLowerCase(); | |
61 | + | String hash = sha256OfFile(f); | |
75 | 62 | ||
76 | 63 | try { | |
77 | 64 | String expected = readSha256FromFile(sha256); | |
… | |||
95 | 82 | ||
96 | 83 | public boolean isUpToDate() { | |
97 | 84 | File sha256 = new File(getFile() + ".sha256"); | |
85 | + | if(!sha256.exists()) | |
86 | + | return true; | |
87 | + | ||
98 | 88 | try { | |
99 | 89 | String current = readSha256FromFile(sha256); | |
100 | 90 | String latest = getSha256(); | |
… | |||
117 | 107 | } | |
118 | 108 | ||
119 | 109 | @Override | |
120 | - | public Map<String, File> getDownloads() { | |
121 | - | HashMap<String, File> result = new HashMap<>(); | |
122 | - | result.put(mUrl, getFile()); | |
110 | + | public Map<String, Pair<File, File>> getDownloads() { | |
111 | + | HashMap<String, Pair<File, File>> result = new HashMap<>(); | |
112 | + | Pair<File, File> pair = new Pair<>(getTemporaryFile(), getFile()); | |
113 | + | result.put(mUrl, pair); | |
123 | 114 | return result; | |
124 | 115 | } | |
125 | 116 | ||
126 | 117 | @Override | |
127 | 118 | public void remove() { | |
128 | - | File file = getFile(); | |
129 | - | file.delete(); | |
119 | + | if(getFile().exists()) | |
120 | + | getFile().delete(); | |
121 | + | if(getTemporaryFile().exists()) | |
122 | + | getTemporaryFile().delete(); | |
130 | 123 | } | |
131 | 124 | ||
132 | 125 | ||
133 | 126 | public int getSize() { | |
134 | - | if(file.exists()) | |
127 | + | if(getFile().exists()) | |
135 | 128 | return (int) (getFile().length()); | |
129 | + | else if(getTemporaryFile().exists()) | |
130 | + | return (int) (getTemporaryFile().length()); | |
136 | 131 | else | |
137 | 132 | return 0; | |
138 | 133 | } |