diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/GsonConfigParser.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/GsonConfigParser.java new file mode 100644 index 00000000..76ffdbac --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/GsonConfigParser.java @@ -0,0 +1,37 @@ +package com.panda3ds.pandroid.data; + +import com.google.gson.Gson; +import com.panda3ds.pandroid.lang.Task; +import com.panda3ds.pandroid.utils.FileUtils; + +public class GsonConfigParser { + private final Gson gson = new Gson(); + private final String name; + + public GsonConfigParser(String name){ + this.name = name; + } + + private String getPath(){ + return FileUtils.getConfigPath()+"/"+name+".json"; + } + + public void save(Object data){ + synchronized (this) { + new Task(() -> { + String json = gson.toJson(data); + FileUtils.writeTextFile(FileUtils.getConfigPath(), name + ".json", json); + }).runSync(); + } + } + + public T load(Class clazz){ + String[] content = new String[]{"{}"}; + new Task(()->{ + if (FileUtils.exists(getPath())){ + content[0] = FileUtils.readTextFile(getPath()); + } + }).runSync(); + return gson.fromJson(content[0], clazz); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java index 127f2ffa..0ee41dde 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java @@ -1,58 +1,56 @@ package com.panda3ds.pandroid.data.config; -import android.content.Context; -import android.content.SharedPreferences; - -import com.panda3ds.pandroid.app.PandroidApplication; +import com.panda3ds.pandroid.data.GsonConfigParser; import com.panda3ds.pandroid.utils.Constants; import java.io.Serializable; +import java.util.HashMap; public class GlobalConfig { - private static SharedPreferences data; + + private static final GsonConfigParser parser = new GsonConfigParser(Constants.PREF_GLOBAL_CONFIG); public static final int VALUE_THEME_ANDROID = 0; public static final int VALUE_THEME_LIGHT = 1; public static final int VALUE_THEME_DARK = 2; public static final int VALUE_THEME_BLACK = 3; + public static DataModel data; + public static final Key KEY_APP_THEME = new Key<>("app.theme", VALUE_THEME_ANDROID); public static void initialize() { - data = PandroidApplication.getAppContext() - .getSharedPreferences(Constants.PREF_GLOBAL_CONFIG, Context.MODE_PRIVATE); + data = parser.load(DataModel.class); } public static T get(Key key) { Serializable value; + + if (!data.configs.containsKey(key.name)){ + return key.defaultValue; + } + if (key.defaultValue instanceof String) { - value = data.getString(key.name, (String) key.defaultValue); + value = (String) data.configs.get(key.name); } else if (key.defaultValue instanceof Integer) { - value = data.getInt(key.name, (int) key.defaultValue); + value = ((Number) data.get(key.name)).intValue(); } else if (key.defaultValue instanceof Boolean) { - value = data.getBoolean(key.name, (boolean) key.defaultValue); + value = (boolean) data.get(key.name); } else if (key.defaultValue instanceof Long) { - value = data.getLong(key.name, (long) key.defaultValue); + value = ((Number) data.get(key.name)).longValue(); } else { - value = data.getFloat(key.name, (float) key.defaultValue); + value = ((Number) data.get(key.name)).floatValue(); } return (T) value; } public static synchronized void set(Key key, T value) { - if (value instanceof String) { - data.edit().putString(key.name, (String) value).apply(); - } else if (value instanceof Integer) { - data.edit().putInt(key.name, (int) value).apply(); - } else if (value instanceof Boolean) { - data.edit().putBoolean(key.name, (boolean) value).apply(); - } else if (value instanceof Long) { - data.edit().putLong(key.name, (long) value).apply(); - } else if (value instanceof Float) { - data.edit().putFloat(key.name, (float) value).apply(); - } else { - throw new IllegalArgumentException("Invalid global config value instance"); - } + data.configs.put(key.name, value); + writeChanges(); + } + + private static void writeChanges(){ + parser.save(data); } private static class Key { @@ -64,4 +62,12 @@ public class GlobalConfig { this.defaultValue = defaultValue; } } + + private static class DataModel { + private final HashMap configs = new HashMap<>(); + + public Object get(String key){ + return configs.get(key); + } + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputMap.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputMap.java index b94ceaac..ab2cdd04 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputMap.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputMap.java @@ -1,30 +1,27 @@ package com.panda3ds.pandroid.input; -import android.content.Context; -import android.content.SharedPreferences; - -import com.panda3ds.pandroid.app.PandroidApplication; +import com.panda3ds.pandroid.data.GsonConfigParser; import com.panda3ds.pandroid.utils.Constants; public class InputMap { - - private static SharedPreferences data; - private static final String KEY_DEAD_ZONE = "deadZone"; + public static final GsonConfigParser parser = new GsonConfigParser(Constants.PREF_INPUT_MAP); + private static DataModel data; public static void initialize() { - data = PandroidApplication.getAppContext().getSharedPreferences(Constants.PREF_INPUT_MAP, Context.MODE_PRIVATE); + data = parser.load(DataModel.class); } public static float getDeadZone() { - return data.getFloat(KEY_DEAD_ZONE, 0.2f); + return data.deadZone; } public static void set(KeyName key, String name) { - data.edit().putString(key.name(), name).apply(); + data.keys[key.ordinal()] = name; + writeConfig(); } public static String relative(KeyName key) { - return data.getString(key.name(), "-"); + return data.keys[key.ordinal()] == null ? "-" : data.keys[key.ordinal()]; } public static KeyName relative(String name) { @@ -36,7 +33,16 @@ public class InputMap { } public static void setDeadZone(float value) { - data.edit().putFloat(KEY_DEAD_ZONE, Math.max(0.0f, Math.min(1.0f, value))).apply(); + data.deadZone = Math.max(0.0f, Math.min(1.0f, value)); + writeConfig(); } + private static void writeConfig() { + parser.save(data); + } + + private static class DataModel { + public float deadZone = 0.2f; + public final String[] keys = new String[32]; + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java new file mode 100644 index 00000000..1b76c757 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java @@ -0,0 +1,20 @@ +package com.panda3ds.pandroid.lang; + +public class Task extends Thread { + public Task(Runnable runnable){ + super(runnable); + } + + + public void runSync(){ + start(); + waitFinish(); + } + public void waitFinish(){ + try { + join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java index 7e926eb3..1f0cff07 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java @@ -3,20 +3,24 @@ package com.panda3ds.pandroid.utils; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.util.Log; import androidx.documentfile.provider.DocumentFile; import com.panda3ds.pandroid.app.PandroidApplication; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; public class FileUtils { public static final String MODE_READ = "r"; - private static DocumentFile parseFile(String path){ - if (path.startsWith("/")){ + + private static DocumentFile parseFile(String path) { + if (path.startsWith("/")) { return DocumentFile.fromFile(new File(path)); } Uri uri = Uri.parse(path); @@ -31,32 +35,81 @@ public class FileUtils { return parseFile(path).getName(); } - public static String getPrivatePath(){ + public static String getPrivatePath() { File file = getContext().getFilesDir(); - if (!file.exists()){ + if (!file.exists()) { file.mkdirs(); } return file.getAbsolutePath(); } - public static boolean exists(String path){ + public static String getConfigPath() { + File file = new File(getPrivatePath(), "config"); + if (!file.exists()) { + file.mkdirs(); + } + return file.getAbsolutePath(); + } + + public static boolean exists(String path) { return parseFile(path).exists(); } - public static boolean createDir(String path, String name){ + public static boolean createDir(String path, String name) { DocumentFile folder = parseFile(path); if (folder.findFile(name) != null) return true; return folder.createDirectory(name) != null; } - public static boolean createFile(String path, String name){ + public static boolean createFile(String path, String name) { DocumentFile folder = parseFile(path); if (folder.findFile(name) != null) return true; return folder.createFile("", name) != null; } + public static boolean writeTextFile(String path, String name, String content) { + try { + if (!exists(path + "/" + name)) { + createFile(path, name); + } + OutputStream stream = getOutputStream(path + "/" + name); + stream.write(content.getBytes(StandardCharsets.UTF_8)); + stream.flush(); + stream.close(); + } catch (Exception e) { + Log.e(Constants.LOG_TAG, "Error on write text file: ", e); + return false; + } + return true; + } + + public static String readTextFile(String path) { + if (!exists(path)) + return null; + + try { + InputStream stream = getInputStream(path); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + int len; + byte[] buffer = new byte[1024 * 8]; + while ((len = stream.read(buffer)) != -1) + output.write(buffer, 0, len); + + stream.close(); + output.flush(); + output.close(); + + byte[] data = output.toByteArray(); + + return new String(data, 0, data.length); + } catch (Exception e) { + return null; + } + } + public static InputStream getInputStream(String path) throws FileNotFoundException { return getContext().getContentResolver().openInputStream(parseFile(path).getUri()); } @@ -66,7 +119,6 @@ public class FileUtils { } public static void makeUriPermanent(String uri, String mode) { - int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION; if (mode.toLowerCase().contains("w")) flags &= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java index 32f7c2a5..7fe111d7 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java @@ -2,46 +2,34 @@ package com.panda3ds.pandroid.utils; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.util.Log; -import com.google.gson.Gson; import com.panda3ds.pandroid.app.GameActivity; -import com.panda3ds.pandroid.app.PandroidApplication; +import com.panda3ds.pandroid.data.GsonConfigParser; import com.panda3ds.pandroid.data.game.GameMetadata; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.Objects; public class GameUtils { - private static final Bitmap DEFAULT_ICON = Bitmap.createBitmap(48,48, Bitmap.Config.ARGB_8888); - private static final String KEY_GAME_LIST = "gameList"; - private static final ArrayList games = new ArrayList<>(); - private static SharedPreferences data; - private static final Gson gson = new Gson(); + private static final Bitmap DEFAULT_ICON = Bitmap.createBitmap(48, 48, Bitmap.Config.ARGB_8888); + public static GsonConfigParser parser = new GsonConfigParser(Constants.PREF_GAME_UTILS); + + private static DataModel data; private static GameMetadata currentGame; public static void initialize() { - data = PandroidApplication.getAppContext().getSharedPreferences(Constants.PREF_GAME_UTILS, Context.MODE_PRIVATE); - - GameMetadata[] list = gson.fromJson(data.getString(KEY_GAME_LIST, "[]"), GameMetadata[].class); - - for (GameMetadata game: list) - game.getIcon(); - - games.clear(); - games.addAll(Arrays.asList(list)); + data = parser.load(DataModel.class); } public static GameMetadata findByRomPath(String romPath) { - for (GameMetadata game : games) { + for (GameMetadata game : data.games) { if (Objects.equals(romPath, game.getRomPath())) { return game; } @@ -60,51 +48,53 @@ public class GameUtils { } public static void removeGame(GameMetadata game) { - games.remove(game); - saveAll(); + data.games.remove(game); + writeChanges(); } public static void addGame(GameMetadata game) { - games.add(0,game); - saveAll(); + data.games.add(0, game); + writeChanges(); } public static ArrayList getGames() { - return new ArrayList<>(games); + return new ArrayList<>(data.games); } - private static synchronized void saveAll() { - data.edit() - .putString(KEY_GAME_LIST, gson.toJson(games.toArray(new GameMetadata[0]))) - .apply(); + private static void writeChanges() { + parser.save(data); } public static void setGameIcon(String id, Bitmap icon) { try { String appPath = FileUtils.getPrivatePath(); FileUtils.createDir(appPath, "cache_icons"); - FileUtils.createFile(appPath+"/cache_icons/", id+".png"); + FileUtils.createFile(appPath + "/cache_icons/", id + ".png"); - OutputStream output = FileUtils.getOutputStream(appPath+"/cache_icons/"+id+".png"); + OutputStream output = FileUtils.getOutputStream(appPath + "/cache_icons/" + id + ".png"); icon.compress(Bitmap.CompressFormat.PNG, 100, output); output.close(); - } catch (Exception e){ + } catch (Exception e) { Log.e(Constants.LOG_TAG, "Error on save game icon: ", e); } } public static Bitmap loadGameIcon(String id) { try { - String path = FileUtils.getPrivatePath()+"/cache_icons/"+id+".png"; + String path = FileUtils.getPrivatePath() + "/cache_icons/" + id + ".png"; if (FileUtils.exists(path)) { InputStream stream = FileUtils.getInputStream(path); Bitmap image = BitmapFactory.decodeStream(stream); stream.close(); return image; } - } catch (Exception e){ + } catch (Exception e) { Log.e(Constants.LOG_TAG, "Error on load game icon: ", e); } return DEFAULT_ICON; } + + private static class DataModel { + public final ArrayList games = new ArrayList<>(); + } }