diff --git a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseSiteManager.java b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseSiteManager.java index a9a4452a..4c467397 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseSiteManager.java +++ b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseSiteManager.java @@ -32,13 +32,12 @@ public class DatabaseSiteManager { this.helper = helper; } + public Callable byId(int id) { + return () -> helper.siteDao.queryForId(id); + } + public Callable> getAll() { - return new Callable>() { - @Override - public List call() throws Exception { - return helper.siteDao.queryForAll(); - } - }; + return () -> helper.siteDao.queryForAll(); } public Callable getCount() { @@ -46,35 +45,23 @@ public class DatabaseSiteManager { } public Callable add(final SiteModel site) { - return new Callable() { - @Override - public SiteModel call() throws Exception { - helper.siteDao.create(site); - - return site; - } + return () -> { + helper.siteDao.create(site); + return site; }; } public Callable update(final SiteModel site) { - return new Callable() { - @Override - public SiteModel call() throws Exception { - helper.siteDao.update(site); - - return site; - } + return () -> { + helper.siteDao.update(site); + return site; }; } public Callable updateId(final SiteModel site, final int newId) { - return new Callable() { - @Override - public SiteModel call() throws Exception { - helper.siteDao.updateId(site, newId); - - return site; - } + return () -> { + helper.siteDao.updateId(site, newId); + return site; }; } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/orm/SiteModel.java b/Clover/app/src/main/java/org/floens/chan/core/model/orm/SiteModel.java index b45b44ef..cf9a256b 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/orm/SiteModel.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/orm/SiteModel.java @@ -20,15 +20,35 @@ package org.floens.chan.core.model.orm; import android.util.Pair; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; +import org.floens.chan.core.settings.json.BooleanJsonSetting; +import org.floens.chan.core.settings.json.LongJsonSetting; +import org.floens.chan.core.settings.json.RuntimeTypeAdapterFactory; +import org.floens.chan.core.settings.json.IntegerJsonSetting; import org.floens.chan.core.model.json.site.SiteConfig; -import org.floens.chan.core.model.json.site.SiteUserSettings; +import org.floens.chan.core.settings.json.JsonSetting; +import org.floens.chan.core.settings.json.JsonSettings; +import org.floens.chan.core.settings.json.StringJsonSetting; @DatabaseTable(tableName = "site") public class SiteModel { - private static final Gson gson = new Gson(); + private static final Gson gson; + + static { + RuntimeTypeAdapterFactory userSettingAdapter = + RuntimeTypeAdapterFactory.of(JsonSetting.class, "type") + .registerSubtype(StringJsonSetting.class, "string") + .registerSubtype(IntegerJsonSetting.class, "integer") + .registerSubtype(LongJsonSetting.class, "long") + .registerSubtype(BooleanJsonSetting.class, "boolean"); + + gson = new GsonBuilder() + .registerTypeAdapterFactory(userSettingAdapter) + .create(); + } @DatabaseField(generatedId = true, allowGeneratedIdInsert = true) public int id; @@ -42,15 +62,18 @@ public class SiteModel { public SiteModel() { } - public void storeConfigFields(SiteConfig config, SiteUserSettings userSettings) { + public void storeConfig(SiteConfig config) { this.configuration = gson.toJson(config); + } + + public void storeUserSettings(JsonSettings userSettings) { this.userSettings = gson.toJson(userSettings); } - public Pair loadConfigFields() { + public Pair loadConfigFields() { return Pair.create( gson.fromJson(this.configuration, SiteConfig.class), - gson.fromJson(this.userSettings, SiteUserSettings.class) + gson.fromJson(this.userSettings, JsonSettings.class) ); } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/BooleanSetting.java b/Clover/app/src/main/java/org/floens/chan/core/settings/BooleanSetting.java index 76d970a7..784dbcec 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/BooleanSetting.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/BooleanSetting.java @@ -17,14 +17,12 @@ */ package org.floens.chan.core.settings; -import android.content.SharedPreferences; - public class BooleanSetting extends Setting { private boolean hasCached = false; private boolean cached; - public BooleanSetting(SharedPreferences sharedPreferences, String key, Boolean def) { - super(sharedPreferences, key, def); + public BooleanSetting(SettingProvider settingProvider, String key, Boolean def) { + super(settingProvider, key, def); } @Override @@ -32,7 +30,7 @@ public class BooleanSetting extends Setting { if (hasCached) { return cached; } else { - cached = sharedPreferences.getBoolean(key, def); + cached = settingProvider.getBoolean(key, def); hasCached = true; return cached; } @@ -41,7 +39,7 @@ public class BooleanSetting extends Setting { @Override public void set(Boolean value) { if (!value.equals(get())) { - sharedPreferences.edit().putBoolean(key, value).apply(); + settingProvider.putBoolean(key, value); cached = value; onValueChanged(); } diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java index 3658a2d7..44ec5928 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java @@ -17,7 +17,6 @@ */ package org.floens.chan.core.settings; -import android.content.SharedPreferences; import android.os.Environment; import android.text.TextUtils; @@ -159,7 +158,7 @@ public class ChanSettings { public static final BooleanSetting crashReporting; static { - SharedPreferences p = AndroidUtils.getPreferences(); + SettingProvider p = new SharedPreferencesSettingProvider(AndroidUtils.getPreferences()); theme = new StringSetting(p, "preference_theme", "yotsuba"); diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/CounterSetting.java b/Clover/app/src/main/java/org/floens/chan/core/settings/CounterSetting.java index 840f403f..90dcdbb4 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/CounterSetting.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/CounterSetting.java @@ -17,11 +17,9 @@ */ package org.floens.chan.core.settings; -import android.content.SharedPreferences; - public class CounterSetting extends IntegerSetting { - public CounterSetting(SharedPreferences sharedPreferences, String key) { - super(sharedPreferences, key, 0); + public CounterSetting(SettingProvider settingProvider, String key) { + super(settingProvider, key, 0); } public int increase() { diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/IntegerSetting.java b/Clover/app/src/main/java/org/floens/chan/core/settings/IntegerSetting.java index 062b9340..3350b16a 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/IntegerSetting.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/IntegerSetting.java @@ -17,14 +17,12 @@ */ package org.floens.chan.core.settings; -import android.content.SharedPreferences; - public class IntegerSetting extends Setting { private boolean hasCached = false; private Integer cached; - public IntegerSetting(SharedPreferences sharedPreferences, String key, Integer def) { - super(sharedPreferences, key, def); + public IntegerSetting(SettingProvider settingProvider, String key, Integer def) { + super(settingProvider, key, def); } @Override @@ -32,7 +30,7 @@ public class IntegerSetting extends Setting { if (hasCached) { return cached; } else { - cached = sharedPreferences.getInt(key, def); + cached = settingProvider.getInt(key, def); hasCached = true; return cached; } @@ -41,7 +39,7 @@ public class IntegerSetting extends Setting { @Override public void set(Integer value) { if (!value.equals(get())) { - sharedPreferences.edit().putInt(key, value).apply(); + settingProvider.putInt(key, value); cached = value; onValueChanged(); } diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/LongSetting.java b/Clover/app/src/main/java/org/floens/chan/core/settings/LongSetting.java index c68b88d8..b3af5284 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/LongSetting.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/LongSetting.java @@ -17,14 +17,12 @@ */ package org.floens.chan.core.settings; -import android.content.SharedPreferences; - public class LongSetting extends Setting { private boolean hasCached = false; private Long cached; - public LongSetting(SharedPreferences sharedPreferences, String key, Long def) { - super(sharedPreferences, key, def); + public LongSetting(SettingProvider settingProvider, String key, Long def) { + super(settingProvider, key, def); } @Override @@ -32,7 +30,7 @@ public class LongSetting extends Setting { if (hasCached) { return cached; } else { - cached = sharedPreferences.getLong(key, def); + cached = settingProvider.getLong(key, def); hasCached = true; return cached; } @@ -41,7 +39,7 @@ public class LongSetting extends Setting { @Override public void set(Long value) { if (!value.equals(get())) { - sharedPreferences.edit().putLong(key, value).apply(); + settingProvider.putLong(key, value); cached = value; onValueChanged(); } diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/OptionsSetting.java b/Clover/app/src/main/java/org/floens/chan/core/settings/OptionsSetting.java index c8ca49d5..8814d381 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/OptionsSetting.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/OptionsSetting.java @@ -17,15 +17,13 @@ */ package org.floens.chan.core.settings; -import android.content.SharedPreferences; - public class OptionsSetting extends Setting { private boolean hasCached = false; private T cached; private T[] items; - public OptionsSetting(SharedPreferences sharedPreferences, String key, T[] items, T def) { - super(sharedPreferences, key, def); + public OptionsSetting(SettingProvider settingProvider, String key, T[] items, T def) { + super(settingProvider, key, def); this.items = items; } @@ -34,7 +32,7 @@ public class OptionsSetting extends Setting { if (hasCached) { return cached; } else { - String itemName = sharedPreferences.getString(key, def.getName()); + String itemName = settingProvider.getString(key, def.getName()); T selectedItem = null; for (T item : items) { if (item.getName().equals(itemName)) { @@ -54,7 +52,7 @@ public class OptionsSetting extends Setting { @Override public void set(T value) { if (!value.equals(get())) { - sharedPreferences.edit().putString(key, value.getName()).apply(); + settingProvider.putString(key, value.getName()); cached = value; onValueChanged(); } diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/Setting.java b/Clover/app/src/main/java/org/floens/chan/core/settings/Setting.java index a3b0ad65..4d56f3db 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/Setting.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/Setting.java @@ -17,19 +17,17 @@ */ package org.floens.chan.core.settings; -import android.content.SharedPreferences; - import java.util.ArrayList; import java.util.List; public abstract class Setting { - protected final SharedPreferences sharedPreferences; + protected final SettingProvider settingProvider; protected final String key; protected final T def; private List> callbacks = new ArrayList<>(); - public Setting(SharedPreferences sharedPreferences, String key, T def) { - this.sharedPreferences = sharedPreferences; + public Setting(SettingProvider settingProvider, String key, T def) { + this.settingProvider = settingProvider; this.key = key; this.def = def; } diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/SettingProvider.java b/Clover/app/src/main/java/org/floens/chan/core/settings/SettingProvider.java new file mode 100644 index 00000000..2e25e512 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/SettingProvider.java @@ -0,0 +1,36 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.settings; + +public interface SettingProvider { + int getInt(String key, int def); + + void putInt(String key, int value); + + long getLong(String key, long def); + + void putLong(String key, long value); + + boolean getBoolean(String key, boolean def); + + void putBoolean(String key, boolean value); + + String getString(String key, String def); + + void putString(String key, String value); +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/SharedPreferencesSettingProvider.java b/Clover/app/src/main/java/org/floens/chan/core/settings/SharedPreferencesSettingProvider.java new file mode 100644 index 00000000..efc653f6 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/SharedPreferencesSettingProvider.java @@ -0,0 +1,68 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.settings; + +import android.content.SharedPreferences; + +public class SharedPreferencesSettingProvider implements SettingProvider { + private SharedPreferences prefs; + + public SharedPreferencesSettingProvider(SharedPreferences prefs) { + this.prefs = prefs; + } + + @Override + public int getInt(String key, int def) { + return prefs.getInt(key, def); + } + + @Override + public void putInt(String key, int value) { + prefs.edit().putInt(key, value).apply(); + } + + @Override + public long getLong(String key, long def) { + return prefs.getLong(key, def); + } + + @Override + public void putLong(String key, long value) { + prefs.edit().putLong(key, value).apply(); + } + + @Override + public boolean getBoolean(String key, boolean def) { + return prefs.getBoolean(key, def); + } + + @Override + public void putBoolean(String key, boolean value) { + prefs.edit().putBoolean(key, value).apply(); + } + + @Override + public String getString(String key, String def) { + return prefs.getString(key, def); + } + + @Override + public void putString(String key, String value) { + prefs.edit().putString(key, value).apply(); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/StringSetting.java b/Clover/app/src/main/java/org/floens/chan/core/settings/StringSetting.java index 6c002faf..5600ab0c 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/StringSetting.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/StringSetting.java @@ -17,14 +17,12 @@ */ package org.floens.chan.core.settings; -import android.content.SharedPreferences; - public class StringSetting extends Setting { private boolean hasCached = false; private String cached; - public StringSetting(SharedPreferences sharedPreferences, String key, String def) { - super(sharedPreferences, key, def); + public StringSetting(SettingProvider settingProvider, String key, String def) { + super(settingProvider, key, def); } @Override @@ -32,7 +30,7 @@ public class StringSetting extends Setting { if (hasCached) { return cached; } else { - cached = sharedPreferences.getString(key, def); + cached = settingProvider.getString(key, def); hasCached = true; return cached; } @@ -41,7 +39,7 @@ public class StringSetting extends Setting { @Override public void set(String value) { if (!value.equals(get())) { - sharedPreferences.edit().putString(key, value).apply(); + settingProvider.putString(key, value); cached = value; onValueChanged(); } diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/json/BooleanJsonSetting.java b/Clover/app/src/main/java/org/floens/chan/core/settings/json/BooleanJsonSetting.java new file mode 100644 index 00000000..7a6912e6 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/json/BooleanJsonSetting.java @@ -0,0 +1,25 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.settings.json; + +import com.google.gson.annotations.SerializedName; + +public class BooleanJsonSetting extends JsonSetting { + @SerializedName("value") + public boolean value; +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/json/IntegerJsonSetting.java b/Clover/app/src/main/java/org/floens/chan/core/settings/json/IntegerJsonSetting.java new file mode 100644 index 00000000..16995dd0 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/json/IntegerJsonSetting.java @@ -0,0 +1,25 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.settings.json; + +import com.google.gson.annotations.SerializedName; + +public class IntegerJsonSetting extends JsonSetting { + @SerializedName("value") + public int value; +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/json/site/SiteUserSettings.java b/Clover/app/src/main/java/org/floens/chan/core/settings/json/JsonSetting.java similarity index 81% rename from Clover/app/src/main/java/org/floens/chan/core/model/json/site/SiteUserSettings.java rename to Clover/app/src/main/java/org/floens/chan/core/settings/json/JsonSetting.java index 45be61c6..6f86559c 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/json/site/SiteUserSettings.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/json/JsonSetting.java @@ -15,8 +15,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.floens.chan.core.model.json.site; +package org.floens.chan.core.settings.json; +import com.google.gson.annotations.SerializedName; -public class SiteUserSettings { +public class JsonSetting { + @SerializedName("key") + public String key; } diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/json/JsonSettings.java b/Clover/app/src/main/java/org/floens/chan/core/settings/json/JsonSettings.java new file mode 100644 index 00000000..e4523386 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/json/JsonSettings.java @@ -0,0 +1,29 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.settings.json; + + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + +public class JsonSettings { + @SerializedName("settings") + List settings = new ArrayList<>(); +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/json/JsonSettingsProvider.java b/Clover/app/src/main/java/org/floens/chan/core/settings/json/JsonSettingsProvider.java new file mode 100644 index 00000000..465190d9 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/json/JsonSettingsProvider.java @@ -0,0 +1,152 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.settings.json; + +import org.floens.chan.core.settings.SettingProvider; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public class JsonSettingsProvider implements SettingProvider { + public final JsonSettings jsonSettings; + private Callback callback; + + private Map byKey = new HashMap<>(); + + public JsonSettingsProvider(JsonSettings jsonSettings, Callback callback) { + this.jsonSettings = jsonSettings; + this.callback = callback; + + load(); + } + + @Override + public int getInt(String key, int def) { + JsonSetting setting = byKey.get(key); + if (setting != null) { + return ((IntegerJsonSetting) setting).value; + } else { + return def; + } + } + + @Override + public void putInt(String key, int value) { + JsonSetting jsonSetting = byKey.get(key); + if (jsonSetting == null) { + IntegerJsonSetting v = new IntegerJsonSetting(); + v.value = value; + byKey.put(key, v); + } else { + ((IntegerJsonSetting) jsonSetting).value = value; + } + save(); + } + + @Override + public long getLong(String key, long def) { + JsonSetting setting = byKey.get(key); + if (setting != null) { + return ((LongJsonSetting) setting).value; + } else { + return def; + } + } + + @Override + public void putLong(String key, long value) { + JsonSetting jsonSetting = byKey.get(key); + if (jsonSetting == null) { + LongJsonSetting v = new LongJsonSetting(); + v.value = value; + byKey.put(key, v); + } else { + ((LongJsonSetting) jsonSetting).value = value; + } + save(); + } + + @Override + public boolean getBoolean(String key, boolean def) { + JsonSetting setting = byKey.get(key); + if (setting != null) { + return ((BooleanJsonSetting) setting).value; + } else { + return def; + } + } + + @Override + public void putBoolean(String key, boolean value) { + JsonSetting jsonSetting = byKey.get(key); + if (jsonSetting == null) { + BooleanJsonSetting v = new BooleanJsonSetting(); + v.value = value; + byKey.put(key, v); + } else { + ((BooleanJsonSetting) jsonSetting).value = value; + } + save(); + } + + @Override + public String getString(String key, String def) { + JsonSetting setting = byKey.get(key); + if (setting != null) { + return ((StringJsonSetting) setting).value; + } else { + return def; + } + } + + @Override + public void putString(String key, String value) { + JsonSetting jsonSetting = byKey.get(key); + if (jsonSetting == null) { + StringJsonSetting v = new StringJsonSetting(); + v.value = value; + byKey.put(key, v); + } else { + ((StringJsonSetting) jsonSetting).value = value; + } + save(); + } + + private void load() { + byKey.clear(); + for (JsonSetting setting : jsonSettings.settings) { + byKey.put(setting.key, setting); + } + } + + private void save() { + List settings = new ArrayList<>(); + for (Map.Entry e : byKey.entrySet()) { + settings.add(e.getValue()); + } + jsonSettings.settings = settings; + callback.save(); + } + + public interface Callback { + void save(); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/json/LongJsonSetting.java b/Clover/app/src/main/java/org/floens/chan/core/settings/json/LongJsonSetting.java new file mode 100644 index 00000000..92aea749 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/json/LongJsonSetting.java @@ -0,0 +1,25 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.settings.json; + +import com.google.gson.annotations.SerializedName; + +public class LongJsonSetting extends JsonSetting { + @SerializedName("value") + public long value; +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/json/RuntimeTypeAdapterFactory.java b/Clover/app/src/main/java/org/floens/chan/core/settings/json/RuntimeTypeAdapterFactory.java new file mode 100644 index 00000000..dc8c4a2b --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/json/RuntimeTypeAdapterFactory.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.floens.chan.core.settings.json; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.Streams; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Adapts values whose runtime type may differ from their declaration type. This + * is necessary when a field's type is not the same type that GSON should create + * when deserializing that field. For example, consider these types: + *
   {@code
+ *   abstract class Shape {
+ *     int x;
+ *     int y;
+ *   }
+ *   class Circle extends Shape {
+ *     int radius;
+ *   }
+ *   class Rectangle extends Shape {
+ *     int width;
+ *     int height;
+ *   }
+ *   class Diamond extends Shape {
+ *     int width;
+ *     int height;
+ *   }
+ *   class Drawing {
+ *     Shape bottomShape;
+ *     Shape topShape;
+ *   }
+ * }
+ *

Without additional type information, the serialized JSON is ambiguous. Is + * the bottom shape in this drawing a rectangle or a diamond?

   {@code
+ *   {
+ *     "bottomShape": {
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * This class addresses this problem by adding type information to the + * serialized JSON and honoring that type information when the JSON is + * deserialized:
   {@code
+ *   {
+ *     "bottomShape": {
+ *       "type": "Diamond",
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "type": "Circle",
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * Both the type field name ({@code "type"}) and the type labels ({@code + * "Rectangle"}) are configurable. + * + *

Registering Types

+ * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field + * name to the {@link #of} factory method. If you don't supply an explicit type + * field name, {@code "type"} will be used.
   {@code
+ *   RuntimeTypeAdapterFactory shapeAdapterFactory
+ *       = RuntimeTypeAdapterFactory.of(Shape.class, "type");
+ * }
+ * Next register all of your subtypes. Every subtype must be explicitly + * registered. This protects your application from injection attacks. If you + * don't supply an explicit type label, the type's simple name will be used. + *
   {@code
+ *   shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
+ *   shapeAdapter.registerSubtype(Circle.class, "Circle");
+ *   shapeAdapter.registerSubtype(Diamond.class, "Diamond");
+ * }
+ * Finally, register the type adapter factory in your application's GSON builder: + *
   {@code
+ *   Gson gson = new GsonBuilder()
+ *       .registerTypeAdapterFactory(shapeAdapterFactory)
+ *       .create();
+ * }
+ * Like {@code GsonBuilder}, this API supports chaining:
   {@code
+ *   RuntimeTypeAdapterFactory shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
+ *       .registerSubtype(Rectangle.class)
+ *       .registerSubtype(Circle.class)
+ *       .registerSubtype(Diamond.class);
+ * }
+ */ +public class RuntimeTypeAdapterFactory implements TypeAdapterFactory { + private final Class baseType; + private final String typeFieldName; + private final Map> labelToSubtype = new LinkedHashMap>(); + private final Map, String> subtypeToLabel = new LinkedHashMap, String>(); + + private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName) { + if (typeFieldName == null || baseType == null) { + throw new NullPointerException(); + } + this.baseType = baseType; + this.typeFieldName = typeFieldName; + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code + * typeFieldName} as the type field name. Type field names are case sensitive. + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { + return new RuntimeTypeAdapterFactory(baseType, typeFieldName); + } + + /** + * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as + * the type field name. + */ + public static RuntimeTypeAdapterFactory of(Class baseType) { + return new RuntimeTypeAdapterFactory(baseType, "type"); + } + + /** + * Registers {@code type} identified by {@code label}. Labels are case + * sensitive. + * + * @throws IllegalArgumentException if either {@code type} or {@code label} + * have already been registered on this type adapter. + */ + public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { + if (type == null || label == null) { + throw new NullPointerException(); + } + if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { + throw new IllegalArgumentException("types and labels must be unique"); + } + labelToSubtype.put(label, type); + subtypeToLabel.put(type, label); + return this; + } + + /** + * Registers {@code type} identified by its {@link Class#getSimpleName simple + * name}. Labels are case sensitive. + * + * @throws IllegalArgumentException if either {@code type} or its simple name + * have already been registered on this type adapter. + */ + public RuntimeTypeAdapterFactory registerSubtype(Class type) { + return registerSubtype(type, type.getSimpleName()); + } + + public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getRawType() != baseType) { + return null; + } + + final Map> labelToDelegate + = new LinkedHashMap>(); + final Map, TypeAdapter> subtypeToDelegate + = new LinkedHashMap, TypeAdapter>(); + for (Map.Entry> entry : labelToSubtype.entrySet()) { + TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); + labelToDelegate.put(entry.getKey(), delegate); + subtypeToDelegate.put(entry.getValue(), delegate); + } + + return new TypeAdapter() { + @Override public R read(JsonReader in) throws IOException { + JsonElement jsonElement = Streams.parse(in); + JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); + if (labelJsonElement == null) { + throw new JsonParseException("cannot deserialize " + baseType + + " because it does not define a field named " + typeFieldName); + } + String label = labelJsonElement.getAsString(); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); + if (delegate == null) { + throw new JsonParseException("cannot deserialize " + baseType + " subtype named " + + label + "; did you forget to register a subtype?"); + } + return delegate.fromJsonTree(jsonElement); + } + + @Override public void write(JsonWriter out, R value) throws IOException { + Class srcType = value.getClass(); + String label = subtypeToLabel.get(srcType); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); + if (delegate == null) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + "; did you forget to register a subtype?"); + } + JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); + if (jsonObject.has(typeFieldName)) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + " because it already defines a field named " + typeFieldName); + } + JsonObject clone = new JsonObject(); + clone.add(typeFieldName, new JsonPrimitive(label)); + for (Map.Entry e : jsonObject.entrySet()) { + clone.add(e.getKey(), e.getValue()); + } + Streams.write(clone, out); + } + }.nullSafe(); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/json/StringJsonSetting.java b/Clover/app/src/main/java/org/floens/chan/core/settings/json/StringJsonSetting.java new file mode 100644 index 00000000..bf3b91e5 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/json/StringJsonSetting.java @@ -0,0 +1,25 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.settings.json; + +import com.google.gson.annotations.SerializedName; + +public class StringJsonSetting extends JsonSetting { + @SerializedName("value") + public String value; +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/Site.java b/Clover/app/src/main/java/org/floens/chan/core/site/Site.java index f2dc304e..1f44203e 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/Site.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/Site.java @@ -21,7 +21,7 @@ import android.support.annotation.Nullable; import org.floens.chan.core.model.Post; import org.floens.chan.core.model.json.site.SiteConfig; -import org.floens.chan.core.model.json.site.SiteUserSettings; +import org.floens.chan.core.settings.json.JsonSettings; import org.floens.chan.core.model.orm.Board; import org.floens.chan.core.model.orm.Loadable; import org.floens.chan.core.site.common.ChanReader; @@ -115,13 +115,13 @@ public interface Site { * @param config the site config * @param userSettings the site user settings */ - void initialize(int id, SiteConfig config, SiteUserSettings userSettings); + void initialize(int id, SiteConfig config, JsonSettings userSettings); void postInitialize(); /** * Global positive (>0) integer that uniquely identifies this site.
- * Use the id received from {@link #initialize(int, SiteConfig, SiteUserSettings)}. + * Use the id received from {@link #initialize(int, SiteConfig, JsonSettings)}. * * @return a positive (>0) integer that uniquely identifies this site. */ diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/SiteBase.java b/Clover/app/src/main/java/org/floens/chan/core/site/SiteBase.java index c9ab1618..8ac63a74 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/SiteBase.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/SiteBase.java @@ -24,8 +24,10 @@ import org.codejargon.feather.Feather; import org.floens.chan.core.database.LoadableProvider; import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.model.json.site.SiteConfig; -import org.floens.chan.core.model.json.site.SiteUserSettings; import org.floens.chan.core.model.orm.Board; +import org.floens.chan.core.settings.SettingProvider; +import org.floens.chan.core.settings.json.JsonSettings; +import org.floens.chan.core.settings.json.JsonSettingsProvider; import org.floens.chan.core.site.http.HttpCallManager; import java.util.Collections; @@ -35,15 +37,17 @@ import static org.floens.chan.Chan.injector; public abstract class SiteBase implements Site { protected int id; protected SiteConfig config; - protected SiteUserSettings userSettings; protected HttpCallManager httpCallManager; protected RequestQueue requestQueue; protected BoardManager boardManager; protected LoadableProvider loadableProvider; + private JsonSettings userSettings; + protected SettingProvider settingsProvider; + @Override - public void initialize(int id, SiteConfig config, SiteUserSettings userSettings) { + public void initialize(int id, SiteConfig config, JsonSettings userSettings) { this.id = id; this.config = config; this.userSettings = userSettings; @@ -56,6 +60,11 @@ public abstract class SiteBase implements Site { requestQueue = injector.instance(RequestQueue.class); boardManager = injector.instance(BoardManager.class); loadableProvider = injector.instance(LoadableProvider.class); + SiteManager siteManager = injector.instance(SiteManager.class); + + settingsProvider = new JsonSettingsProvider(userSettings, () -> { + siteManager.updateUserSettings(this, userSettings); + }); if (boardsType() == BoardsType.DYNAMIC) { boards(boards -> boardManager.createAll(boards.boards)); diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/SiteManager.java b/Clover/app/src/main/java/org/floens/chan/core/site/SiteManager.java index e85c508a..09a60048 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/SiteManager.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/SiteManager.java @@ -21,7 +21,7 @@ package org.floens.chan.core.site; import android.util.Pair; import org.floens.chan.core.model.json.site.SiteConfig; -import org.floens.chan.core.model.json.site.SiteUserSettings; +import org.floens.chan.core.settings.json.JsonSettings; import org.floens.chan.core.model.orm.SiteModel; import org.floens.chan.core.site.sites.chan4.Chan4; @@ -86,10 +86,17 @@ public class SiteManager { callback.onSiteAdded(site); } + public void updateUserSettings(Site site, JsonSettings jsonSettings) { + SiteModel siteModel = siteRepository.byId(site.id()); + if (siteModel == null) throw new NullPointerException("siteModel == null"); + siteRepository.updateSiteUserSettingsAsync(siteModel, jsonSettings); + } + public void initialize() { if (initialized) { throw new IllegalStateException("Already initialized"); } + initialized = true; if (addSiteForLegacy) { addSiteForLegacy = false; @@ -100,7 +107,7 @@ public class SiteManager { config.classId = Sites.SITE_CLASSES.indexOfValue(site.getClass()); config.external = false; - SiteModel model = siteRepository.create(config, new SiteUserSettings()); + SiteModel model = siteRepository.create(config, new JsonSettings()); siteRepository.setId(model, 0); } @@ -129,7 +136,7 @@ public class SiteManager { */ private void createNewSite(Site site) { SiteConfig config = new SiteConfig(); - SiteUserSettings settings = new SiteUserSettings(); + JsonSettings settings = new JsonSettings(); config.classId = Sites.SITE_CLASSES.indexOfValue(site.getClass()); config.external = false; @@ -138,9 +145,9 @@ public class SiteManager { } private Site fromModel(SiteModel siteModel) { - Pair configFields = siteModel.loadConfigFields(); + Pair configFields = siteModel.loadConfigFields(); SiteConfig config = configFields.first; - SiteUserSettings settings = configFields.second; + JsonSettings settings = configFields.second; Site site = instantiateSiteClass(config.classId); site.initialize(siteModel.id, config, settings); diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/SiteRepository.java b/Clover/app/src/main/java/org/floens/chan/core/site/SiteRepository.java index 4a0b0ba5..57d36674 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/SiteRepository.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/SiteRepository.java @@ -2,7 +2,7 @@ package org.floens.chan.core.site; import org.floens.chan.core.database.DatabaseManager; import org.floens.chan.core.model.json.site.SiteConfig; -import org.floens.chan.core.model.json.site.SiteUserSettings; +import org.floens.chan.core.settings.json.JsonSettings; import org.floens.chan.core.model.orm.SiteModel; import java.util.List; @@ -21,9 +21,15 @@ public class SiteRepository { return databaseManager.runTask(databaseManager.getDatabaseSiteManager().getAll()); } - public SiteModel create(SiteConfig config, SiteUserSettings userSettings) { + public SiteModel byId(int id) { + return databaseManager.runTask(databaseManager.getDatabaseSiteManager() + .byId(id)); + } + + public SiteModel create(SiteConfig config, JsonSettings userSettings) { SiteModel siteModel = new SiteModel(); - siteModel.storeConfigFields(config, userSettings); + siteModel.storeConfig(config); + siteModel.storeUserSettings(userSettings); databaseManager.runTask(databaseManager.getDatabaseSiteManager().add(siteModel)); return siteModel; } @@ -32,4 +38,10 @@ public class SiteRepository { databaseManager.runTask(databaseManager.getDatabaseSiteManager() .updateId(siteModel, id)); } + + public void updateSiteUserSettingsAsync(SiteModel siteModel, JsonSettings jsonSettings) { + siteModel.storeUserSettings(jsonSettings); + databaseManager.runTaskAsync(databaseManager.getDatabaseSiteManager() + .update(siteModel)); + } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java b/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java index 1cae5166..5e6318cf 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java @@ -17,7 +17,6 @@ */ package org.floens.chan.core.site.sites.chan4; -import android.content.SharedPreferences; import android.support.annotation.Nullable; import android.webkit.CookieManager; import android.webkit.WebView; @@ -26,6 +25,8 @@ import org.floens.chan.core.model.Post; import org.floens.chan.core.model.orm.Board; import org.floens.chan.core.model.orm.Loadable; import org.floens.chan.core.settings.ChanSettings; +import org.floens.chan.core.settings.SettingProvider; +import org.floens.chan.core.settings.SharedPreferencesSettingProvider; import org.floens.chan.core.settings.StringSetting; import org.floens.chan.core.site.Authentication; import org.floens.chan.core.site.Boards; @@ -253,7 +254,8 @@ public class Chan4 extends SiteBase { private final StringSetting passToken; public Chan4() { - SharedPreferences p = AndroidUtils.getPreferences(); + // we used these before multisite, and lets keep using them. + SettingProvider p = new SharedPreferencesSettingProvider(AndroidUtils.getPreferences()); passUser = new StringSetting(p, "preference_pass_token", ""); passPass = new StringSetting(p, "preference_pass_pin", ""); // token was renamed, before it meant the username, now it means the token returned