Skip to content
Snippets Groups Projects
Commit 8280f52f authored by Torsten Grote's avatar Torsten Grote
Browse files

Merge branch 'mirror_cache_exception' into 'master'

added better handling for unexpected error count values

Closes acra-crash-reports#808 and acra-crash-reports#811

See merge request !1526
parents a87acf3e 6fc68d2e
No related branches found
No related tags found
No related merge requests found
......@@ -594,22 +594,46 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
return output;
}
private String intMapToString(HashMap<String, Integer> intMap) {
private String intMapToString(Map<String, Integer> intMap) {
String output = "";
for (String key : intMap.keySet()) {
Integer value = intMap.get(key);
if (key == null || key.isEmpty()) {
Utils.debugLog(TAG, "Don't serialize record with null key");
} else if (value == null) {
Utils.debugLog(TAG, "Don't serialize null value for: " + key);
} else {
if (!output.isEmpty()) {
output = output + "\n";
}
output = output + key + " " + intMap.get(key);
output = output + key + " " + value;
}
}
return output;
}
private HashMap<String, Integer> stringToIntMap(String mapString) {
private Map<String, Integer> stringToIntMap(String mapString) {
HashMap<String, Integer> output = new HashMap<String, Integer>();
for (String line : mapString.split("\n")) {
String[] pair = line.split(" ");
output.put(pair[0], Integer.valueOf(pair[1]));
// values may be missing or unparseable
String key = pair[0];
Integer value = 0;
if (key != null && !key.isEmpty()) {
if (pair.length > 1) {
try {
value = Integer.valueOf(pair[1]);
} catch (NumberFormatException e) {
// use default value if stored value can't be parsed
Utils.debugLog(TAG, "Serialized map entry value can't be parsed: " + line);
}
} else {
Utils.debugLog(TAG, "Serialized map entry value is missing: " + line);
}
output.put(key, value);
} else {
Utils.debugLog(TAG, "Serialized map entry key is missing: " + line);
}
}
return output;
}
......@@ -622,12 +646,12 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
return preferences.getBoolean(PREF_PREFER_FOREIGN, false);
}
public void setMirrorErrorData(HashMap<String, Integer> mirrorErrorMap) {
public void setMirrorErrorData(Map<String, Integer> mirrorErrorMap) {
preferences.edit().putString(PREF_MIRROR_ERROR_DATA, intMapToString(mirrorErrorMap)).apply();
}
public HashMap<String, Integer> getMirrorErrorData() {
HashMap<String, Integer> mirrorDataMap = new HashMap<String, Integer>();
public Map<String, Integer> getMirrorErrorData() {
Map<String, Integer> mirrorDataMap = new HashMap<String, Integer>();
String mapString = preferences.getString(PREF_MIRROR_ERROR_DATA, "");
if (mapString == null || mapString.isEmpty()) {
// no-op, return empty map to avoid null issues
......
......@@ -11,35 +11,39 @@ import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.data.App;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class FDroidMirrorParameterManager implements MirrorParameterManager {
private volatile HashMap<String, Integer> errorCache;
private static final int DELAY_TIME = 1;
private final ConcurrentHashMap<String, Integer> errorCache;
private static final int DELAY_TIME = 5;
private static final TimeUnit DELAY_UNIT = TimeUnit.SECONDS;
private volatile boolean writeErrorScheduled = false;
private final Runnable delayedErrorWrite = () -> {
Preferences prefs = Preferences.get();
prefs.setMirrorErrorData(errorCache);
writeErrorScheduled = false;
};
private final AtomicBoolean writeErrorScheduled = new AtomicBoolean(false);
private final Runnable delayedErrorWrite;
private final ScheduledExecutorService writeErrorExecutor = Executors.newSingleThreadScheduledExecutor();
public FDroidMirrorParameterManager() {
Preferences prefs = Preferences.get();
errorCache = prefs.getMirrorErrorData();
errorCache = new ConcurrentHashMap<String, Integer>(prefs.getMirrorErrorData());
delayedErrorWrite = () -> {
if (writeErrorScheduled.compareAndSet(true, false)) {
Map<String, Integer> snapshot = Collections.unmodifiableMap(new HashMap<String, Integer>(errorCache));
Preferences writePrefs = Preferences.get();
writePrefs.setMirrorErrorData(snapshot);
}
};
}
public void updateErrorCacheAndPrefs(@NonNull String url, @NonNull Integer errorCount) {
errorCache.put(url, errorCount);
if (!writeErrorScheduled) {
writeErrorScheduled = true;
if (writeErrorScheduled.compareAndSet(false, true)) {
writeErrorExecutor.schedule(delayedErrorWrite, DELAY_TIME, DELAY_UNIT);
}
}
......
......@@ -20,6 +20,7 @@ package org.fdroid.fdroid;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.content.Context;
......@@ -35,6 +36,7 @@ import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.shadows.ShadowLog;
import java.util.HashMap;
import java.util.Map;
@RunWith(RobolectricTestRunner.class)
......@@ -157,4 +159,16 @@ public class PreferencesTest {
assertNotEquals(Long.parseLong(defaults.getString(Preferences.PREF_KEEP_CACHE_TIME, null)),
Preferences.get().getKeepCacheTime());
}
@Test
public void testMirrorErrorMethods() {
Preferences.setupForTests(CONTEXT);
Preferences preferences = Preferences.get();
// serialize an empty map
preferences.setMirrorErrorData(new HashMap<>(0));
Map<String, Integer> result = preferences.getMirrorErrorData();
// deserializing should return an empty map without throwing any exceptions
assertNotNull(result);
assertEquals(result.size(), 0);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment