Commit 11dd7112 authored by Mygod's avatar Mygod

Implement plugin entirely in Kotlin

parent 612a34a6
......@@ -39,7 +39,6 @@ import android.support.v7.app.AppCompatActivity
import android.support.v7.content.res.AppCompatResources
import android.support.v7.preference.PreferenceDataStore
import android.support.v7.widget.TooltipCompat
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.TextView
......@@ -355,7 +354,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
}
else -> null
}
if (TextUtils.isEmpty(sharedStr)) return
if (sharedStr.isNullOrEmpty()) return
val profiles = Profile.findAll(sharedStr).toList()
if (profiles.isEmpty()) {
Snackbar.make(findViewById(R.id.snackbar), R.string.profile_invalid_input, Snackbar.LENGTH_LONG).show()
......
......@@ -143,7 +143,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean = try {
val selected = pluginConfiguration.selected
pluginConfiguration = PluginConfiguration(pluginConfiguration.pluginsOptions +
(pluginConfiguration.selected to PluginOptions(selected, newValue as String?)), selected)
(pluginConfiguration.selected to PluginOptions(selected, newValue as? String?)), selected)
DataStore.plugin = pluginConfiguration.toString()
DataStore.dirty = true
true
......
......@@ -29,7 +29,6 @@ import android.os.Build
import android.os.IBinder
import android.os.RemoteCallbackList
import android.support.v4.os.UserManagerCompat
import android.text.TextUtils
import android.util.Base64
import android.util.Log
import android.widget.Toast
......@@ -218,7 +217,7 @@ object BaseService {
fun onBind(intent: Intent): IBinder? = if (intent.action == Action.SERVICE) data.binder else null
fun checkProfile(profile: Profile): Boolean =
if (TextUtils.isEmpty(profile.host) || TextUtils.isEmpty(profile.password)) {
if (profile.host.isEmpty() || profile.password.isEmpty()) {
stopRunner(true, (this as Context).getString(R.string.proxy_empty))
false
} else true
......
......@@ -50,7 +50,7 @@ class PluginConfiguration(val pluginsOptions: Map<String, PluginOptions>, val se
})
fun getOptions(id: String): PluginOptions = if (id.isEmpty()) PluginOptions() else
pluginsOptions.get(id) ?: PluginOptions(id, PluginManager.fetchPlugins()[id]?.defaultConfig)
pluginsOptions[id] ?: PluginOptions(id, PluginManager.fetchPlugins()[id]?.defaultConfig)
val selectedOptions: PluginOptions get() = getOptions(selected)
override fun toString(): String {
......
......@@ -18,35 +18,33 @@
* *
*******************************************************************************/
package com.github.shadowsocks.plugin;
package com.github.shadowsocks.plugin
/**
* The contract between the plugin provider and host. Contains definitions for the supported actions, extras, etc.
*
* This class is written in Java to keep Java interoperability.
*/
public final class PluginContract {
private PluginContract() { }
object PluginContract {
/**
* ContentProvider Action: Used for NativePluginProvider.
*
* Constant Value: "com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"
*/
public static final String ACTION_NATIVE_PLUGIN = "com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN";
const val ACTION_NATIVE_PLUGIN = "com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"
/**
* Activity Action: Used for ConfigurationActivity.
*
* Constant Value: "com.github.shadowsocks.plugin.ACTION_CONFIGURE"
*/
public static final String ACTION_CONFIGURE = "com.github.shadowsocks.plugin.ACTION_CONFIGURE";
const val ACTION_CONFIGURE = "com.github.shadowsocks.plugin.ACTION_CONFIGURE"
/**
* Activity Action: Used for HelpActivity or HelpCallback.
*
* Constant Value: "com.github.shadowsocks.plugin.ACTION_HELP"
*/
public static final String ACTION_HELP = "com.github.shadowsocks.plugin.ACTION_HELP";
const val ACTION_HELP = "com.github.shadowsocks.plugin.ACTION_HELP"
/**
* The lookup key for a string that provides the plugin entry binary.
......@@ -55,7 +53,7 @@ public final class PluginContract {
*
* Constant Value: "com.github.shadowsocks.plugin.EXTRA_ENTRY"
*/
public static final String EXTRA_ENTRY = "com.github.shadowsocks.plugin.EXTRA_ENTRY";
const val EXTRA_ENTRY = "com.github.shadowsocks.plugin.EXTRA_ENTRY"
/**
* The lookup key for a string that provides the options as a string.
*
......@@ -63,34 +61,34 @@ public final class PluginContract {
*
* Constant Value: "com.github.shadowsocks.plugin.EXTRA_OPTIONS"
*/
public static final String EXTRA_OPTIONS = "com.github.shadowsocks.plugin.EXTRA_OPTIONS";
const val EXTRA_OPTIONS = "com.github.shadowsocks.plugin.EXTRA_OPTIONS"
/**
* The lookup key for a CharSequence that provides user relevant help message.
*
* Example: "obfs=<http|tls> Enable obfuscating: HTTP or TLS (Experimental).
* obfs-host=<host_name> Hostname for obfuscating (Experimental)."
* Example: "obfs=<http></http>|tls> Enable obfuscating: HTTP or TLS (Experimental).
* obfs-host=<host_name> Hostname for obfuscating (Experimental)."
*
* Constant Value: "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE"
*/
public static final String EXTRA_HELP_MESSAGE = "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE";
</host_name> */
const val EXTRA_HELP_MESSAGE = "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE"
/**
* The metadata key to retrieve plugin id. Required for plugins.
*
* Constant Value: "com.github.shadowsocks.plugin.id"
*/
public static final String METADATA_KEY_ID = "com.github.shadowsocks.plugin.id";
const val METADATA_KEY_ID = "com.github.shadowsocks.plugin.id"
/**
* The metadata key to retrieve default configuration. Default value is empty.
*
* Constant Value: "com.github.shadowsocks.plugin.default_config"
*/
public static final String METADATA_KEY_DEFAULT_CONFIG = "com.github.shadowsocks.plugin.default_config";
const val METADATA_KEY_DEFAULT_CONFIG = "com.github.shadowsocks.plugin.default_config"
public static final String METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable";
const val METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable"
/** ConfigurationActivity result: fallback to manual edit mode. */
public static final int RESULT_FALLBACK = 1;
/** ConfigurationActivity result: fallback to manual edit mode. */
const val RESULT_FALLBACK = 1
/**
* Relative to the file to be copied. This column is required.
......@@ -99,7 +97,7 @@ public final class PluginContract {
*
* Type: String
*/
public static final String COLUMN_PATH = "path";
const val COLUMN_PATH = "path"
/**
* File mode bits. Default value is "644".
*
......@@ -107,14 +105,14 @@ public final class PluginContract {
*
* Type: String
*/
public static final String COLUMN_MODE = "mode";
const val COLUMN_MODE = "mode"
/**
* The scheme for general plugin actions.
*/
public static final String SCHEME = "plugin";
const val SCHEME = "plugin"
/**
* The authority for general plugin actions.
*/
public static final String AUTHORITY = "com.github.shadowsocks";
const val AUTHORITY = "com.github.shadowsocks"
}
......@@ -18,110 +18,87 @@
* *
*******************************************************************************/
package com.github.shadowsocks.plugin;
package com.github.shadowsocks.plugin
import android.text.TextUtils;
import java.util.HashMap;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.*
/**
* Helper class for processing plugin options.
*
* Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java
*/
public final class PluginOptions extends HashMap<String, String> {
public PluginOptions() {
super();
}
public PluginOptions(int initialCapacity) {
super(initialCapacity);
}
public PluginOptions(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
class PluginOptions : HashMap<String, String?> {
var id = ""
// TODO: this method is not needed since API 24
public String getOrDefault(Object key, String defaultValue) {
String v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
constructor() : super()
constructor(initialCapacity: Int) : super(initialCapacity)
constructor(initialCapacity: Int, loadFactor: Float) : super(initialCapacity, loadFactor)
private PluginOptions(String options, boolean parseId) {
this();
if (TextUtils.isEmpty(options)) return;
final StringTokenizer tokenizer = new StringTokenizer(options + ';', "\\=;", true);
final StringBuilder current = new StringBuilder();
String key = null;
private constructor(options: String?, parseId: Boolean) : this() {
@Suppress("NAME_SHADOWING")
var parseId = parseId
if (options.isNullOrEmpty()) return
val tokenizer = StringTokenizer(options + ';', "\\=;", true)
val current = StringBuilder()
var key: String? = null
while (tokenizer.hasMoreTokens()) {
String nextToken = tokenizer.nextToken();
if ("\\".equals(nextToken)) current.append(tokenizer.nextToken());
else if ("=".equals(nextToken) && key == null) {
key = current.toString();
current.setLength(0);
} else if (";".equals(nextToken)) {
if (key != null) {
put(key, current.toString());
key = null;
} else if (current.length() > 0)
if (parseId) id = current.toString(); else put(current.toString(), null);
current.setLength(0);
parseId = false;
} else current.append(nextToken);
val nextToken = tokenizer.nextToken()
when (nextToken) {
"\\" -> current.append(tokenizer.nextToken())
"=" -> if (key == null) {
key = current.toString()
current.setLength(0)
} else current.append(nextToken)
";" -> {
if (key != null) {
put(key, current.toString())
key = null
} else if (current.isNotEmpty())
if (parseId) id = current.toString() else put(current.toString(), null)
current.setLength(0)
parseId = false
}
else -> current.append(nextToken)
}
}
}
public PluginOptions(String options) {
this(options, true);
}
public PluginOptions(String id, String options) {
this(options, false);
this.id = id;
}
public String id = "";
constructor(options: String?) : this(options, true)
constructor(id: String, options: String?) : this(options, false) {
this.id = id
}
private static void append(StringBuilder result, String str) {
for (int i = 0; i < str.length(); ++i) {
char ch = str.charAt(i);
switch (ch) {
case '\\': case '=': case ';': result.append('\\'); // intentionally no break
default: result.append(ch);
private fun append(result: StringBuilder, str: String) = (0 until str.length)
.map { str[it] }
.forEach {
when (it) {
'\\', '=', ';' -> {
result.append('\\') // intentionally no break
result.append(it)
}
else -> result.append(it)
}
}
}
}
public String toString(boolean trimId) {
final StringBuilder result = new StringBuilder();
if (!trimId) if (TextUtils.isEmpty(id)) return ""; else append(result, id);
for (Entry<String, String> entry : entrySet()) {
if (result.length() > 0) result.append(';');
append(result, entry.getKey());
String value = entry.getValue();
fun toString(trimId: Boolean): String {
val result = StringBuilder()
if (!trimId) if (id.isEmpty()) return "" else append(result, id)
for ((key, value) in entries) {
if (result.isNotEmpty()) result.append(';')
append(result, key)
if (value != null) {
result.append('=');
append(result, value);
result.append('=')
append(result, value)
}
}
return result.toString();
}
@Override
public String toString() {
return toString(true);
return result.toString()
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
PluginOptions that = (PluginOptions) o;
return Objects.equals(id, that.id) && super.equals(that);
}
override fun toString(): String = toString(true)
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), id);
override fun equals(other: Any?): Boolean {
if (this === other) return true
return javaClass == other?.javaClass && super.equals(other) && id == (other as PluginOptions).id
}
override fun hashCode(): Int = Objects.hash(super.hashCode(), id)
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment