Introduced settings import/export

This commit is contained in:
max-klaus 2019-10-08 21:10:13 +03:00
parent 30f257dc8b
commit e65a5b1f7d
2 changed files with 744 additions and 2 deletions

View file

@ -42,11 +42,20 @@ import net.osmand.plus.voice.CommandPlayer;
import net.osmand.render.RenderingRulesStorage;
import net.osmand.util.Algorithms;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
@ -61,6 +70,8 @@ import java.util.StringTokenizer;
public class OsmandSettings {
public static final int VERSION = 1;
public interface OsmandPreference<T> {
T get();
@ -83,6 +94,16 @@ public class OsmandSettings {
boolean isSet();
boolean isSetForMode(ApplicationMode m);
boolean writeToJson(JSONObject json, ApplicationMode appMode) throws JSONException;
void readFromJson(JSONObject json, ApplicationMode appMode) throws JSONException;
String asString();
String asStringModeValue(ApplicationMode m);
T parseString(String s);
}
private abstract class PreferenceWithListener<T> implements OsmandPreference<T> {
@ -172,6 +193,10 @@ public class OsmandSettings {
registeredPreferences.put(APPLICATION_MODE.getId(), APPLICATION_MODE);
}
public Map<String, OsmandPreference<?>> getRegisteredPreferences() {
return Collections.unmodifiableMap(registeredPreferences);
}
private static final String SETTING_CUSTOMIZED_ID = "settings_customized";
private void setCustomized() {
@ -387,8 +412,53 @@ public class OsmandSettings {
public boolean setModeValue(ApplicationMode m, ApplicationMode obj) {
throw new UnsupportedOperationException();
}
@Override
public boolean writeToJson(JSONObject json, ApplicationMode appMode) throws JSONException {
return writeAppModeToJson(json, this);
}
@Override
public void readFromJson(JSONObject json, ApplicationMode appMode) throws JSONException {
readAppModeFromJson(json, this);
}
@Override
public String asString() {
return appModeToString(get());
}
@Override
public String asStringModeValue(ApplicationMode m) {
return appModeToString(m);
}
@Override
public ApplicationMode parseString(String s) {
return appModeFromString(s);
}
};
private String appModeToString(ApplicationMode appMode) {
return appMode.getStringKey();
}
private ApplicationMode appModeFromString(String s) {
return ApplicationMode.valueOfStringKey(s, ApplicationMode.DEFAULT);
}
private boolean writeAppModeToJson(JSONObject json, OsmandPreference<ApplicationMode> appModePref) throws JSONException {
json.put(appModePref.getId(), appModePref.asString());
return true;
}
private void readAppModeFromJson(JSONObject json, OsmandPreference<ApplicationMode> appModePref) throws JSONException {
String s = json.getString(appModePref.getId());
if (s != null) {
appModePref.set(appModePref.parseString(s));
}
}
public ApplicationMode getApplicationMode() {
return APPLICATION_MODE.get();
}
@ -469,6 +539,10 @@ public class OsmandSettings {
return this;
}
public boolean isGlobal() {
return global;
}
public CommonPreference<T> cache() {
cache = true;
return this;
@ -587,6 +661,53 @@ public class OsmandSettings {
public boolean isSetForMode(ApplicationMode mode) {
return settingsAPI.contains(getProfilePreferences(mode), getId());
}
@Override
public boolean writeToJson(JSONObject json, ApplicationMode appMode) throws JSONException {
if (appMode != null) {
if (isSetForMode(appMode)) {
json.put(getId(), asStringModeValue(appMode));
return true;
}
} else if (global) {
if (isSet()) {
json.put(getId(), asString());
return true;
}
}
return false;
}
@Override
public void readFromJson(JSONObject json, ApplicationMode appMode) throws JSONException {
if (isGlobal()) {
String globalValue = json.getString(getId());
if (globalValue != null) {
set(parseString(globalValue));
}
} else {
Object jsonModeValuesObj = json.get(getId());
if (jsonModeValuesObj instanceof JSONObject) {
JSONObject jsonModeValues = (JSONObject) jsonModeValuesObj;
for (ApplicationMode m : ApplicationMode.allPossibleValues()) {
String modeValue = jsonModeValues.getString(m.getStringKey());
if (modeValue != null) {
setModeValue(m, parseString(modeValue));
}
}
}
}
}
@Override
public String asString() {
return get().toString();
}
@Override
public String asStringModeValue(ApplicationMode m) {
return getModeValue(m).toString();
}
}
public class BooleanPreference extends CommonPreference<Boolean> {
@ -605,6 +726,11 @@ public class OsmandSettings {
protected boolean setValue(Object prefs, Boolean val) {
return settingsAPI.edit(prefs).putBoolean(getId(), val).commit();
}
@Override
public Boolean parseString(String s) {
return Boolean.parseBoolean(s);
}
}
private class BooleanAccessibilityPreference extends BooleanPreference {
@ -645,6 +771,10 @@ public class OsmandSettings {
return settingsAPI.edit(prefs).putInt(getId(), val).commit();
}
@Override
public Integer parseString(String s) {
return Integer.parseInt(s);
}
}
private class LongPreference extends CommonPreference<Long> {
@ -664,6 +794,10 @@ public class OsmandSettings {
return settingsAPI.edit(prefs).putLong(getId(), val).commit();
}
@Override
public Long parseString(String s) {
return Long.parseLong(s);
}
}
private class FloatPreference extends CommonPreference<Float> {
@ -683,6 +817,10 @@ public class OsmandSettings {
return settingsAPI.edit(prefs).putFloat(getId(), val).commit();
}
@Override
public Float parseString(String s) {
return Float.parseFloat(s);
}
}
public class StringPreference extends CommonPreference<String> {
@ -701,6 +839,10 @@ public class OsmandSettings {
return settingsAPI.edit(prefs).putString(getId(), (val != null) ? val.trim() : val).commit();
}
@Override
public String parseString(String s) {
return s;
}
}
public class ListStringPreference extends StringPreference {
@ -752,8 +894,6 @@ public class OsmandSettings {
}
return false;
}
}
public class EnumIntPreference<E extends Enum<E>> extends CommonPreference<E> {
@ -784,6 +924,25 @@ public class OsmandSettings {
return settingsAPI.edit(prefs).putInt(getId(), val.ordinal()).commit();
}
@Override
public String asString() {
return get().name();
}
@Override
public String asStringModeValue(ApplicationMode m) {
return getModeValue(m).name();
}
@Override
public E parseString(String s) {
for (E value : values) {
if (value.name().equals(s)) {
return value;
}
}
return null;
}
}
///////////// PREFERENCES classes ////////////////
@ -959,6 +1118,31 @@ public class OsmandSettings {
return valueSaved;
}
@Override
public boolean writeToJson(JSONObject json, ApplicationMode appMode) throws JSONException {
return writeAppModeToJson(json, this);
}
@Override
public void readFromJson(JSONObject json, ApplicationMode appMode) throws JSONException {
readAppModeFromJson(json, this);
}
@Override
public String asString() {
return appModeToString(get());
}
@Override
public String asStringModeValue(ApplicationMode m) {
return appModeToString(m);
}
@Override
public ApplicationMode parseString(String s) {
return appModeFromString(s);
}
};
public final OsmandPreference<ApplicationMode> LAST_ROUTE_APPLICATION_MODE = new CommonPreference<ApplicationMode>("last_route_application_mode_backup_string", ApplicationMode.DEFAULT) {
@ -976,6 +1160,31 @@ public class OsmandSettings {
protected boolean setValue(Object prefs, ApplicationMode val) {
return settingsAPI.edit(prefs).putString(getId(), val.getStringKey()).commit();
}
@Override
public boolean writeToJson(JSONObject json, ApplicationMode appMode) throws JSONException {
return writeAppModeToJson(json, this);
}
@Override
public void readFromJson(JSONObject json, ApplicationMode appMode) throws JSONException {
readAppModeFromJson(json, this);
}
@Override
public String asString() {
return appModeToString(get());
}
@Override
public String asStringModeValue(ApplicationMode m) {
return appModeToString(m);
}
@Override
public ApplicationMode parseString(String s) {
return appModeFromString(s);
}
};
public final OsmandPreference<Boolean> FIRST_MAP_IS_DOWNLOADED = new BooleanPreference(

View file

@ -0,0 +1,533 @@
package net.osmand.plus;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandSettings.OsmandPreference;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class SettingsHelper {
private static final Log LOG = PlatformUtil.getLog(SettingsHelper.class);
private static final int BUFFER = 1024;
public enum SettingsItemType {
GLOBAL,
PROFILE,
PLUGIN,
DATA,
FILE,
}
public abstract static class SettingsItem {
private SettingsItemType type;
SettingsItem(@NonNull SettingsItemType type) {
this.type = type;
}
public SettingsItemType getType() {
return type;
}
public abstract String getName();
public abstract SettingsItemReader getReader();
public abstract SettingsItemWriter getWriter();
}
public abstract static class SettingsItemReader<T extends SettingsItem> {
private T item;
public SettingsItemReader(@NonNull T item) {
this.item = item;
}
public abstract void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException;
}
public abstract static class SettingsItemWriter<T extends SettingsItem> {
private T item;
public SettingsItemWriter(T item) {
this.item = item;
}
public T getItem() {
return item;
}
public abstract boolean writeToStream(@NonNull OutputStream outputStream) throws IOException;
}
public abstract static class OsmandSettingsItem extends SettingsItem {
private OsmandSettings settings;
protected OsmandSettingsItem(@NonNull SettingsItemType type, @NonNull OsmandSettings settings) {
super(type);
this.settings = settings;
}
public OsmandSettings getSettings() {
return settings;
}
}
public abstract static class OsmandSettingsItemReader extends SettingsItemReader<OsmandSettingsItem> {
private OsmandSettings settings;
public OsmandSettingsItemReader(@NonNull OsmandSettingsItem item, @NonNull OsmandSettings settings) {
super(item);
this.settings = settings;
}
protected abstract void readPreferenceFromJson(@NonNull OsmandPreference<?> preference,
@NonNull JSONObject json) throws JSONException;
@Override
public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException {
StringBuilder buf = new StringBuilder();
try {
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String str;
while ((str = in.readLine()) != null) {
buf.append(str);
}
} catch (IOException e) {
throw new IllegalArgumentException("Cannot read json body", e);
}
String jsonStr = buf.toString();
if (Algorithms.isEmpty(jsonStr)) {
throw new IllegalArgumentException("Cannot find json body");
}
JSONObject json;
try {
json = new JSONObject(jsonStr);
} catch (JSONException e) {
throw new IllegalArgumentException("Json parse error", e);
}
Map<String, OsmandPreference<?>> prefs = settings.getRegisteredPreferences();
Iterator<String> iter = json.keys();
while (iter.hasNext()) {
String key = iter.next();
OsmandPreference<?> p = prefs.get(key);
try {
readPreferenceFromJson(p, json);
} catch (JSONException e) {
LOG.error(null, e);
}
}
}
}
public abstract static class OsmandSettingsItemWriter extends SettingsItemWriter<OsmandSettingsItem> {
private OsmandSettings settings;
public OsmandSettingsItemWriter(OsmandSettingsItem item, OsmandSettings settings) {
super(item);
this.settings = settings;
}
protected abstract void writePreferenceToJson(@NonNull OsmandPreference<?> preference,
@NonNull JSONObject json) throws JSONException;
@Override
public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException {
JSONObject json = new JSONObject();
Map<String, OsmandPreference<?>> prefs = settings.getRegisteredPreferences();
for (OsmandPreference<?> pref : prefs.values()) {
try {
writePreferenceToJson(pref, json);
} catch (JSONException e) {
LOG.error(null, e);
}
}
if (json.length() > 0) {
try {
String s = json.toString(2);
outputStream.write(s.getBytes("UTF-8"));
} catch (JSONException e) {
LOG.error(null, e);
}
return true;
}
return false;
}
}
public static class GlobalSettingsItem extends OsmandSettingsItem {
public GlobalSettingsItem(@NonNull OsmandSettings settings) {
super(SettingsItemType.GLOBAL, settings);
}
@Override
public String getName() {
return "global";
}
@Override
public SettingsItemReader getReader() {
return new OsmandSettingsItemReader(this, getSettings()) {
@Override
protected void readPreferenceFromJson(@NonNull OsmandPreference<?> preference, @NonNull JSONObject json) throws JSONException {
preference.readFromJson(json, null);
}
};
}
@Override
public SettingsItemWriter getWriter() {
return new OsmandSettingsItemWriter(this, getSettings()) {
@Override
protected void writePreferenceToJson(@NonNull OsmandPreference<?> preference, @NonNull JSONObject json) throws JSONException {
preference.writeToJson(json, null);
}
};
}
}
public static class ProfileSettingsItem extends OsmandSettingsItem {
private ApplicationMode appMode;
public ProfileSettingsItem(@NonNull OsmandSettings settings, @NonNull ApplicationMode appMode) {
super(SettingsItemType.PROFILE, settings);
this.appMode = appMode;
}
@Override
public String getName() {
return appMode.getStringKey();
}
@Override
public SettingsItemReader getReader() {
return new OsmandSettingsItemReader(this, getSettings()) {
@Override
protected void readPreferenceFromJson(@NonNull OsmandPreference<?> preference, @NonNull JSONObject json) throws JSONException {
preference.readFromJson(json, appMode);
}
};
}
@Override
public SettingsItemWriter getWriter() {
return new OsmandSettingsItemWriter(this, getSettings()) {
@Override
protected void writePreferenceToJson(@NonNull OsmandPreference<?> preference, @NonNull JSONObject json) throws JSONException {
preference.writeToJson(json, appMode);
}
};
}
}
public abstract static class StreamSettingsItemReader extends SettingsItemReader<StreamSettingsItem> {
public StreamSettingsItemReader(@NonNull StreamSettingsItem item) {
super(item);
}
}
public static class StreamSettingsItemWriter extends SettingsItemWriter<StreamSettingsItem> {
public StreamSettingsItemWriter(StreamSettingsItem item) {
super(item);
}
@Override
public boolean writeToStream(@NonNull OutputStream outputStream) throws IOException {
boolean hasData = false;
InputStream is = getItem().inputStream;
if (is != null) {
byte[] data = new byte[BUFFER];
int count;
while ((count = is.read(data, 0, BUFFER)) != -1) {
outputStream.write(data, 0, count);
if (!hasData) {
hasData = true;
}
}
Algorithms.closeStream(is);
}
return hasData;
}
}
public abstract static class StreamSettingsItem extends SettingsItem {
@Nullable
private InputStream inputStream;
private String name;
public StreamSettingsItem(@NonNull SettingsItemType type, @NonNull String name) {
super(type);
this.name = name;
}
public StreamSettingsItem(@NonNull SettingsItemType type, @NonNull InputStream inputStream, @NonNull String name) {
super(type);
this.inputStream = inputStream;
this.name = name;
}
@Nullable
public InputStream getInputStream() {
return inputStream;
}
protected void setInputStream(@Nullable InputStream inputStream) {
this.inputStream = inputStream;
}
@Override
public String getName() {
return name;
}
@Override
public SettingsItemWriter getWriter() {
return new StreamSettingsItemWriter(this);
}
}
public static class DataSettingsItem extends StreamSettingsItem {
@Nullable
private byte[] data;
public DataSettingsItem(@NonNull String name) {
super(SettingsItemType.DATA, name);
}
public DataSettingsItem(@NonNull byte[] data, @NonNull String name) {
super(SettingsItemType.DATA, name);
this.data = data;
}
@Nullable
public byte[] getData() {
return data;
}
@Override
public SettingsItemReader getReader() {
return new StreamSettingsItemReader(this) {
@Override
public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[BUFFER];
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
DataSettingsItem.this.data = buffer.toByteArray();
}
};
}
@Override
public SettingsItemWriter getWriter() {
setInputStream(new ByteArrayInputStream(data));
return super.getWriter();
}
}
public static class FileSettingsItem extends StreamSettingsItem {
private File file;
public FileSettingsItem(@NonNull OsmandApplication app, @NonNull File file) {
super(SettingsItemType.FILE, file.getPath().replace(app.getAppPath(null).getPath(), ""));
this.file = file;
}
public File getFile() {
return file;
}
@Override
public SettingsItemReader getReader() {
return new StreamSettingsItemReader(this) {
@Override
public void readFromStream(@NonNull InputStream inputStream) throws IOException, IllegalArgumentException {
OutputStream output = new FileOutputStream(file);
byte[] buffer = new byte[BUFFER];
int count;
while ((count = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, count);
}
output.flush();
}
};
}
@Override
public SettingsItemWriter getWriter() {
try {
setInputStream(new FileInputStream(file));
} catch (FileNotFoundException e) {
LOG.error(null, e);
}
return super.getWriter();
}
}
private static class SettingsItemsFactory {
private OsmandApplication app;
SettingsItemsFactory(OsmandApplication app) {
this.app = app;
}
@Nullable
public SettingsItem createItem(@NonNull SettingsItemType type, @NonNull String name) {
OsmandSettings settings = app.getSettings();
switch (type) {
case GLOBAL:
return new GlobalSettingsItem(settings);
case PROFILE:
ApplicationMode appMode = ApplicationMode.valueOfStringKey(name, null);
return appMode != null ? new ProfileSettingsItem(settings, appMode) : null;
case PLUGIN:
return null;
case DATA:
return new DataSettingsItem(name);
case FILE:
return new FileSettingsItem(app, new File(app.getAppPath(null), name));
}
return null;
}
}
public static class SettingsExporter {
private Map<String, SettingsItem> items;
private Map<String, String> additionalParams;
public SettingsExporter() {
items = new LinkedHashMap<>();
additionalParams = new LinkedHashMap<>();
}
public void addSettingsItem(SettingsItem item) throws IllegalArgumentException {
if (items.containsKey(item.getName())) {
throw new IllegalArgumentException("Already has such item: " + item.getName());
}
items.put(item.getName(), item);
}
public void addAdditionalParam(String key, String value) {
additionalParams.put(key, value);
}
public void exportSettings(File zipFile) throws JSONException, IOException {
JSONObject json = new JSONObject();
json.put("osmand_settings_version", OsmandSettings.VERSION);
for (Map.Entry<String, String> param : additionalParams.entrySet()) {
json.put(param.getKey(), param.getValue());
}
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(zipFile), BUFFER);
ZipOutputStream zos = new ZipOutputStream(bos);
try {
for (SettingsItem item : items.values()) {
ZipEntry entry = new ZipEntry(item.getName());
entry.setExtra(item.getType().name().getBytes());
zos.putNextEntry(entry);
item.getWriter().writeToStream(zos);
zos.closeEntry();
}
zos.flush();
zos.finish();
} finally {
Algorithms.closeStream(zos);
Algorithms.closeStream(bos);
}
}
}
public static class SettingsImporter {
private OsmandApplication app;
private List<SettingsItem> items;
public SettingsImporter(@NonNull OsmandApplication app) {
this.app = app;
}
public List<SettingsItem> getItems() {
return Collections.unmodifiableList(items);
}
public void importSettings(File zipFile) throws IllegalArgumentException, IOException {
items = new ArrayList<>();
ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile));
InputStream ois = new BufferedInputStream(zis);
SettingsItemsFactory itemsFactory = new SettingsItemsFactory(app);
try {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
String itemTypeStr = new String(entry.getExtra());
if (!Algorithms.isEmpty(itemTypeStr)) {
try {
SettingsItemType type = SettingsItemType.valueOf(itemTypeStr);
SettingsItem item = itemsFactory.createItem(type, entry.getName());
if (item != null) {
item.getReader().readFromStream(ois);
items.add(item);
}
} catch (IllegalArgumentException e) {
LOG.error("Wrong SettingsItemType: " + itemTypeStr, e);
} finally {
zis.closeEntry();
}
}
}
} catch (IOException ex) {
LOG.error(ex);
} finally {
Algorithms.closeStream(ois);
Algorithms.closeStream(zis);
}
}
}
}