emoji table and cache fix, fixed crash
This commit is contained in:
parent
2988726d50
commit
182e69f35f
|
@ -2,7 +2,7 @@ package org.nuclearfog.twidda;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
|
||||||
import org.nuclearfog.twidda.backend.image.ImageCache;
|
import org.nuclearfog.twidda.backend.image.EmojiCache;
|
||||||
import org.nuclearfog.twidda.backend.image.PicassoBuilder;
|
import org.nuclearfog.twidda.backend.image.PicassoBuilder;
|
||||||
import org.nuclearfog.twidda.backend.utils.BlurHashDecoder;
|
import org.nuclearfog.twidda.backend.utils.BlurHashDecoder;
|
||||||
import org.nuclearfog.twidda.config.GlobalSettings;
|
import org.nuclearfog.twidda.config.GlobalSettings;
|
||||||
|
@ -38,7 +38,7 @@ public class ClientApplication extends Application {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLowMemory() {
|
public void onLowMemory() {
|
||||||
ImageCache.clear();
|
EmojiCache.clear();
|
||||||
PicassoBuilder.clear();
|
PicassoBuilder.clear();
|
||||||
BlurHashDecoder.clearCache();
|
BlurHashDecoder.clearCache();
|
||||||
super.onLowMemory();
|
super.onLowMemory();
|
||||||
|
|
|
@ -49,10 +49,14 @@ public class ImageDownloader extends AsyncExecutor<ImageDownloader.ImageLoaderPa
|
||||||
}
|
}
|
||||||
// create file
|
// create file
|
||||||
String ext = '.' + mimeType.substring(mimeType.indexOf('/') + 1);
|
String ext = '.' + mimeType.substring(mimeType.indexOf('/') + 1);
|
||||||
File imageFile = new File(request.cache, StringUtils.getRandomString() + ext);
|
// use deterministic filename depending on the url
|
||||||
imageFile.createNewFile();
|
File imageFile = new File(request.cacheFolder, StringUtils.getMD5signature(request.uri.toString()) + ext);
|
||||||
|
// if file exists with this signature, use this file
|
||||||
|
if (imageFile.exists()) {
|
||||||
|
return new ImageLoaderResult(Uri.fromFile(imageFile), null);
|
||||||
|
}
|
||||||
// copy image to cache folder
|
// copy image to cache folder
|
||||||
|
imageFile.createNewFile();
|
||||||
FileOutputStream output = new FileOutputStream(imageFile);
|
FileOutputStream output = new FileOutputStream(imageFile);
|
||||||
int length;
|
int length;
|
||||||
byte[] buffer = new byte[4096];
|
byte[] buffer = new byte[4096];
|
||||||
|
@ -60,7 +64,6 @@ public class ImageDownloader extends AsyncExecutor<ImageDownloader.ImageLoaderPa
|
||||||
output.write(buffer, 0, length);
|
output.write(buffer, 0, length);
|
||||||
input.close();
|
input.close();
|
||||||
output.close();
|
output.close();
|
||||||
|
|
||||||
// create Uri from cached image
|
// create Uri from cached image
|
||||||
return new ImageLoaderResult(Uri.fromFile(imageFile), null);
|
return new ImageLoaderResult(Uri.fromFile(imageFile), null);
|
||||||
} catch (ConnectionException exception) {
|
} catch (ConnectionException exception) {
|
||||||
|
@ -75,11 +78,11 @@ public class ImageDownloader extends AsyncExecutor<ImageDownloader.ImageLoaderPa
|
||||||
*/
|
*/
|
||||||
public static class ImageLoaderParam {
|
public static class ImageLoaderParam {
|
||||||
|
|
||||||
final File cache;
|
|
||||||
final Uri uri;
|
final Uri uri;
|
||||||
|
final File cacheFolder;
|
||||||
|
|
||||||
public ImageLoaderParam(Uri uri, File cache) {
|
public ImageLoaderParam(Uri uri, File cacheFolder) {
|
||||||
this.cache = cache;
|
this.cacheFolder = cacheFolder;
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import androidx.annotation.Nullable;
|
||||||
import org.nuclearfog.twidda.backend.api.Connection;
|
import org.nuclearfog.twidda.backend.api.Connection;
|
||||||
import org.nuclearfog.twidda.backend.api.ConnectionManager;
|
import org.nuclearfog.twidda.backend.api.ConnectionManager;
|
||||||
import org.nuclearfog.twidda.backend.helper.MediaStatus;
|
import org.nuclearfog.twidda.backend.helper.MediaStatus;
|
||||||
import org.nuclearfog.twidda.backend.image.ImageCache;
|
import org.nuclearfog.twidda.backend.image.EmojiCache;
|
||||||
import org.nuclearfog.twidda.model.Emoji;
|
import org.nuclearfog.twidda.model.Emoji;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -26,14 +26,14 @@ import java.util.TreeMap;
|
||||||
public class TextEmojiLoader extends AsyncExecutor<TextEmojiLoader.EmojiParam, TextEmojiLoader.EmojiResult> {
|
public class TextEmojiLoader extends AsyncExecutor<TextEmojiLoader.EmojiParam, TextEmojiLoader.EmojiResult> {
|
||||||
|
|
||||||
private Connection connection;
|
private Connection connection;
|
||||||
private ImageCache cache;
|
private EmojiCache cache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public TextEmojiLoader(Context context) {
|
public TextEmojiLoader(Context context) {
|
||||||
connection = ConnectionManager.getDefaultConnection(context);
|
connection = ConnectionManager.getDefaultConnection(context);
|
||||||
cache = ImageCache.get(context);
|
cache = EmojiCache.get(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,12 +42,12 @@ public class TextEmojiLoader extends AsyncExecutor<TextEmojiLoader.EmojiParam, T
|
||||||
try {
|
try {
|
||||||
Map<String, Bitmap> result = new TreeMap<>();
|
Map<String, Bitmap> result = new TreeMap<>();
|
||||||
for (Emoji emoji : param.emojis) {
|
for (Emoji emoji : param.emojis) {
|
||||||
Bitmap icon = cache.getImage(emoji.getCode());
|
Bitmap icon = cache.getImage(emoji.getUrl());
|
||||||
if (icon == null) {
|
if (icon == null) {
|
||||||
MediaStatus media = connection.downloadImage(emoji.getUrl());
|
MediaStatus media = connection.downloadImage(emoji.getUrl());
|
||||||
InputStream input = media.getStream();
|
InputStream input = media.getStream();
|
||||||
icon = BitmapFactory.decodeStream(input);
|
icon = BitmapFactory.decodeStream(input);
|
||||||
cache.putImage(emoji.getCode(), icon);
|
cache.putImage(emoji.getUrl(), icon);
|
||||||
}
|
}
|
||||||
if (icon.getHeight() > 0 && icon.getWidth() > 0) {
|
if (icon.getHeight() > 0 && icon.getWidth() > 0) {
|
||||||
icon = Bitmap.createScaledBitmap(icon, icon.getWidth() * param.size / icon.getHeight(), param.size, false);
|
icon = Bitmap.createScaledBitmap(icon, icon.getWidth() * param.size / icon.getHeight(), param.size, false);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.util.LruCache;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.nuclearfog.twidda.BuildConfig;
|
import org.nuclearfog.twidda.BuildConfig;
|
||||||
|
import org.nuclearfog.twidda.backend.utils.StringUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
@ -24,7 +25,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
*
|
*
|
||||||
* @author nuclearfog
|
* @author nuclearfog
|
||||||
*/
|
*/
|
||||||
public class ImageCache {
|
public class EmojiCache {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* size of the lru cache (max entry count)
|
* size of the lru cache (max entry count)
|
||||||
|
@ -41,7 +42,7 @@ public class ImageCache {
|
||||||
*/
|
*/
|
||||||
private static final String FOLDER = "images";
|
private static final String FOLDER = "images";
|
||||||
|
|
||||||
private static ImageCache instance;
|
private static EmojiCache instance;
|
||||||
|
|
||||||
private final LruCache<String, Bitmap> cache;
|
private final LruCache<String, Bitmap> cache;
|
||||||
private Map<String, File> files;
|
private Map<String, File> files;
|
||||||
|
@ -50,7 +51,7 @@ public class ImageCache {
|
||||||
/**
|
/**
|
||||||
* @param context context used to determine cache folder path
|
* @param context context used to determine cache folder path
|
||||||
*/
|
*/
|
||||||
private ImageCache(Context context) {
|
private EmojiCache(Context context) {
|
||||||
files = new ConcurrentHashMap<>();
|
files = new ConcurrentHashMap<>();
|
||||||
cache = new LruCache<>(SIZE);
|
cache = new LruCache<>(SIZE);
|
||||||
try {
|
try {
|
||||||
|
@ -83,9 +84,9 @@ public class ImageCache {
|
||||||
*
|
*
|
||||||
* @return singleton instance of this class
|
* @return singleton instance of this class
|
||||||
*/
|
*/
|
||||||
public static ImageCache get(Context context) {
|
public static EmojiCache get(Context context) {
|
||||||
if (instance == null)
|
if (instance == null)
|
||||||
instance = new ImageCache(context);
|
instance = new EmojiCache(context);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,20 +104,21 @@ public class ImageCache {
|
||||||
/**
|
/**
|
||||||
* put image to cache and save as file if not exists
|
* put image to cache and save as file if not exists
|
||||||
*
|
*
|
||||||
* @param key key of the image (tag)
|
* @param url url of the image
|
||||||
* @param image image bitmap
|
* @param image image bitmap
|
||||||
*/
|
*/
|
||||||
public void putImage(String key, Bitmap image) {
|
public void putImage(String url, Bitmap image) {
|
||||||
synchronized (instance.cache) {
|
synchronized (instance.cache) {
|
||||||
cache.put(key, image);
|
String hash = StringUtils.getMD5signature(url);
|
||||||
if (!files.containsKey(key)) {
|
cache.put(hash, image);
|
||||||
|
if (!files.containsKey(hash)) {
|
||||||
try {
|
try {
|
||||||
File file = new File(imageFolder, key);
|
File file = new File(imageFolder, hash);
|
||||||
if ((file.exists() && file.canWrite()) || file.createNewFile()) {
|
if ((file.exists() && file.canWrite()) || file.createNewFile()) {
|
||||||
FileOutputStream output = new FileOutputStream(file);
|
FileOutputStream output = new FileOutputStream(file);
|
||||||
image.compress(Bitmap.CompressFormat.PNG, 1, output);
|
image.compress(Bitmap.CompressFormat.PNG, 1, output);
|
||||||
output.close();
|
output.close();
|
||||||
files.put(key, file);
|
files.put(hash, file);
|
||||||
}
|
}
|
||||||
} catch (IOException | SecurityException exception) {
|
} catch (IOException | SecurityException exception) {
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
|
@ -130,20 +132,21 @@ public class ImageCache {
|
||||||
/**
|
/**
|
||||||
* get image from cache or file
|
* get image from cache or file
|
||||||
*
|
*
|
||||||
* @param key key of the image (tag)
|
* @param url url of the image
|
||||||
* @return image bitmap or null if not found
|
* @return image bitmap or null if not found
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public Bitmap getImage(String key) {
|
public Bitmap getImage(String url) {
|
||||||
synchronized (instance.cache) {
|
synchronized (instance.cache) {
|
||||||
Bitmap result = cache.get(key);
|
String hash = StringUtils.getMD5signature(url);
|
||||||
|
Bitmap result = cache.get(hash);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
try {
|
try {
|
||||||
File file = files.get(key);
|
File file = files.get(hash);
|
||||||
if (file != null && file.canRead()) {
|
if (file != null && file.canRead()) {
|
||||||
result = BitmapFactory.decodeFile(file.getAbsolutePath());
|
result = BitmapFactory.decodeFile(file.getAbsolutePath());
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
cache.put(key, result);
|
cache.put(hash, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SecurityException exception) {
|
} catch (SecurityException exception) {
|
||||||
|
@ -161,28 +164,30 @@ public class ImageCache {
|
||||||
* and reduce cache size by deleting old image files
|
* and reduce cache size by deleting old image files
|
||||||
*/
|
*/
|
||||||
public void trimCache() {
|
public void trimCache() {
|
||||||
File[] files = imageFolder.listFiles();
|
synchronized (instance.cache) {
|
||||||
if (files != null) {
|
File[] files = imageFolder.listFiles();
|
||||||
long size = 0L;
|
if (files != null) {
|
||||||
for (File file : files) {
|
long size = 0L;
|
||||||
size += file.length();
|
|
||||||
}
|
|
||||||
if (size > CACHE_SIZE) {
|
|
||||||
Arrays.sort(files, new Comparator<File>() {
|
|
||||||
@Override
|
|
||||||
public int compare(File file1, File file2) {
|
|
||||||
if (file1.lastModified() > file2.lastModified())
|
|
||||||
return 1;
|
|
||||||
else if (file1.lastModified() < file2.lastModified())
|
|
||||||
return -1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
size -= file.length();
|
size += file.length();
|
||||||
file.delete();
|
}
|
||||||
if (size < CACHE_SIZE) {
|
if (size > CACHE_SIZE) {
|
||||||
break;
|
Arrays.sort(files, new Comparator<File>() {
|
||||||
|
@Override
|
||||||
|
public int compare(File file1, File file2) {
|
||||||
|
if (file1.lastModified() > file2.lastModified())
|
||||||
|
return 1;
|
||||||
|
else if (file1.lastModified() < file2.lastModified())
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (File file : files) {
|
||||||
|
size -= file.length();
|
||||||
|
file.delete();
|
||||||
|
if (size < CACHE_SIZE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,7 +10,10 @@ import org.nuclearfog.twidda.BuildConfig;
|
||||||
import org.nuclearfog.twidda.R;
|
import org.nuclearfog.twidda.R;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
@ -187,6 +190,27 @@ public class StringUtils {
|
||||||
return new String(Base64.encode(randomBytes, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
|
return new String(Base64.encode(randomBytes, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate MD5 hash from string
|
||||||
|
*
|
||||||
|
* @param text text to hash
|
||||||
|
* @return hash string
|
||||||
|
*/
|
||||||
|
public static String getMD5signature(String text) {
|
||||||
|
try {
|
||||||
|
MessageDigest m = MessageDigest.getInstance("MD5");
|
||||||
|
m.reset();
|
||||||
|
m.update(text.getBytes());
|
||||||
|
byte[] digest = m.digest();
|
||||||
|
BigInteger bigInt = new BigInteger(1, digest);
|
||||||
|
return bigInt.toString(16);
|
||||||
|
} catch (NoSuchAlgorithmException exception) {
|
||||||
|
if (text.length() > 200)
|
||||||
|
return text.substring(0, 200);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* convert to percentage string (RFC 3986)
|
* convert to percentage string (RFC 3986)
|
||||||
*
|
*
|
||||||
|
|
|
@ -988,10 +988,12 @@ public class GlobalSettings {
|
||||||
* notify listener when settings changes and clear old instances
|
* notify listener when settings changes and clear old instances
|
||||||
*/
|
*/
|
||||||
private void notifySettingsChange() {
|
private void notifySettingsChange() {
|
||||||
for (SettingsChangeObserver observer : settingsChangeObservers) {
|
synchronized (this) {
|
||||||
observer.onSettingsChange();
|
for (SettingsChangeObserver observer : settingsChangeObservers) {
|
||||||
|
observer.onSettingsChange();
|
||||||
|
}
|
||||||
|
settingsChangeObservers.clear();
|
||||||
}
|
}
|
||||||
settingsChangeObservers.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,7 +20,7 @@ public class DatabaseAdapter {
|
||||||
/**
|
/**
|
||||||
* database version
|
* database version
|
||||||
*/
|
*/
|
||||||
private static final int DB_VERSION = 19;
|
private static final int DB_VERSION = 20;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* database file name
|
* database file name
|
||||||
|
@ -137,9 +137,9 @@ public class DatabaseAdapter {
|
||||||
*/
|
*/
|
||||||
private static final String TABLE_EMOJI = "CREATE TABLE IF NOT EXISTS "
|
private static final String TABLE_EMOJI = "CREATE TABLE IF NOT EXISTS "
|
||||||
+ EmojiTable.NAME + "("
|
+ EmojiTable.NAME + "("
|
||||||
+ EmojiTable.CODE + " TEXT PRIMARY KEY,"
|
+ EmojiTable.URL + " TEXT PRIMARY KEY,"
|
||||||
+ EmojiTable.CATEGORY + " TEXT,"
|
+ EmojiTable.CATEGORY + " TEXT,"
|
||||||
+ EmojiTable.URL + " TEXT);";
|
+ EmojiTable.CODE + " TEXT);";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQL query to create a poll table
|
* SQL query to create a poll table
|
||||||
|
@ -412,8 +412,13 @@ public class DatabaseAdapter {
|
||||||
db.execSQL(UPDATE_MEDIA_ADD_DESCRIPTION);
|
db.execSQL(UPDATE_MEDIA_ADD_DESCRIPTION);
|
||||||
db.setVersion(18);
|
db.setVersion(18);
|
||||||
}
|
}
|
||||||
if (db.getVersion() < DB_VERSION) {
|
if (db.getVersion() < 19) {
|
||||||
db.execSQL(UPDATE_MEDIA_ADD_BLUR_HASH);
|
db.execSQL(UPDATE_MEDIA_ADD_BLUR_HASH);
|
||||||
|
db.setVersion(19);
|
||||||
|
}
|
||||||
|
if (db.getVersion() < DB_VERSION) {
|
||||||
|
db.delete(EmojiTable.NAME, null, null);
|
||||||
|
db.execSQL(TABLE_EMOJI);
|
||||||
db.setVersion(DB_VERSION);
|
db.setVersion(DB_VERSION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue