From cf5fe2e948d8830d9e8b3050335c2ba1f92b2d86 Mon Sep 17 00:00:00 2001 From: wipedlifepotato <60944239+wipedlifepotato@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:34:30 +0700 Subject: [PATCH] Perferences (#66) --- app/build.gradle | 15 + app/src/main/AndroidManifest.xml | 19 + .../java/org/purplei2p/i2pd/I2PDActivity.java | 2 +- .../i2pd/I2PDPermsAskerActivity.java | 2 + .../org/purplei2p/i2pd/I2PdQSTileService.java | 56 + .../i2pd/MainPreferenceActivity.java | 459 +++++++ .../org/purplei2p/i2pd/SettingsActivity.java | 14 +- .../purplei2p/i2pd/iniedotr/IniEditor.java | 1182 ----------------- app/src/main/res/layout/activity_main.xml | 16 +- app/src/main/res/layout/activity_settings.xml | 12 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/preferences_i2pd.xml | 109 +- 13 files changed, 653 insertions(+), 1235 deletions(-) create mode 100644 app/src/main/java/org/purplei2p/i2pd/I2PdQSTileService.java create mode 100644 app/src/main/java/org/purplei2p/i2pd/MainPreferenceActivity.java delete mode 100644 app/src/main/java/org/purplei2p/i2pd/iniedotr/IniEditor.java diff --git a/app/build.gradle b/app/build.gradle index f5858d8..f9b9b99 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,14 +5,29 @@ plugins { dependencies { implementation 'androidx.core:core:1.9.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' + // implementation 'org.ini4j:ini4j:0.5.4' + implementation 'org.apache.commons:commons-configuration2:2.9.0' +} + configurations { + all{ + //exclude group: 'commons-logging', module: 'commons-logging' + //exclude group: 'org.apache.httpcomponents' + exclude module: 'httpclient' + } } android { + lintOptions { + // Not so a good way + disable 'DuplicatePlatformClasses' + } + compileSdkVersion 33 defaultConfig { applicationId "org.purplei2p.i2pd" targetSdkVersion 33 + // TODO: 24? minSdkVersion 16 versionCode 2500200 versionName "2.50.2" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9e2edce..e7c2d76 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,6 +23,17 @@ android:theme="@android:style/Theme.DeviceDefault" android:usesCleartextTraffic="true"> + + + + + + + + + diff --git a/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java b/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java index 0c0ecc7..2ebdd15 100644 --- a/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java +++ b/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java @@ -111,7 +111,7 @@ public class I2PDActivity extends Activity { Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - + startService(new Intent(this, ForegroundService.class)); textView = (TextView) findViewById(R.id.appStatusText); HTTPProxyState = (CheckBox) findViewById(R.id.service_httpproxy_box); SOCKSProxyState = (CheckBox) findViewById(R.id.service_socksproxy_box); diff --git a/app/src/main/java/org/purplei2p/i2pd/I2PDPermsAskerActivity.java b/app/src/main/java/org/purplei2p/i2pd/I2PDPermsAskerActivity.java index 318ab85..2df2780 100644 --- a/app/src/main/java/org/purplei2p/i2pd/I2PDPermsAskerActivity.java +++ b/app/src/main/java/org/purplei2p/i2pd/I2PDPermsAskerActivity.java @@ -183,6 +183,7 @@ public class I2PDPermsAskerActivity extends Activity { } } } else if (requestCode == APP_STORAGE_ACCESS_REQUEST_CODE && resultCode == RESULT_OK) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Environment.isExternalStorageManager()) { startMainActivity(); } else { @@ -195,5 +196,6 @@ public class I2PDPermsAskerActivity extends Activity { } else { finish(); // close the app } + } } } diff --git a/app/src/main/java/org/purplei2p/i2pd/I2PdQSTileService.java b/app/src/main/java/org/purplei2p/i2pd/I2PdQSTileService.java new file mode 100644 index 0000000..3a06165 --- /dev/null +++ b/app/src/main/java/org/purplei2p/i2pd/I2PdQSTileService.java @@ -0,0 +1,56 @@ +package org.purplei2p.i2pd; + +import android.content.Intent; +import android.service.quicksettings.Tile; +import android.service.quicksettings.TileService; +import android.util.Log; + +import android.annotation.TargetApi; +import android.os.Build; + +@TargetApi(Build.VERSION_CODES.N) +public class I2PdQSTileService extends TileService { + + private static final String TAG = "MyQSTileService"; + @Override + public void onClick() { + super.onClick(); + Log.d(TAG, "Tile clicked."); + + try { + // Add the FLAG_ACTIVITY_NEW_TASK flag + Intent intent = new Intent(this, I2PDActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + startActivityAndCollapse(intent); + } catch (Exception e) { + Log.e(TAG, "Error starting ForegroundService", e); + } + } + + + + @Override + public void onStartListening() { + super.onStartListening(); + Log.d(TAG, "Tile started listening."); + } + + @Override + public void onStopListening() { + super.onStopListening(); + Log.d(TAG, "Tile stopped listening."); + } + + @Override + public void onTileAdded() { + super.onTileAdded(); + Log.d(TAG, "Tile added."); + } + + @Override + public void onTileRemoved() { + super.onTileRemoved(); + Log.d(TAG, "Tile removed."); + } +} diff --git a/app/src/main/java/org/purplei2p/i2pd/MainPreferenceActivity.java b/app/src/main/java/org/purplei2p/i2pd/MainPreferenceActivity.java new file mode 100644 index 0000000..3d2ecec --- /dev/null +++ b/app/src/main/java/org/purplei2p/i2pd/MainPreferenceActivity.java @@ -0,0 +1,459 @@ +package org.purplei2p.i2pd; +import android.os.Build; + +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.PreferenceActivity; +import android.widget.Toast; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import android.util.Log; +import java.io.File; +import org.apache.commons.configuration2.INIConfiguration; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.file.*; + +public class MainPreferenceActivity extends PreferenceActivity { + private Boolean isOldConfigExists = false; + public static final String CONFIG_FILE_PATH = "/sdcard/i2pd/i2pd.conf"; + private static final String OLD_FILE_PREFIX= "__old"; + + boolean isOldConfigExists() + { + try { + return new File(CONFIG_FILE_PATH + OLD_FILE_PREFIX).exists(); + }catch(Exception e) { return false; } + } + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences_i2pd); + INIConfiguration properties = readConfiguration(); + // backup old configuration. + if (!isOldConfigExists) + { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (Files.exists(Paths.get(CONFIG_FILE_PATH))) { + Files.copy(Paths.get(CONFIG_FILE_PATH), Paths.get(CONFIG_FILE_PATH + OLD_FILE_PREFIX)); + } + } // if build ... + } catch (IOException e) { + e.printStackTrace(); + } + + } + // delete empty sections + { + writeConfiguration(properties); + } + // Main Categoryре + ListPreference logLevel = (ListPreference) findPreference("logLevelPreference"); + logLevel.setValue(properties.getString("log", "info")); + logLevel.setOnPreferenceChangeListener((preference, newValue) -> { + // Modify properties + //properties.setProperty("log", (String) newValue); + // Save modified properties + //writeConfiguration(properties); + writeConfiguration(properties, "log", (String) newValue); + return true; + }); + + CheckBoxPreference ipv4Enable = (CheckBoxPreference) findPreference("ipv4EnablePreference"); + boolean ipv4Enabled = Boolean.parseBoolean(properties.getString("ipv4", "true")); // "true" - значение по умолчанию, если ключ отсутствует + ipv4Enable.setChecked(ipv4Enabled); + ipv4Enable.setOnPreferenceChangeListener((preference, newValue) -> { + // Modify properties + // Convert Object to boolean + boolean newValueBoolean = (boolean) newValue; + //properties.setProperty("ipv4", String.valueOf(newValueBoolean)); // assuming "ipv4" is the key + // Save modified properties + writeConfiguration(properties, "ipv4", String.valueOf(newValueBoolean)); + return true; + }); + CheckBoxPreference ipv6Enable = (CheckBoxPreference) findPreference("ipv6EnablePreference"); + boolean ipv6Enabled = Boolean.parseBoolean(properties.getString("ipv6", "false")); + ipv6Enable.setChecked(ipv6Enabled); + ipv6Enable.setOnPreferenceChangeListener((preference, newValue) -> { + // Modify properties + // Convert Object to boolean + boolean newValueBoolean = (boolean) newValue; + //properties.setProperty("ipv6", String.valueOf(newValueBoolean)); // assuming "ipv4" is the key + // Save modified properties + writeConfiguration(properties, "ipv6", String.valueOf(newValueBoolean)); + return true; + }); +// Example for portPreference (EditTextPreference) + EditTextPreference portPreference = (EditTextPreference) findPreference("portPreference"); + String portValue = properties.getString("port", "auto"); + portPreference.setText(portValue); + portPreference.setOnPreferenceChangeListener((preference, newValue) -> { + // Modify properties + /*Properties properties = readProperties(); + // Convert Object to String (assuming "port" is the key) + String newValueString = newValue.toString(); + properties.setProperty("port", newValueString); + // Save modified properties + writeProperties(properties);*/ + Toast.makeText(MainPreferenceActivity.this, "For security reasons, changes are not allowed. If you really want this, open the config.", Toast.LENGTH_SHORT).show(); + + return true; + }); + + // Example for bandwidthPreference (ListPreference) + ListPreference bandwidthPreference = (ListPreference) findPreference("bandwidthPreference"); + String bandwidthValue = properties.getString("bandwidth", "L"); + bandwidthPreference.setValue(bandwidthValue); + bandwidthPreference.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + //properties.setProperty("bandwidth", newValueString); + writeConfiguration(properties, "bandwidth", newValueString); + return true; + }); +// Example for noTransitPreference (CheckBoxPreference) + CheckBoxPreference noTransitPreference = (CheckBoxPreference) findPreference("noTransitPreference"); + boolean noTransitPreferenceEnabled = Boolean.parseBoolean(properties.getString("notransit", "false")); + noTransitPreference.setChecked(noTransitPreferenceEnabled); + + noTransitPreference.setOnPreferenceChangeListener((preference, newValue) -> { + boolean newValueBoolean = (boolean) newValue; + //properties.setProperty("notransit", String.valueOf(newValueBoolean)); + writeConfiguration(properties, "notransit", String.valueOf(newValueBoolean)); + return true; + }); + CheckBoxPreference floodfillPreference = (CheckBoxPreference) findPreference("floodfillPreference"); + boolean floodfillPreferenceEnabled = Boolean.parseBoolean(properties.getString("floodfill", "false")); + floodfillPreference.setChecked(floodfillPreferenceEnabled); + floodfillPreference.setOnPreferenceChangeListener((preference, newValue) -> { + boolean newValueBoolean = (boolean) newValue; + //properties.setProperty("floodfill", String.valueOf(newValueBoolean)); + writeConfiguration(properties, "floodfill", String.valueOf(newValueBoolean)); + return true; + }); + // ^^^ general + // vvv not general (sections name) + CheckBoxPreference ssuPreference = (CheckBoxPreference) findPreference("ssuPreference"); + boolean ssuPreferenceEnabled = Boolean.parseBoolean(properties.getString("ssu2.enabled", "true")); + ssuPreference.setChecked(ssuPreferenceEnabled); + ssuPreference.setOnPreferenceChangeListener((preference, newValue) -> { + boolean newValueBoolean = (boolean) newValue; + + properties.setProperty("ssu2.enabled", String.valueOf(newValueBoolean)); + writeConfiguration(properties); + + return true; // Allow the change + }); + // NTCP2 Category + CheckBoxPreference ntcp2Enable = (CheckBoxPreference) findPreference("ntcp2EnablePreference"); + + boolean ntcp2EnabledPreference = Boolean.parseBoolean(properties.getString("ntcp2.enabled", "true")); + ntcp2Enable.setChecked(ntcp2EnabledPreference); + + + ntcp2Enable.setOnPreferenceChangeListener((preference, newValue) -> { + boolean newValueBoolean = (boolean) newValue; + + properties.setProperty("ntcp2.enabled", String.valueOf(newValueBoolean)); + writeConfiguration(properties); + + return true; // Allow the change + }); + CheckBoxPreference ntcp2Publish = (CheckBoxPreference) findPreference("ntcp2PublishPreference"); + + boolean ntcp2PublishEnabledPreference = Boolean.parseBoolean(properties.getString("ntcp2.published", "true")); + ntcp2Publish.setChecked(ntcp2EnabledPreference); + + + ntcp2Publish.setOnPreferenceChangeListener((preference, newValue) -> { + boolean newValueBoolean = (boolean) newValue; + + properties.setProperty("ntcp2.published", String.valueOf(newValueBoolean)); + writeConfiguration(properties); + + return true; // Allow the change + }); + // Web Console Category + CheckBoxPreference webConsoleEnable = (CheckBoxPreference) findPreference("webConsoleEnablePreference"); + boolean webConsoleEnableValue = Boolean.parseBoolean(properties.getString("http.enabled", "false")); + webConsoleEnable.setChecked(webConsoleEnableValue); + + webConsoleEnable.setOnPreferenceChangeListener((preference, newValue) -> { + boolean newValueBoolean = (boolean) newValue; + properties.setProperty("http.enabled", String.valueOf(newValueBoolean)); + // Save modified properties + writeConfiguration(properties); + return true; + }); + EditTextPreference webConsoleAddress = (EditTextPreference) findPreference("webConsoleAddressPreference"); + String webConsoleAddressValue = properties.getString("http.address", "127.0.0.1"); + webConsoleAddress.setText(webConsoleAddressValue); + webConsoleAddress.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("http.address", newValueString); + writeConfiguration(properties); + return true; + }); + EditTextPreference webConsolePort = (EditTextPreference) findPreference("webConsolePortPreference"); + String webConsolePortValue = properties.getString("http.port", "7070"); + webConsolePort.setText(webConsolePortValue); + webConsolePort.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("http.port", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + CheckBoxPreference webConsoleAuth = (CheckBoxPreference) findPreference("webConsoleAuthPreference"); + boolean webConsoleAuthValue = Boolean.parseBoolean(properties.getString("http.auth", "false")); + webConsoleAuth.setChecked(webConsoleAuthValue); + webConsoleAuth.setOnPreferenceChangeListener((preference, newValue) -> { + boolean newValueBoolean = (boolean) newValue; + properties.setProperty("http.auth", String.valueOf(newValueBoolean)); + // Save modified properties + writeConfiguration(properties); + return true; + }); + EditTextPreference webConsoleUser = (EditTextPreference) findPreference("webConsoleUserPreference"); + String webConsoleUserValue = properties.getString("http.user", "user"); + webConsoleUser.setText(webConsoleUserValue); + webConsoleUser.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("http.user", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + EditTextPreference webConsolePassword = (EditTextPreference) findPreference("webConsolePasswordPreference"); + String webConsolePasswordValue = properties.getString("http.pass", "pass"); + webConsolePassword.setText(webConsolePasswordValue); + + webConsolePassword.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("http.pass", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + // HTTP Proxy Category + CheckBoxPreference httpProxyEnable = (CheckBoxPreference) findPreference("httpProxyEnablePreference"); + boolean httpProxyEnabled = Boolean.parseBoolean(properties.getString("httpproxy.enabled", "true")); + httpProxyEnable.setChecked(httpProxyEnabled); + httpProxyEnable.setOnPreferenceChangeListener((preference, newValue) -> { + boolean newValueBoolean = (boolean) newValue; + properties.setProperty("httpproxy.enabled", String.valueOf(newValueBoolean)); + // Save modified properties + writeConfiguration(properties); + return true; + }); + EditTextPreference httpProxyAddress = (EditTextPreference) findPreference("httpProxyAddressPreference"); + String httpProxyAddressValue = properties.getString("httpproxy.address", "127.0.0.1"); + httpProxyAddress.setText(httpProxyAddressValue); + + httpProxyAddress.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("httpproxy.address", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + EditTextPreference httpProxyPort = (EditTextPreference) findPreference("httpProxyPortPreference"); + String httpProxyPortValue = properties.getString("httpproxy.port", "4444"); + httpProxyPort.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("httpproxy.port", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + EditTextPreference httpProxyKeys = (EditTextPreference) findPreference("httpProxyKeysPreference"); + String httpProxyKeyValue = properties.getString("httpproxy.keys", "transient"); + httpProxyKeys.setText(httpProxyKeyValue); + httpProxyKeys.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("httpproxy.keys", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + + // SOCKS Proxy Category + CheckBoxPreference socksProxyEnable = (CheckBoxPreference) findPreference("socksProxyEnablePreference"); + boolean socksProxyEnabled = Boolean.parseBoolean(properties.getString("socksproxy.enabled", "true")); + socksProxyEnable.setChecked(socksProxyEnabled); + socksProxyEnable.setOnPreferenceChangeListener((preference, newValue) -> { + boolean newValueBoolean = (boolean) newValue; + properties.setProperty("socksproxy.enabled", String.valueOf(newValueBoolean)); + // Save modified properties + writeConfiguration(properties); + return true; + }); + EditTextPreference socksProxyAddress = (EditTextPreference) findPreference("socksProxyAddressPreference"); + String socksProxyAddressValue = properties.getString("socksproxy.address", "127.0.0.1"); + socksProxyAddress.setText(socksProxyAddressValue); + socksProxyAddress.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("socksproxy.address", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + EditTextPreference socksProxyPort = (EditTextPreference) findPreference("socksProxyPortPreference"); + String socksProxyPortValue = properties.getString("socksproxy.port", "4447"); + socksProxyPort.setText(socksProxyPortValue); + socksProxyPort.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("socksproxy.port", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + EditTextPreference socksProxyKeys = (EditTextPreference) findPreference("socksProxyKeysPreference"); + String socksProxyKeysValue = properties.getString("socksproxy.keys", "transient"); + socksProxyKeys.setText(socksProxyKeysValue); + socksProxyKeys.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("socksproxy.keys", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + // SAM Category + CheckBoxPreference samEnable = (CheckBoxPreference) findPreference("samEnablePreference"); + boolean samEnableValue = Boolean.parseBoolean(properties.getString("sam.enabled", "true")); + samEnable.setChecked(samEnableValue); + samEnable.setOnPreferenceChangeListener((preference, newValue) -> { + boolean newValueBoolean = (boolean) newValue; + properties.setProperty("sam.enabled", String.valueOf(newValueBoolean)); + // Save modified properties + writeConfiguration(properties); + return true; + }); + EditTextPreference samAddress = (EditTextPreference) findPreference("samAddressPreference"); + String samAddressValue = properties.getString("sam.address", "127.0.0.1"); + samAddress.setText(samAddressValue); + samAddress.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("sam.address", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + EditTextPreference samPort = (EditTextPreference) findPreference("samPortPreference"); + String samPortValue = properties.getString("sam.port", "7656"); + samPort.setText(samPortValue); + samPort.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("sam.port", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + // UPnP Category + CheckBoxPreference upnpEnable = (CheckBoxPreference) findPreference("upnpEnablePreference"); + boolean upnpEnableValue = Boolean.parseBoolean(properties.getString("upnp.enabled", "true")); + upnpEnable.setChecked(upnpEnableValue); + upnpEnable.setOnPreferenceChangeListener((preference, newValue) -> { + boolean newValueBoolean = (boolean) newValue; + properties.setProperty("upnp.enabled", String.valueOf(newValueBoolean)); + // Save modified properties + writeConfiguration(properties); + return true; + }); + EditTextPreference upnpForwardName = (EditTextPreference) findPreference("upnpForwardNamePreference"); + String upnpForwardNameValue = properties.getString("upnp.name", "I2Pd"); + upnpForwardName.setText(upnpForwardNameValue); + upnpForwardName.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("upnp.name", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + // Limits Category + EditTextPreference transitTunnelEdit = (EditTextPreference) findPreference("transitTunnelPreference"); + String transitTunnelValue = properties.getString("limits.transittunnels", "3000"); + transitTunnelEdit.setText(transitTunnelValue); + transitTunnelEdit.setOnPreferenceChangeListener((preference, newValue) -> { + String newValueString = newValue.toString(); + properties.setProperty("limits.transittunnels", newValueString); + // Save modified properties + writeConfiguration(properties); + return true; + }); + } +/* + private Properties readProperties() { + if (isOldConfigExists) { + Toast.makeText(this, oldConfigErrMsg, Toast.LENGTH_SHORT).show(); + } + Properties properties = new Properties(); + try (FileReader reader = new FileReader(CONFIG_FILE_PATH)) { + properties.load(reader); + } catch (IOException e) { + e.printStackTrace(); + } + return properties; + } +*/ + /* + private void writeProperties(Properties properties) { + if (isOldConfigExists) { + Toast.makeText(this, oldConfigErrMsg, Toast.LENGTH_SHORT).show(); + } else { + try (FileWriter writer = new FileWriter(CONFIG_FILE_PATH)) { + properties.store(writer, "Updated properties"); + } catch (IOException e) { + e.printStackTrace(); + } + } + }*/ + + private INIConfiguration readConfiguration() { + INIConfiguration iniConfiguration = new INIConfiguration(); + try (FileReader reader = new FileReader(CONFIG_FILE_PATH)) { + iniConfiguration.read(reader); + } catch (Exception e) { + e.printStackTrace(); + } + return iniConfiguration; + } + private void writeConfiguration(INIConfiguration iniConfiguration) + { + writeConfiguration(iniConfiguration, "", ""); + } + private void writeConfiguration(INIConfiguration iniConfiguration, String option, String value) { + try (FileWriter writer = new FileWriter(CONFIG_FILE_PATH)) { + StringWriter stringWriter = new StringWriter(); + iniConfiguration.write(stringWriter); + String configFileContent = stringWriter.toString(); + + // Удаление пустых секций из строки конфигурации + String regexEmptySections = "(\\[\\w+\\]\\n(\\n|$)|\\[\\w+\\](\\z|\\Z))"; + configFileContent = configFileContent.replaceAll(regexEmptySections, ""); + + // Если указана опция для перемещения + if (!option.isEmpty()) { + Log.d("configFileContent","option is not empty"); + String optionPattern = option + "\\s+?=\\s+?\\w+"; // Паттерн для поиска опции с присваиванием в начале строки + Log.d("confiFileContent","config File contains option"); + if (configFileContent.contains(option)) configFileContent = configFileContent.replaceAll(optionPattern, ""); // Удаляем первое вхождение + Log.d("confiFileContent", option); + Log.d("confiFileContent", value); + Log.d("confiFileContent","Add on start line"); + configFileContent = option + "=" + value + "\n" + configFileContent; + + } + Log.d("configFileContent", configFileContent); + writer.write(configFileContent); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/org/purplei2p/i2pd/SettingsActivity.java b/app/src/main/java/org/purplei2p/i2pd/SettingsActivity.java index 43d9b1a..27fe126 100644 --- a/app/src/main/java/org/purplei2p/i2pd/SettingsActivity.java +++ b/app/src/main/java/org/purplei2p/i2pd/SettingsActivity.java @@ -11,8 +11,12 @@ import android.os.Bundle; import android.provider.Settings; import android.util.Log; import android.view.MenuItem; +import android.view.View; +import android.widget.Button; import android.widget.CompoundButton; import android.widget.Switch; + + import java.io.File; import java.util.List; import java.util.Objects; @@ -21,7 +25,6 @@ import java.util.Objects; //import org.purplei2p.i2pd.iniedotr.IniEditor; public class SettingsActivity extends Activity { - //protected IniEditor iniedit = new IniEditor(); private String TAG = "i2pdSrvcSettings"; private File cacheDir; public static String onBootFileName = "/onBoot"; // just file, empty, if exist the do autostart, if not then no. @@ -83,9 +86,16 @@ public class SettingsActivity extends Activity { setContentView(R.layout.activity_settings); Objects.requireNonNull(getActionBar()).setDisplayHomeAsUpEnabled(true); Switch autostart_switch = findViewById(R.id.autostart_enable); - + Button openPreferences = findViewById(R.id.OpenPreferences); cacheDir = getApplicationContext().getCacheDir(); File onBoot = new File(cacheDir.getAbsolutePath() + onBootFileName); + openPreferences.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SettingsActivity.this, MainPreferenceActivity.class); + startActivity(intent); + } + }); autostart_switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // do something, the isChecked will be diff --git a/app/src/main/java/org/purplei2p/i2pd/iniedotr/IniEditor.java b/app/src/main/java/org/purplei2p/i2pd/iniedotr/IniEditor.java deleted file mode 100644 index 8bb2b4c..0000000 --- a/app/src/main/java/org/purplei2p/i2pd/iniedotr/IniEditor.java +++ /dev/null @@ -1,1182 +0,0 @@ -/* - IniEditor is Copyright (c) 2003-2013, Nik Haldimann - All rights reserved. Distributed under a BSD-style license. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER - OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package org.purplei2p.i2pd.iniedotr; - -import java.io.*; -import java.util.*; - -/** - * Loads, edits and saves INI-style configuration files. While loading from and - * saving to streams and files, IniEditor preserves comments and - * blank lines as well as the order of sections and lines in general. - *

- * IniEditor assumes configuration files to be split in sections. - * A section starts out with a header, which consists of the section name - * enclosed in brackets ('[' and ']'). Everything - * before the first section header is ignored when loading from a stream or - * file. The {@link IniEditor.Section} class can be used to load - * configuration files without sections (ie Java-style properties). - *

- * A "common section" may be named. All sections inherit the options of this - * section but can overwrite them. - *

- * IniEditor represents an INI file (or rather, its sections) line - * by line, as comment, blank and option lines. A comment is a line which has a - * comment delimiter as its first non-white space character. The default comment - * delimiters, which may be overwritten, are '#' and - * ';'. - *

- * A blank line is any line that consists only of white space. - *

- * Everything else is an option line. Option names and values are separated by - * option delimiters '=', ':' or white space (spaces - * and tabs). - *

- * Here's a minimal example. Suppose, we have this in a file called - * users.ini: - *

- *   [root]
- *   role = administrator
- *   last_login = 2003-05-04
- *
- *   [joe]
- *   role = author
- *   last_login = 2003-05-13
- * 
- * Let's load that file, add something to it and save the changes: - *
- *   IniEditor users = new IniEditor();
- *   users.load("users.ini");
- *   users.set("root", "last_login", "2003-05-16");
- *   users.addComment("root", "Must change password often");
- *   users.set("root", "change_pwd", "10 days");
- *   users.addBlankLine("root");
- *   users.save("users.ini");
- * 
- * Now, the file looks like this: - *
- *   [root]
- *   role = administrator
- *   last_login = 2003-05-16
- *
- *   # Must change password often
- *   change_pwd = 10 days
- *
- *   [joe]
- *   role = author
- *   last_login = 2003-05-13
- * 
- *

- * IniEditor provides services simliar to the standard Java API class - * java.util.Properties. It uses its own parser, though, which - * differs in these respects from that of Properties: - *

- * - * @author Nik Haldimann, nhaldimann at gmail dot com - * @version r5 (3/4/2013) - */ -public class IniEditor { - - private static boolean DEFAULT_CASE_SENSITIVITY = false; - - private Map sections; - private List sectionOrder; - private String commonName; - private char[] commentDelims; - private boolean isCaseSensitive; - private OptionFormat optionFormat; - - /** - * Constructs new bare IniEditor instance. - */ - public IniEditor() { - this(null, null); - } - - /** - * Constructs new bare IniEditor instance specifying case-sensitivity. - * - * @param isCaseSensitive section and option names are case-sensitive if - * this is true - */ - public IniEditor(boolean isCaseSensitive) { - this(null, null, isCaseSensitive); - } - - /** - * Constructs new IniEditor instance with a common section. Options in the - * common section are used as defaults for all other sections. - * - * @param commonName name of the common section - */ - public IniEditor(String commonName) { - this(commonName, null); - } - - /** - * Constructs new IniEditor instance with a common section. Options in the - * common section are used as defaults for all other sections. - * - * @param commonName name of the common section - * @param isCaseSensitive section and option names are case-sensitive if - * this is true - */ - public IniEditor(String commonName, boolean isCaseSensitive) { - this(commonName, null, isCaseSensitive); - } - - /** - * Constructs new IniEditor defining comment delimiters. - * - * @param delims an array of characters to be recognized as starters of - * comment lines; the first of them will be used for newly created - * comments - */ - public IniEditor(char[] delims) { - this(null, delims); - } - - /** - * Constructs new IniEditor defining comment delimiters. - * - * @param delims an array of characters to be recognized as starters of - * comment lines; the first of them will be used for newly created - * comments - * @param isCaseSensitive section and option names are case-sensitive if - * this is true - */ - public IniEditor(char[] delims, boolean isCaseSensitive) { - this(null, delims, isCaseSensitive); - } - - /** - * Constructs new IniEditor instance with a common section, defining - * comment delimiters. Options in the common section are used as defaults - * for all other sections. - * - * @param commonName name of the common section - * @param delims an array of characters to be recognized as starters of - * comment lines; the first of them will be used for newly created - * comments - */ - public IniEditor(String commonName, char[] delims) { - this(commonName, delims, DEFAULT_CASE_SENSITIVITY); - } - - /** - * Constructs new IniEditor instance with a common section, defining - * comment delimiters. Options in the common section are used as defaults - * for all other sections. - * - * @param commonName name of the common section - * @param delims an array of characters to be recognized as starters of - * comment lines; the first of them will be used for newly created - * comments - */ - public IniEditor(String commonName, char[] delims, boolean isCaseSensitive) { - this.sections = new HashMap(); - this.sectionOrder = new LinkedList(); - this.isCaseSensitive = isCaseSensitive; - if (commonName != null) { - this.commonName = commonName; - addSection(this.commonName); - } - this.commentDelims = delims; - this.optionFormat = new OptionFormat(Section.DEFAULT_OPTION_FORMAT); - } - - /** - * Sets the option format for this instance to the given string. Options - * will be rendered according to the given format string when printed. The - * string must contain %s three times, these will be replaced - * with the option name, the option separator and the option value in this - * order. Literal percentage signs must be escaped by preceding them with - * another percentage sign (i.e., %% corresponds to one - * percentage sign). The default format string is "%s %s %s". - * - * Option formats may look like format strings as supported by Java 1.5, but - * the string is in fact parsed in a custom fashion to guarantee backwards - * compatibility. So don't try clever stuff like using format conversion - * types other than %s. - * - * @param formatString a format string, containing %s exactly - * three times - * @throws IllegalArgumentException if the format string is illegal - */ - public void setOptionFormatString(String formatString) { - this.optionFormat = new OptionFormat(formatString); - } - - /** - * Returns the value of a given option in a given section or null if either - * the section or the option don't exist. If a common section was defined - * options are also looked up there if they're not present in the specific - * section. - * - * @param section the section's name - * @param option the option's name - * @return the option's value - * @throws NullPointerException any of the arguments is null - */ - public String get(String section, String option) { - if (hasSection(section)) { - Section sect = getSection(section); - if (sect.hasOption(option)) { - return sect.get(option); - } - if (this.commonName != null) { - return getSection(this.commonName).get(option); - } - } - return null; - } - - /** - * Returns a map of a given section with option/value pairs or null if the - * section doesn't exist. - * - * @param section the section's name - * @return HashMap of option/value pairs from the section - * @throws NullPointerException if section is null - */ - public Map< String, String > getSectionMap(String section) { - Map< String, String > sectionMap = new HashMap< String, String >(); - if (hasSection(section)) { - Section sect = getSection(section); - for(String key : sect.options.keySet()) { - sectionMap.put(key, sect.options.get(key).value); - } - return sectionMap; - } - return null; - } - - /** - * Sets the value of an option in a section, if the option exist, otherwise - * adds the option to the section. Trims white space from the start and the - * end of the value and deletes newline characters it might contain. - * - * @param section the section's name - * @param option the option's name - * @param value the option's value - * @throws IniEditor.NoSuchSectionException no section with the given name exists - * @throws IllegalArgumentException the option name is illegal, - * ie contains a '=' character or consists only of white space - * @throws NullPointerException section or option are null - */ - public void set(String section, String option, String value) { - if (hasSection(section)) { - getSection(section).set(option, value); - } - else { - throw new NoSuchSectionException(section); - } - } - - /** - * Removes an option from a section if it exists. Will not remove options - * from the common section if it's not directly addressed. - * - * @param section the section's name - * @param option the option's name - * @return true if the option was actually removed - * @throws IniEditor.NoSuchSectionException no section with the given name exists - */ - public boolean remove(String section, String option) { - if (hasSection(section)) { - return getSection(section).remove(option); - } - else { - throw new NoSuchSectionException(section); - } - } - - /** - * Checks whether an option exists in a given section. Options in the - * common section are assumed to not exist in particular sections, - * unless they're overwritten. - * - * @param section the section's name - * @param option the option's name - * @return true if the given section has the option - */ - public boolean hasOption(String section, String option) { - return hasSection(section) && getSection(section).hasOption(option); - } - - /** - * Checks whether a section with a particular name exists in this instance. - * - * @param name the name of the section - * @return true if the section exists - */ - public boolean hasSection(String name) { - return this.sections.containsKey(normSection(name)); - } - - /** - * Adds a section if it doesn't exist yet. - * - * @param name the name of the section - * @return true if the section didn't already exist - * @throws IllegalArgumentException the name is illegal, ie contains one - * of the characters '[' and ']' or consists only of white space - */ - public boolean addSection(String name) { - String normName = normSection(name); - if (!hasSection(normName)) { - // Section constructor might throw IllegalArgumentException - Section section = new Section(normName, this.commentDelims, - this.isCaseSensitive); - section.setOptionFormat(this.optionFormat); - this.sections.put(normName, section); - this.sectionOrder.add(normName); - return true; - } - else { - return false; - } - } - - /** - * Removes a section if it exists. - * - * @param name the section's name - * @return true if the section actually existed - * @throws IllegalArgumentException when trying to remove the common section - */ - public boolean removeSection(String name) { - String normName = normSection(name); - if (this.commonName != null && this.commonName.equals(normName)) { - throw new IllegalArgumentException("Can't remove common section"); - } - if (hasSection(normName)) { - this.sections.remove(normName); - this.sectionOrder.remove(normName); - return true; - } - else { - return false; - } - } - - /** - * Returns all section names in this instance minus the common section if - * one was defined. - * - * @return list of the section names in original/insertion order - */ - public List sectionNames() { - List sectList = new ArrayList(this.sectionOrder); - if (this.commonName != null) { - sectList.remove(this.commonName); - } - return sectList; - } - - /** - * Returns all option names of a section, not including options from the - * common section. - * - * @param section the section's name - * @return list of option names - * @throws IniEditor.NoSuchSectionException no section with the given name exists - */ - public List optionNames(String section) { - if (hasSection(section)) { - return getSection(section).optionNames(); - } - else { - throw new NoSuchSectionException(section); - } - } - - /** - * Adds a comment line to the end of a section. A comment spanning - * several lines (ie with line breaks) will be split up, one comment - * line for each line. - * - * @param section the section's name - * @param comment the comment - * @throws IniEditor.NoSuchSectionException no section with the given name exists - */ - public void addComment(String section, String comment) { - if (hasSection(section)) { - getSection(section).addComment(comment); - } - else { - throw new NoSuchSectionException(section); - } - } - - /** - * Adds a blank line to the end of a section. - * - * @param section the section's name - * @throws IniEditor.NoSuchSectionException no section with the given name exists - */ - public void addBlankLine(String section) { - if (hasSection(section)) { - getSection(section).addBlankLine(); - } - else { - throw new NoSuchSectionException(section); - } - } - - /** - * Writes this instance in INI format to a file. - * - * @param filename the file to write to - * @throws IOException at an I/O problem - */ - public void save(String filename) throws IOException { - save(new File(filename)); - } - - /** - * Writes this instance in INI format to a file. - * - * @param file where to save to - * @throws IOException at an I/O problem - */ - public void save(File file) throws IOException { - OutputStream out = new FileOutputStream(file); - save(out); - out.close(); - } - - /** - * Writes this instance in INI format to an output stream. This method - * takes an OutputStream for maximum flexibility, internally - * it does of course use a writer for character based output. - * - * @param stream where to write - * @throws IOException at an I/O problem - */ - public void save(OutputStream stream) throws IOException { - save(new OutputStreamWriter(stream)); - } - - /** - * Writes this instance in INI format to an output stream writer. - * - * @param streamWriter where to write - * @throws IOException at an I/O problem - */ - public void save(OutputStreamWriter streamWriter) throws IOException { - Iterator it = this.sectionOrder.iterator(); - PrintWriter writer = new PrintWriter(streamWriter, true); - while (it.hasNext()) { - Section sect = getSection(it.next()); - writer.println(sect.header()); - sect.save(writer); - } - } - - /** - * Loads INI formatted input from a file into this instance, using the - * default character encoding. Everything in the file before the first - * section header is ignored. - * - * @param filename file to read from - * @throws IOException at an I/O problem - */ - public void load(String filename) throws IOException { - load(new File(filename)); - } - - /** - * Loads INI formatted input from a file into this instance, using the - * default character encoding. Everything in the file before the first - * section header is ignored. - * - * @param file file to read from - * @throws IOException at an I/O problem - */ - public void load(File file) throws IOException { - InputStream in = new FileInputStream(file); - load(in); - in.close(); - } - - /** - * Loads INI formatted input from a stream into this instance, using the - * default character encoding. This method takes an InputStream - * for maximum flexibility, internally it does use a reader (using the - * default character encoding) for character based input. Everything in the - * stream before the first section header is ignored. - * - * @param stream where to read from - * @throws IOException at an I/O problem - */ - public void load(InputStream stream) throws IOException { - load(new InputStreamReader(stream)); - } - - /** - * Loads INI formatted input from a stream reader into this instance. - * Everything in the stream before the first section header is ignored. - * - * @param streamReader where to read from - * @throws IOException at an I/O problem - */ - public void load(InputStreamReader streamReader) throws IOException { - BufferedReader reader = new BufferedReader(streamReader); - String curSection = null; - String line = null; - - while (reader.ready()) { - line = reader.readLine().trim(); - if (line.length() > 0 && line.charAt(0) == Section.HEADER_START) { - int endIndex = line.indexOf(Section.HEADER_END); - if (endIndex >= 0) { - curSection = line.substring(1, endIndex); - addSection(curSection); - } - } - if (curSection != null) { - Section sect = getSection(curSection); - sect.load(reader); - } - } - } - - /** - * Returns a section by name or null if not found. - * - * @param name the section's name - * @return the section - */ - private Section getSection(String name) { - return sections.get(normSection(name)); - } - - /** - * Normalizes an arbitrary string for use as a section name. Currently - * only makes the string lower-case (provided this instance isn't case- - * sensitive) and trims leading and trailing white space. Note that - * normalization isn't enforced by the Section class. - * - * @param name the string to be used as section name - * @return a normalized section name - */ - private String normSection(String name) { - if (!this.isCaseSensitive) { - name = name.toLowerCase(); - } - return name.trim(); - } - - private static String[] toStringArray(Collection coll) { - Object[] objArray = coll.toArray(); - String[] strArray = new String[objArray.length]; - for (int i = 0; i < objArray.length; i++) { - strArray[i] = (String)objArray[i]; - } - return strArray; - } - - /** - * Loads, edits and saves a section of an INI-style configuration file. This - * class does actually belong to the internals of {@link IniEditor} and - * should rarely ever be used directly. It's exposed because it can be - * useful for plain, section-less configuration files (Java-style - * properties, for example). - */ - public static class Section { - - private String name; - private Map options; - private List lines; - private char[] optionDelims; - private char[] optionDelimsSorted; - private char[] commentDelims; - private char[] commentDelimsSorted; - private boolean isCaseSensitive; - private OptionFormat optionFormat; - - private static final char[] DEFAULT_OPTION_DELIMS - = new char[] {'=', ':'}; - private static final char[] DEFAULT_COMMENT_DELIMS - = new char[] {'#', ';'}; - private static final char[] OPTION_DELIMS_WHITESPACE - = new char[] {' ', '\t'}; - private static final boolean DEFAULT_CASE_SENSITIVITY = false; - public static final String DEFAULT_OPTION_FORMAT = "%s %s %s"; - - public static final char HEADER_START = '['; - public static final char HEADER_END = ']'; - private static final int NAME_MAXLENGTH = 1024; - private static final char[] INVALID_NAME_CHARS = {HEADER_START, HEADER_END}; - - /** - * Constructs a new section. - * - * @param name the section's name - * @throws IllegalArgumentException the section's name is illegal - */ - public Section(String name) { - this(name, null); - } - - /** - * Constructs a new section, specifying case-sensitivity. - * - * @param name the section's name - * @param isCaseSensitive option names are case-sensitive if this is true - * @throws IllegalArgumentException the section's name is illegal - */ - public Section(String name, boolean isCaseSensitive) { - this(name, null, isCaseSensitive); - } - - /** - * Constructs a new section, defining comment delimiters. - * - * @param name the section's name - * @param delims an array of characters to be recognized as starters of - * comment lines; the first of them will be used for newly created - * comments - * @throws IllegalArgumentException the section's name is illegal - */ - public Section(String name, char[] delims) { - this(name, delims, DEFAULT_CASE_SENSITIVITY); - } - - /** - * Constructs a new section, defining comment delimiters. - * - * @param name the section's name - * @param delims an array of characters to be recognized as starters of - * comment lines; the first of them will be used for newly created - * comments - * @param isCaseSensitive option names are case-sensitive if this is true - * @throws IllegalArgumentException the section's name is illegal - */ - public Section(String name, char[] delims, boolean isCaseSensitive) { - if (!validName(name)) { - throw new IllegalArgumentException("Illegal section name:" + name); - } - this.name = name; - this.isCaseSensitive = isCaseSensitive; - this.options = new HashMap(); - this.lines = new LinkedList(); - this.optionDelims = DEFAULT_OPTION_DELIMS; - this.commentDelims = (delims == null ? DEFAULT_COMMENT_DELIMS : delims); - this.optionFormat = new OptionFormat(DEFAULT_OPTION_FORMAT); - // sorting so we can later use binary search - this.optionDelimsSorted = new char[this.optionDelims.length]; - System.arraycopy(this.optionDelims, 0, this.optionDelimsSorted, 0, this.optionDelims.length); - this.commentDelimsSorted = new char[this.commentDelims.length]; - System.arraycopy(this.commentDelims, 0, this.commentDelimsSorted, 0, this.commentDelims.length); - Arrays.sort(this.optionDelimsSorted); - Arrays.sort(this.commentDelimsSorted); - } - - /** - * Sets the option format for this section to the given string. Options - * in this section will be rendered according to the given format - * string. The string must contain %s three times, these - * will be replaced with the option name, the option separator and the - * option value in this order. Literal percentage signs must be escaped - * by preceding them with another percentage sign (i.e., %% - * corresponds to one percentage sign). The default format string is - * "%s %s %s". - * - * Option formats may look like format strings as supported by Java 1.5, - * but the string is in fact parsed in a custom fashion to guarantee - * backwards compatibility. So don't try clever stuff like using format - * conversion types other than %s. - * - * @param formatString a format string, containing %s - * exactly three times - * @throws IllegalArgumentException if the format string is illegal - */ - public void setOptionFormatString(String formatString) { - this.setOptionFormat(new OptionFormat(formatString)); - } - - /** - * Sets the option format for this section. Options will be rendered - * according to the given format when printed. - * - * @param format a compiled option format - */ - public void setOptionFormat(OptionFormat format) { - this.optionFormat = format; - } - - /** - * Returns the names of all options in this section. - * - * @return list of names of this section's options in - * original/insertion order - */ - public List optionNames() { - List optNames = new LinkedList(); - Iterator it = this.lines.iterator(); - while (it.hasNext()) { - Line line = it.next(); - if (line instanceof Option) { - optNames.add(((Option)line).name()); - } - } - return optNames; - } - - /** - * Checks whether a given option exists in this section. - * - * @param name the name of the option to test for - * @return true if the option exists in this section - */ - public boolean hasOption(String name) { - return this.options.containsKey(normOption(name)); - } - - /** - * Returns an option's value. - * - * @param option the name of the option - * @return the requested option's value or null if no - * option with the specified name exists - */ - public String get(String option) { - String normed = normOption(option); - if (hasOption(normed)) { - return getOption(normed).value(); - } - return null; - } - - /** - * Sets an option's value and creates the option if it doesn't exist. - * - * @param option the option's name - * @param value the option's value - * @throws IllegalArgumentException the option name is illegal, - * ie contains a '=' character or consists only of white space - */ - public void set(String option, String value) { - set(option, value, this.optionDelims[0]); - } - - /** - * Sets an option's value and creates the option if it doesn't exist. - * - * @param option the option's name - * @param value the option's value - * @param delim the delimiter between name and value for this option - * @throws IllegalArgumentException the option name is illegal, - * ie contains a '=' character or consists only of white space - */ - public void set(String option, String value, char delim) { - String normed = normOption(option); - if (hasOption(normed)) { - getOption(normed).set(value); - } - else { - // Option constructor might throw IllegalArgumentException - Option opt = new Option(normed, value, delim, this.optionFormat); - this.options.put(normed, opt); - this.lines.add(opt); - } - } - - /** - * Removes an option if it exists. - * - * @param option the name of the option - * @return true if the option was actually removed - */ - public boolean remove(String option) { - String normed = normOption(option); - if (hasOption(normed)) { - this.lines.remove(getOption(normed)); - this.options.remove(normed); - return true; - } - else { - return false; - } - } - - /** - * Adds a comment line to the end of this section. A comment spanning - * several lines (ie with line breaks) will be split up, one comment - * line for each line. - * - * @param comment the comment - */ - public void addComment(String comment) { - addComment(comment, this.commentDelims[0]); - } - - /** - * Adds a comment line to the end of this section. A comment spanning - * several lines (ie with line breaks) will be split up, one comment - * line for each line. - * - * @param comment the comment - * @param delim the delimiter used to mark the start of this comment - */ - public void addComment(String comment, char delim) { - StringTokenizer st = new StringTokenizer(comment.trim(), NEWLINE_CHARS); - while (st.hasMoreTokens()) { - this.lines.add(new Comment(st.nextToken(), delim)); - } - } - private static final String NEWLINE_CHARS = "\n\r"; - - /** - * Adds a blank line to the end of this section. - */ - public void addBlankLine() { - this.lines.add(BLANK_LINE); - } - - /** - * Loads options from a reader into this instance. Will read from the - * stream until it hits a section header, ie a '[' character, and resets - * the reader to point to this character. - * - * @param reader where to read from - * @throws IOException at an I/O problem - */ - public void load(BufferedReader reader) throws IOException { - while (reader.ready()) { - reader.mark(NAME_MAXLENGTH); - String line = reader.readLine().trim(); - - // Check for section header - if (line.length() > 0 && line.charAt(0) == HEADER_START) { - reader.reset(); - return; - } - - int delimIndex = -1; - // blank line - if (line.equals("")) { - this.addBlankLine(); - } - // comment line - else if ((delimIndex = Arrays.binarySearch(this.commentDelimsSorted, line.charAt(0))) >= 0) { - addComment(line.substring(1), this.commentDelimsSorted[delimIndex]); - } - // option line - else { - delimIndex = -1; - int delimNum = -1; - int lastSpaceIndex = -1; - for (int i = 0, l = line.length(); i < l && delimIndex < 0; i++) { - delimNum = Arrays.binarySearch(this.optionDelimsSorted, line.charAt(i)); - if (delimNum >= 0) { - delimIndex = i; - } - else { - boolean isSpace = Arrays.binarySearch( - this.OPTION_DELIMS_WHITESPACE, line.charAt(i)) >= 0; - if (!isSpace && lastSpaceIndex >= 0) { - break; - } - else if (isSpace) { - lastSpaceIndex = i; - } - } - } - // delimiter at start of line - if (delimIndex == 0) { - // XXX what's a man got to do? - } - // no delimiter found - else if (delimIndex < 0) { - if (lastSpaceIndex < 0) { - this.set(line, ""); - } - else { - this.set(line.substring(0, lastSpaceIndex) - , line.substring(lastSpaceIndex+1)); - } - } - // delimiter found - else { - this.set(line.substring(0, delimIndex) - , line.substring(delimIndex+1), line.charAt(delimIndex)); - } - } - } - } - - /** - * Prints this section to a print writer. - * - * @param writer where to write - * @throws IOException at an I/O problem - */ - public void save(PrintWriter writer) throws IOException { - Iterator it = this.lines.iterator(); - while (it.hasNext()) { - writer.println(it.next().toString()); - } - if (writer.checkError()) { - throw new IOException(); - } - } - - /** - * Returns an actual Option instance. - * - * @param option the name of the option, assumed to be normed already (!) - * @return the requested Option instance - * @throws NullPointerException if no option with the specified name exists - */ - private Option getOption(String name) { - return this.options.get(name); - } - - /** - * Returns the bracketed header of this section as appearing in an - * actual INI file. - * - * @return the section's name in brackets - */ - private String header() { - return HEADER_START + this.name + HEADER_END; - } - - /** - * Checks a string for validity as a section name. It can't contain the - * characters '[' and ']'. An empty string or one consisting only of - * white space isn't allowed either. - * - * @param name the name to validate - * @return true if the name validates as a section name - */ - private static boolean validName(String name) { - if (name.trim().equals("")) { - return false; - } - for (int i = 0; i < INVALID_NAME_CHARS.length; i++) { - if (name.indexOf(INVALID_NAME_CHARS[i]) >= 0) { - return false; - } - } - return true; - } - - /** - * Normalizes an arbitrary string for use as an option name, ie makes - * it lower-case (provided this section isn't case-sensitive) and trims - * leading and trailing white space. - * - * @param name the string to be used as option name - * @return a normalized option name - */ - private String normOption(String name) { - if (!this.isCaseSensitive) { - name = name.toLowerCase(); - } - return name.trim(); - } - - } - - private interface Line { - public String toString(); - } - - private static final Line BLANK_LINE = new Line() { - public String toString() { return ""; } - }; - - private static class Option implements Line { - - private String name; - private String value; - private char separator; - private OptionFormat format; - - private static final String ILLEGAL_VALUE_CHARS = "\n\r"; - - public Option(String name, String value, char separator, OptionFormat format) { - if (!validName(name, separator)) { - throw new IllegalArgumentException("Illegal option name:" + name); - } - this.name = name; - this.separator = separator; - this.format = format; - set(value); - } - - public String name() { - return this.name; - } - - public String value() { - return this.value; - } - - public void set(String value) { - if (value == null) { - this.value = value; - } - else { - StringTokenizer st = new StringTokenizer(value.trim(), - ILLEGAL_VALUE_CHARS); - StringBuffer sb = new StringBuffer(); - // XXX this might not be particularly efficient - while (st.hasMoreTokens()) { - sb.append(st.nextToken()); - } - this.value = sb.toString(); - } - } - - public String toString() { - return this.format.format(this.name, this.value, this.separator); - } - - private static boolean validName(String name, char separator) { - if (name.trim().equals("")) { - return false; - } - if (name.indexOf(separator) >= 0) { - return false; - } - return true; - } - - } - - private static class Comment implements Line { - - private String comment; - private char delimiter; - - private static final char DEFAULT_DELIMITER = '#'; - - public Comment(String comment) { - this(comment, DEFAULT_DELIMITER); - } - - public Comment(String comment, char delimiter) { - this.comment = comment.trim(); - this.delimiter = delimiter; - } - - public String toString() { - return this.delimiter + " " + this.comment; - } - - } - - private static class OptionFormat { - - private static final int EXPECTED_TOKENS = 4; - - private String[] formatTokens; - - public OptionFormat(String formatString) { - this.formatTokens = this.compileFormat(formatString); - } - - public String format(String name, String value, char separator) { - String[] t = this.formatTokens; - return t[0] + name + t[1] + separator + t[2] + value + t[3]; - } - - private String[] compileFormat(String formatString) { - String[] tokens = {"", "", "", ""}; - int tokenCount = 0; - boolean seenPercent = false; - StringBuffer token = new StringBuffer(); - for (int i = 0; i < formatString.length(); i++) { - switch (formatString.charAt(i)) { - case '%': - if (seenPercent) { - token.append("%"); - seenPercent = false; - } - else { - seenPercent = true; - } - break; - case 's': - if (seenPercent) { - if (tokenCount >= EXPECTED_TOKENS) { - throw new IllegalArgumentException( - "Illegal option format. Too many %s placeholders."); - } - tokens[tokenCount] = token.toString(); - tokenCount++; - token = new StringBuffer(); - seenPercent = false; - } - else { - token.append("s"); - } - break; - default: - if (seenPercent) { - throw new IllegalArgumentException( - "Illegal option format. Unknown format specifier."); - } - token.append(formatString.charAt(i)); - break; - } - } - if (tokenCount != EXPECTED_TOKENS - 1) { - throw new IllegalArgumentException( - "Illegal option format. Not enough %s placeholders."); - } - tokens[tokenCount] = token.toString(); - return tokens; - } - - } - - /** - * Thrown when an non-existent section is addressed. - */ - public static class NoSuchSectionException extends RuntimeException { - public NoSuchSectionException() { super(); } - public NoSuchSectionException(String msg) { super(msg); } - } - -} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 64b44b1..793a9ae 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -52,7 +52,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" - android:textAppearance="@android:style/TextAppearance.Material.Large" + android:textAppearance="@android:style/TextAppearance.Large" android:textColor="#DFDFDF" /> @@ -100,7 +100,7 @@ android:layout_height="wrap_content" android:clickable="false" android:text="@string/services_socks_proxy" - android:textAppearance="@android:style/TextAppearance.Material.Small" + android:textAppearance="@android:style/TextAppearance.Small" android:textColor="#DFDFDF" /> @@ -115,7 +115,7 @@ android:layout_height="wrap_content" android:clickable="false" android:text="@string/services_bob" - android:textAppearance="@android:style/TextAppearance.Material.Small" + android:textAppearance="@android:style/TextAppearance.Small" android:textColor="#DFDFDF" /> @@ -130,7 +130,7 @@ android:layout_height="wrap_content" android:clickable="false" android:text="@string/services_sam" - android:textAppearance="@android:style/TextAppearance.Material.Small" + android:textAppearance="@android:style/TextAppearance.Small" android:textColor="#DFDFDF" /> @@ -145,11 +145,11 @@ android:layout_height="wrap_content" android:clickable="false" android:text="@string/services_i2cp" - android:textAppearance="@android:style/TextAppearance.Material.Small" + android:textAppearance="@android:style/TextAppearance.Small" android:textColor="#DFDFDF" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index c44b456..f2e5a61 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -28,6 +28,18 @@ android:layout_width="match_parent" android:layout_height="1dp" android:background="?android:attr/listDivider" /> + + + +