Fix scrolling issues in dictonary list

Julien LepillerSun May 31 20:14:20+0200 2020

b717b18

Fix scrolling issues in dictonary list

CHANGELOG.md

88
99
* Display expected file size and entry count in dictionary list.
1010
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+
1117
Changes In 0.2.2
1218
-------------------
1319

app/src/main/java/eu/lepiller/nani/DictionaryDownloadActivity.java

1111
import androidx.appcompat.app.AppCompatActivity;
1212
import android.os.Bundle;
1313
import android.util.Log;
14+
import android.util.Pair;
1415
import android.view.View;
1516
import android.widget.ImageButton;
1617
import android.widget.ImageView;

178179
179180
            Log.d(TAG, "Start downloading");
180181
181-
            for (Map.Entry<String, File> e : d.getDownloads().entrySet()) {
182+
            for (Map.Entry<String, Pair<File, File>> e : d.getDownloads().entrySet()) {
182183
                try {
183184
                    String uri = e.getKey();
184185
                    Log.d(TAG, "URL: " + uri);
185186
187+
                    File temporaryFile = e.getValue().first;
188+
                    File cacheFile = e.getValue().second;
189+
186190
                    // Download a .sha256 file if it exists
187191
                    URL url = new URL(uri + ".sha256");
188-
                    byte data[] = new byte[4096];
192+
                    byte[] data = new byte[4096];
189193
                    int count;
190194
                    File file;
191195

193197
                    connection.connect();
194198
                    if(connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
195199
                        input = connection.getInputStream();
196-
                        file = new File(e.getValue() + ".sha256");
200+
                        file = new File(cacheFile + ".sha256");
197201
                        file.getParentFile().mkdirs();
198202
                        File old_file = new File(file + ".old");
199203
                        if(old_file.exists()) {

227231
228232
                    // .sha256 file now downloaded
229233
                    url = new URL(uri);
230-
                    file = e.getValue();
234+
                    file = temporaryFile;
235+
                    file.getParentFile().mkdirs();
231236
                    long expectedLength = -1;
232237
                    boolean acceptRanges = false;
233238
                    if(file.exists()) {

331336
            download_bar.setProgress(100);
332337
            removeProgress();
333338
            currentDownloadTask = null;
334-
            updateLayout(d);
339+
            d.switchToCacheFile();
335340
            if(!d.isDownloaded() && d.getExpectedFileSize() <= d.getSize()) {
336341
                Snackbar.make(findViewById(R.id.name_view), getString(R.string.error_dico_checksum_fail),
337342
                        Snackbar.LENGTH_LONG).show();
338343
                d.remove();
339344
            }
345+
            updateLayout(d);
340346
        }
341347
    }
342348
}

app/src/main/java/eu/lepiller/nani/dictionary/DictionariesAdapter.java

6262
6363
        int size = dictionary.getExpectedFileSize();
6464
        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));
6666
        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));
6868
        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));
7070
7171
        int entries = dictionary.getExpectedEntries();
7272
        NumberFormat nf = NumberFormat.getInstance();

app/src/main/java/eu/lepiller/nani/dictionary/Dictionary.java

44
import android.graphics.drawable.Drawable;
55
import android.os.Build;
66
import android.util.Log;
7+
import android.util.Pair;
78
89
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
910
1011
import java.io.File;
12+
import java.io.FileInputStream;
1113
import java.io.FileNotFoundException;
1214
import java.io.FileReader;
1315
import java.io.IOException;
16+
import java.math.BigInteger;
1417
import java.net.URL;
18+
import java.security.MessageDigest;
19+
import java.security.NoSuchAlgorithmException;
1520
import java.util.Map;
1621
1722
abstract public class Dictionary {

2025
    private int expectedFileSize;
2126
    private int expectedEntries;
2227
    private String sha256;
23-
    File file;
28+
    File file, temporaryFile;
2429
2530
    Dictionary(String n, String descr, String fullDescr, File cacheDir, int fileSize, int entries, String hash) {
2631
        name = n;

3035
        expectedFileSize = fileSize;
3136
        sha256 = hash;
3237
        this.file = new File(cacheDir, "/dico/" + name);
38+
        this.temporaryFile = new File(cacheDir, "/tmp/" + name);
3339
    }
3440
3541
    public String getName() {

5258
    }
5359
    abstract public boolean isUpToDate();
5460
55-
    protected File getFile() {
61+
    File getFile() {
5662
        return file;
5763
    }
64+
    File getTemporaryFile() {
65+
        return temporaryFile;
66+
    }
5867
5968
    abstract public int getSize();
6069
6170
    abstract public boolean isDownloaded();
71+
    abstract public void switchToCacheFile();
6272
6373
    abstract public int size();
6474
6575
    // Used for downloads: tells what size we currently have downloaded
6676
    abstract int currentSize();
6777
68-
    public abstract Map<String, File> getDownloads();
78+
    public abstract Map<String, Pair<File, File>> getDownloads();
6979
7080
    public Drawable getDrawable(Context context) {
7181
        Drawable drawable;

93103
94104
        return sb.toString().trim();
95105
    }
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+
    }
96130
}

app/src/main/java/eu/lepiller/nani/dictionary/FileDictionary.java

11
package eu.lepiller.nani.dictionary;
22
33
import android.util.Log;
4+
import android.util.Pair;
45
56
import java.io.File;
6-
import java.io.FileInputStream;
77
import java.io.FileNotFoundException;
8-
import java.io.FileReader;
98
import java.io.IOException;
109
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;
1710
import java.util.ArrayList;
1811
import java.util.HashMap;
1912
import java.util.Map;

4740
4841
    @Override
4942
    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();
5158
        if(f.exists()) {
5259
            File sha256 = new File(getFile() + ".sha256");
5360
            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);
7562
7663
                try {
7764
                    String expected = readSha256FromFile(sha256);

9582
9683
    public boolean isUpToDate() {
9784
        File sha256 = new File(getFile() + ".sha256");
85+
        if(!sha256.exists())
86+
            return true;
87+
9888
        try {
9989
            String current = readSha256FromFile(sha256);
10090
            String latest = getSha256();

117107
    }
118108
119109
    @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);
123114
        return result;
124115
    }
125116
126117
    @Override
127118
    public void remove() {
128-
        File file = getFile();
129-
        file.delete();
119+
        if(getFile().exists())
120+
            getFile().delete();
121+
        if(getTemporaryFile().exists())
122+
            getTemporaryFile().delete();
130123
    }
131124
132125
133126
    public int getSize() {
134-
        if(file.exists())
127+
        if(getFile().exists())
135128
            return (int) (getFile().length());
129+
        else if(getTemporaryFile().exists())
130+
            return (int) (getTemporaryFile().length());
136131
        else
137132
            return 0;
138133
    }