emoji table and cache fix, fixed crash

This commit is contained in:
nuclearfog 2023-08-19 18:33:32 +02:00
parent 2988726d50
commit 182e69f35f
No known key found for this signature in database
GPG Key ID: 03488A185C476379
7 changed files with 97 additions and 58 deletions

View File

@ -2,7 +2,7 @@ package org.nuclearfog.twidda;
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.utils.BlurHashDecoder;
import org.nuclearfog.twidda.config.GlobalSettings;
@ -38,7 +38,7 @@ public class ClientApplication extends Application {
@Override
public void onLowMemory() {
ImageCache.clear();
EmojiCache.clear();
PicassoBuilder.clear();
BlurHashDecoder.clearCache();
super.onLowMemory();

View File

@ -49,10 +49,14 @@ public class ImageDownloader extends AsyncExecutor<ImageDownloader.ImageLoaderPa
}
// create file
String ext = '.' + mimeType.substring(mimeType.indexOf('/') + 1);
File imageFile = new File(request.cache, StringUtils.getRandomString() + ext);
imageFile.createNewFile();
// use deterministic filename depending on the url
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
imageFile.createNewFile();
FileOutputStream output = new FileOutputStream(imageFile);
int length;
byte[] buffer = new byte[4096];
@ -60,7 +64,6 @@ public class ImageDownloader extends AsyncExecutor<ImageDownloader.ImageLoaderPa
output.write(buffer, 0, length);
input.close();
output.close();
// create Uri from cached image
return new ImageLoaderResult(Uri.fromFile(imageFile), null);
} catch (ConnectionException exception) {
@ -75,11 +78,11 @@ public class ImageDownloader extends AsyncExecutor<ImageDownloader.ImageLoaderPa
*/
public static class ImageLoaderParam {
final File cache;
final Uri uri;
final File cacheFolder;
public ImageLoaderParam(Uri uri, File cache) {
this.cache = cache;
public ImageLoaderParam(Uri uri, File cacheFolder) {
this.cacheFolder = cacheFolder;
this.uri = uri;
}
}

View File

@ -11,7 +11,7 @@ import androidx.annotation.Nullable;
import org.nuclearfog.twidda.backend.api.Connection;
import org.nuclearfog.twidda.backend.api.ConnectionManager;
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 java.io.InputStream;
@ -26,14 +26,14 @@ import java.util.TreeMap;
public class TextEmojiLoader extends AsyncExecutor<TextEmojiLoader.EmojiParam, TextEmojiLoader.EmojiResult> {
private Connection connection;
private ImageCache cache;
private EmojiCache cache;
/**
*
*/
public TextEmojiLoader(Context 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 {
Map<String, Bitmap> result = new TreeMap<>();
for (Emoji emoji : param.emojis) {
Bitmap icon = cache.getImage(emoji.getCode());
Bitmap icon = cache.getImage(emoji.getUrl());
if (icon == null) {
MediaStatus media = connection.downloadImage(emoji.getUrl());
InputStream input = media.getStream();
icon = BitmapFactory.decodeStream(input);
cache.putImage(emoji.getCode(), icon);
cache.putImage(emoji.getUrl(), icon);
}
if (icon.getHeight() > 0 && icon.getWidth() > 0) {
icon = Bitmap.createScaledBitmap(icon, icon.getWidth() * param.size / icon.getHeight(), param.size, false);

View File

@ -8,6 +8,7 @@ import android.util.LruCache;
import androidx.annotation.Nullable;
import org.nuclearfog.twidda.BuildConfig;
import org.nuclearfog.twidda.backend.utils.StringUtils;
import java.io.File;
import java.io.FileOutputStream;
@ -24,7 +25,7 @@ import java.util.concurrent.ConcurrentHashMap;
*
* @author nuclearfog
*/
public class ImageCache {
public class EmojiCache {
/**
* size of the lru cache (max entry count)
@ -41,7 +42,7 @@ public class ImageCache {
*/
private static final String FOLDER = "images";
private static ImageCache instance;
private static EmojiCache instance;
private final LruCache<String, Bitmap> cache;
private Map<String, File> files;
@ -50,7 +51,7 @@ public class ImageCache {
/**
* @param context context used to determine cache folder path
*/
private ImageCache(Context context) {
private EmojiCache(Context context) {
files = new ConcurrentHashMap<>();
cache = new LruCache<>(SIZE);
try {
@ -83,9 +84,9 @@ public class ImageCache {
*
* @return singleton instance of this class
*/
public static ImageCache get(Context context) {
public static EmojiCache get(Context context) {
if (instance == null)
instance = new ImageCache(context);
instance = new EmojiCache(context);
return instance;
}
@ -103,20 +104,21 @@ public class ImageCache {
/**
* 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
*/
public void putImage(String key, Bitmap image) {
public void putImage(String url, Bitmap image) {
synchronized (instance.cache) {
cache.put(key, image);
if (!files.containsKey(key)) {
String hash = StringUtils.getMD5signature(url);
cache.put(hash, image);
if (!files.containsKey(hash)) {
try {
File file = new File(imageFolder, key);
File file = new File(imageFolder, hash);
if ((file.exists() && file.canWrite()) || file.createNewFile()) {
FileOutputStream output = new FileOutputStream(file);
image.compress(Bitmap.CompressFormat.PNG, 1, output);
output.close();
files.put(key, file);
files.put(hash, file);
}
} catch (IOException | SecurityException exception) {
if (BuildConfig.DEBUG) {
@ -130,20 +132,21 @@ public class ImageCache {
/**
* 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
*/
@Nullable
public Bitmap getImage(String key) {
public Bitmap getImage(String url) {
synchronized (instance.cache) {
Bitmap result = cache.get(key);
String hash = StringUtils.getMD5signature(url);
Bitmap result = cache.get(hash);
if (result == null) {
try {
File file = files.get(key);
File file = files.get(hash);
if (file != null && file.canRead()) {
result = BitmapFactory.decodeFile(file.getAbsolutePath());
if (result != null) {
cache.put(key, result);
cache.put(hash, result);
}
}
} catch (SecurityException exception) {
@ -161,28 +164,30 @@ public class ImageCache {
* and reduce cache size by deleting old image files
*/
public void trimCache() {
File[] files = imageFolder.listFiles();
if (files != null) {
long size = 0L;
for (File file : files) {
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;
}
});
synchronized (instance.cache) {
File[] files = imageFolder.listFiles();
if (files != null) {
long size = 0L;
for (File file : files) {
size -= file.length();
file.delete();
if (size < CACHE_SIZE) {
break;
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) {
size -= file.length();
file.delete();
if (size < CACHE_SIZE) {
break;
}
}
}
}

View File

@ -10,7 +10,10 @@ import org.nuclearfog.twidda.BuildConfig;
import org.nuclearfog.twidda.R;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.NumberFormat;
import java.text.ParseException;
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));
}
/**
* 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)
*

View File

@ -988,10 +988,12 @@ public class GlobalSettings {
* notify listener when settings changes and clear old instances
*/
private void notifySettingsChange() {
for (SettingsChangeObserver observer : settingsChangeObservers) {
observer.onSettingsChange();
synchronized (this) {
for (SettingsChangeObserver observer : settingsChangeObservers) {
observer.onSettingsChange();
}
settingsChangeObservers.clear();
}
settingsChangeObservers.clear();
}
/**

View File

@ -20,7 +20,7 @@ public class DatabaseAdapter {
/**
* database version
*/
private static final int DB_VERSION = 19;
private static final int DB_VERSION = 20;
/**
* database file name
@ -137,9 +137,9 @@ public class DatabaseAdapter {
*/
private static final String TABLE_EMOJI = "CREATE TABLE IF NOT EXISTS "
+ EmojiTable.NAME + "("
+ EmojiTable.CODE + " TEXT PRIMARY KEY,"
+ EmojiTable.URL + " TEXT PRIMARY KEY,"
+ EmojiTable.CATEGORY + " TEXT,"
+ EmojiTable.URL + " TEXT);";
+ EmojiTable.CODE + " TEXT);";
/**
* SQL query to create a poll table
@ -412,8 +412,13 @@ public class DatabaseAdapter {
db.execSQL(UPDATE_MEDIA_ADD_DESCRIPTION);
db.setVersion(18);
}
if (db.getVersion() < DB_VERSION) {
if (db.getVersion() < 19) {
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);
}
}