@@ -0,0 +1,146 @@
|
||||
package com.github.shadowsocks.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* Commandline objects help handling command lines specifying processes to
|
||||
* execute.
|
||||
*
|
||||
* The class can be used to define a command line as nested elements or as a
|
||||
* helper to define a command line by an application.
|
||||
* <p>
|
||||
* <code>
|
||||
* <someelement><br>
|
||||
* <acommandline executable="/executable/to/run"><br>
|
||||
* <argument value="argument 1" /><br>
|
||||
* <argument line="argument_1 argument_2 argument_3" /><br>
|
||||
* <argument value="argument 4" /><br>
|
||||
* </acommandline><br>
|
||||
* </someelement><br>
|
||||
* </code>
|
||||
*
|
||||
* Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java
|
||||
*
|
||||
* Adds support for escape character '\'.
|
||||
*
|
||||
* @author Mygod
|
||||
*/
|
||||
public final class Commandline {
|
||||
private Commandline() { }
|
||||
|
||||
/**
|
||||
* Quote the parts of the given array in way that makes them
|
||||
* usable as command line arguments.
|
||||
* @param line the list of arguments to quote.
|
||||
* @return empty string for null or no command, else every argument split
|
||||
* by spaces and quoted by quoting rules.
|
||||
*/
|
||||
public static String toString(String[] line) {
|
||||
// empty path return empty string
|
||||
if (line == null || line.length == 0) {
|
||||
return "";
|
||||
}
|
||||
// path containing one or more elements
|
||||
final StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < line.length; i++) {
|
||||
if (i > 0) {
|
||||
result.append(' ');
|
||||
}
|
||||
for (int j = 0; j < line[i].length(); ++j) {
|
||||
char ch = line[i].charAt(j);
|
||||
switch (ch) {
|
||||
case ' ': case '\\': case '"': case '\'': result.append('\\'); // intentionally no break
|
||||
default: result.append(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crack a command line.
|
||||
* @param toProcess the command line to process.
|
||||
* @return the command line broken into strings.
|
||||
* An empty or null toProcess parameter results in a zero sized array.
|
||||
*/
|
||||
public static String[] translateCommandline(String toProcess) {
|
||||
if (toProcess == null || toProcess.length() == 0) {
|
||||
//no command? no string
|
||||
return new String[0];
|
||||
}
|
||||
// parse with a simple finite state machine
|
||||
|
||||
final int normal = 0;
|
||||
final int inQuote = 1;
|
||||
final int inDoubleQuote = 2;
|
||||
int state = normal;
|
||||
final StringTokenizer tok = new StringTokenizer(toProcess, "\\\"\' ", true);
|
||||
final ArrayList<String> result = new ArrayList<>();
|
||||
final StringBuilder current = new StringBuilder();
|
||||
boolean lastTokenHasBeenQuoted = false;
|
||||
boolean lastTokenIsSlash = false;
|
||||
|
||||
while (tok.hasMoreTokens()) {
|
||||
String nextTok = tok.nextToken();
|
||||
switch (state) {
|
||||
case inQuote:
|
||||
if ("\'".equals(nextTok)) {
|
||||
lastTokenHasBeenQuoted = true;
|
||||
state = normal;
|
||||
} else {
|
||||
current.append(nextTok);
|
||||
}
|
||||
break;
|
||||
case inDoubleQuote:
|
||||
if ("\"".equals(nextTok)) {
|
||||
if (lastTokenIsSlash) {
|
||||
current.append(nextTok);
|
||||
lastTokenIsSlash = false;
|
||||
} else {
|
||||
lastTokenHasBeenQuoted = true;
|
||||
state = normal;
|
||||
}
|
||||
} else if ("\\".equals(nextTok)) {
|
||||
if (lastTokenIsSlash) {
|
||||
current.append(nextTok);
|
||||
lastTokenIsSlash = false;
|
||||
} else lastTokenIsSlash = true;
|
||||
} else {
|
||||
if (lastTokenIsSlash) {
|
||||
current.append("\\"); // unescaped
|
||||
lastTokenIsSlash = false;
|
||||
}
|
||||
current.append(nextTok);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (lastTokenIsSlash) {
|
||||
current.append(nextTok);
|
||||
lastTokenIsSlash = false;
|
||||
} else if ("\\".equals(nextTok)) lastTokenIsSlash = true; else if ("\'".equals(nextTok)) {
|
||||
state = inQuote;
|
||||
} else if ("\"".equals(nextTok)) {
|
||||
state = inDoubleQuote;
|
||||
} else if (" ".equals(nextTok)) {
|
||||
if (lastTokenHasBeenQuoted || current.length() != 0) {
|
||||
result.add(current.toString());
|
||||
current.setLength(0);
|
||||
}
|
||||
} else {
|
||||
current.append(nextTok);
|
||||
}
|
||||
lastTokenHasBeenQuoted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lastTokenHasBeenQuoted || current.length() != 0) {
|
||||
result.add(current.toString());
|
||||
}
|
||||
if (state == inQuote || state == inDoubleQuote) {
|
||||
throw new IllegalArgumentException("unbalanced quotes in " + toProcess);
|
||||
}
|
||||
if (lastTokenIsSlash) throw new IllegalArgumentException("escape character following nothing in " + toProcess);
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,8 @@ package com.github.shadowsocks.utils
|
||||
import java.io.File
|
||||
import java.net._
|
||||
import java.security.MessageDigest
|
||||
import java.util.{Scanner, StringTokenizer}
|
||||
import java.util.Scanner
|
||||
|
||||
import android.animation.{Animator, AnimatorListenerAdapter}
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.{Context, Intent}
|
||||
import android.graphics._
|
||||
@@ -39,7 +38,6 @@ import com.github.shadowsocks.{BuildConfig, ShadowsocksRunnerService}
|
||||
import org.xbill.DNS._
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{Failure, Try}
|
||||
@@ -98,48 +96,6 @@ object Utils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crack a command line.
|
||||
* Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java#L471
|
||||
* @param toProcess the command line to process.
|
||||
* @return the command line broken into strings.
|
||||
* An empty or null toProcess parameter results in a zero sized ArrayBuffer.
|
||||
*/
|
||||
def translateCommandline(toProcess: String): ArrayBuffer[String] = {
|
||||
if (toProcess == null || toProcess.length == 0) return ArrayBuffer[String]()
|
||||
val tok = new StringTokenizer(toProcess, "\"' ", true)
|
||||
val result = ArrayBuffer[String]()
|
||||
val current = new StringBuilder()
|
||||
var quote = ' '
|
||||
var last = " "
|
||||
while (tok.hasMoreTokens) {
|
||||
val nextTok = tok.nextToken
|
||||
quote match {
|
||||
case '\'' => nextTok match {
|
||||
case "'" => quote = ' '
|
||||
case _ => current.append(nextTok)
|
||||
}
|
||||
case '"' => nextTok match {
|
||||
case "\"" => quote = ' '
|
||||
case _ => current.append(nextTok)
|
||||
}
|
||||
case _ => nextTok match {
|
||||
case "'" => quote = '\''
|
||||
case "\"" => quote = '"'
|
||||
case " " => if (last != " ") {
|
||||
result.append(current.toString)
|
||||
current.setLength(0)
|
||||
}
|
||||
case _ => current.append(nextTok)
|
||||
}
|
||||
}
|
||||
last = nextTok
|
||||
}
|
||||
if (current.nonEmpty) result.append(current.toString)
|
||||
if (quote == '\'' || quote == '"') throw new Exception("Unbalanced quotes in " + toProcess)
|
||||
result
|
||||
}
|
||||
|
||||
def resolve(host: String, addrType: Int): Option[String] = {
|
||||
try {
|
||||
val lookup = new Lookup(host, addrType)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.github.shadowsocks.plugin;
|
||||
|
||||
/**
|
||||
* This class provides String constants that will be used in plugin interfaces.
|
||||
*
|
||||
* This class is written in Java to keep Java interoperability.
|
||||
*
|
||||
* @author Mygod
|
||||
*/
|
||||
public final class PluginInterface {
|
||||
private PluginInterface() { }
|
||||
|
||||
/**
|
||||
* Should be a NativePluginProvider.
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.CATEGORY_NATIVE_PLUGIN"
|
||||
*/
|
||||
public static final String CATEGORY_NATIVE_PLUGIN = "com.github.shadowsocks.plugin.CATEGORY_NATIVE_PLUGIN";
|
||||
|
||||
/**
|
||||
* The lookup key for a string that provides the whole command as a string.
|
||||
*
|
||||
* Example: "obfs=http;obfs-host=www.baidu.com"
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.EXTRA_OPTIONS"
|
||||
*/
|
||||
public static final String 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)."
|
||||
*
|
||||
* Constant Value: "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE"
|
||||
*/
|
||||
public static final String EXTRA_HELP_MESSAGE = "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE";
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* Relative to the file to be copied. This column is required.
|
||||
*
|
||||
* Example: "kcptun", "doc/help.txt"
|
||||
*
|
||||
* Type: String
|
||||
*/
|
||||
public static final String COLUMN_PATH = "path";
|
||||
|
||||
/**
|
||||
* Authority to use for native plugin ContentProvider.
|
||||
*
|
||||
* @param pluginId Plugin ID.
|
||||
* @return com.github.shadowsocks.plugin.$PLUGIN_ID
|
||||
*/
|
||||
public static String getAuthority(String pluginId) {
|
||||
return "com.github.shadowsocks.plugin." + pluginId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity Action: Used for ConfigurationActivity.
|
||||
*
|
||||
* @param pluginId Plugin ID.
|
||||
* @return com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_CONFIGURE
|
||||
*/
|
||||
public static String ACTION_CONFIGURE(String pluginId) {
|
||||
return getAuthority(pluginId) + ".ACTION_CONFIGURE";
|
||||
}
|
||||
/**
|
||||
* Activity Action: Used for HelpActivity or HelpCallback.
|
||||
*
|
||||
* @param pluginId Plugin ID.
|
||||
* @return com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_HELP
|
||||
*/
|
||||
public static String ACTION_HELP(String pluginId) {
|
||||
return getAuthority(pluginId) + ".ACTION_HELP";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.github.shadowsocks.plugin;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* Helper class for processing plugin options.
|
||||
*
|
||||
* Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java
|
||||
*
|
||||
* @author Mygod
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
public PluginOptions(String options) throws IllegalArgumentException {
|
||||
if (TextUtils.isEmpty(options)) return;
|
||||
final StringTokenizer tokenizer = new StringTokenizer(options, "\\=;", true);
|
||||
final StringBuilder current = new StringBuilder();
|
||||
String key = null;
|
||||
boolean firstEntry = true;
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String nextToken = tokenizer.nextToken();
|
||||
if ("\\".equals(nextToken)) current.append(tokenizer.nextToken());
|
||||
else if ("=".equals(nextToken)) {
|
||||
if (key != null) throw new IllegalArgumentException("Duplicate keys in " + options);
|
||||
key = current.toString();
|
||||
current.setLength(0);
|
||||
} else if (";".equals(nextToken)) {
|
||||
if (current.length() > 0) put(key, current.toString());
|
||||
else if (firstEntry) id = key;
|
||||
else throw new IllegalArgumentException("Value missing in " + options);
|
||||
firstEntry = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
public PluginOptions(String id, String options) throws IllegalArgumentException {
|
||||
this(options);
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
public String toString(boolean trimId) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
if (!trimId && !TextUtils.isEmpty(id)) append(result, id);
|
||||
for (Entry<String, String> entry : entrySet()) if (entry.getValue() != null) {
|
||||
if (result.length() > 0) result.append(';');
|
||||
append(result, entry.getKey());
|
||||
result.append('=');
|
||||
append(result, entry.getValue());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
|
||||
/**
|
||||
* Base class for configuration activity. A configuration activity is started when user wishes to configure the
|
||||
* selected plugin. To create a configuration activity, extend this class, implement abstract methods, invoke
|
||||
* `saveChanges(options)` and `discardChanges()` when appropriate, and add it to your manifest like this:
|
||||
*
|
||||
* <pre class="prettyprint"><manifest>
|
||||
* ...
|
||||
* <application>
|
||||
* ...
|
||||
* <activity android:name="com.github.shadowsocks.$PLUGIN_ID.ConfigureActivity">
|
||||
* <intent-filter>
|
||||
* <action android:name="com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_CONFIGURE" />
|
||||
* </intent-filter>
|
||||
* </activity>
|
||||
* ...
|
||||
* </application>
|
||||
*</manifest></pre>
|
||||
*
|
||||
* @author Mygod
|
||||
*/
|
||||
trait ConfigurationActivity extends OptionsCapableActivity {
|
||||
/**
|
||||
* Equivalent to setResult(RESULT_CANCELED).
|
||||
*/
|
||||
final def discardChanges(): Unit = setResult(Activity.RESULT_CANCELED)
|
||||
|
||||
/**
|
||||
* Equivalent to setResult(RESULT_OK, args_with_correct_format).
|
||||
*
|
||||
* @param options PluginOptions to save.
|
||||
*/
|
||||
final def saveChanges(options: PluginOptions): Unit =
|
||||
setResult(Activity.RESULT_OK, new Intent().putExtra(PluginInterface.EXTRA_OPTIONS, options.toString))
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
/**
|
||||
* Base class for a help activity. A help activity is started when user taps help when configuring options for your
|
||||
* plugin. To create a help activity, just extend this class, and add it to your manifest like this:
|
||||
*
|
||||
* <pre class="prettyprint"><manifest>
|
||||
* ...
|
||||
* <application>
|
||||
* ...
|
||||
* <activity android:name="com.github.shadowsocks.$PLUGIN_ID.HelpActivity">
|
||||
* <intent-filter>
|
||||
* <action android:name="com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_HELP" />
|
||||
* </intent-filter>
|
||||
* </activity>
|
||||
* ...
|
||||
* </application>
|
||||
*</manifest></pre>
|
||||
*
|
||||
* @author Mygod
|
||||
*/
|
||||
trait HelpActivity extends OptionsCapableActivity {
|
||||
// HelpActivity can choose to ignore options
|
||||
override protected def onInitializePluginOptions(options: PluginOptions): Unit = ()
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
|
||||
/**
|
||||
* HelpCallback is an HelpActivity but you just need to produce a CharSequence help message instead of having to
|
||||
* provide UI. To create a help callback, just extend this class, implement abstract methods, and add it to your
|
||||
* manifest following the same procedure as adding a HelpActivity.
|
||||
*
|
||||
* @author Mygod
|
||||
*/
|
||||
trait HelpCallback extends HelpActivity {
|
||||
def produceHelpMessage(options: PluginOptions): CharSequence
|
||||
|
||||
override protected def onInitializePluginOptions(options: PluginOptions): Unit = setResult(Activity.RESULT_OK,
|
||||
new Intent().putExtra(PluginInterface.EXTRA_HELP_MESSAGE, produceHelpMessage(options)))
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.content.{ContentProvider, ContentValues}
|
||||
import android.database.{Cursor, MatrixCursor}
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
|
||||
/**
|
||||
* Base class for a native plugin provider. A native plugin provider offers read-only access to files that are required
|
||||
* to run a plugin, such as binary files and other configuration files. To create a native plugin provider, extend this
|
||||
* class, implement the abstract methods, and add it to your manifest like this:
|
||||
*
|
||||
* <pre class="prettyprint"><manifest>
|
||||
* ...
|
||||
* <application>
|
||||
* ...
|
||||
* <provider android:name="com.github.shadowsocks.$PLUGIN_ID.PluginProvider"
|
||||
* android:authorities="com.github.shadowsocks.$PLUGIN_ID">
|
||||
* <intent-filter>
|
||||
* <category android:name="com.github.shadowsocks.plugin.CATEGORY_NATIVE_PLUGIN" />
|
||||
* </intent-filter>
|
||||
* </provider>
|
||||
* ...
|
||||
* </application>
|
||||
*</manifest></pre>
|
||||
*
|
||||
* @author Mygod
|
||||
*/
|
||||
abstract class NativePluginProvider extends ContentProvider {
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* NativePluginProvider returns application/x-elf by default. It's implementer's responsibility to change this to
|
||||
* correct type.
|
||||
*/
|
||||
override def getType(uri: Uri): String = "application/x-elf"
|
||||
|
||||
override def onCreate(): Boolean = true
|
||||
|
||||
/**
|
||||
* Provide all files needed for native plugin.
|
||||
*
|
||||
* @param provider A helper object to use to add files.
|
||||
*/
|
||||
protected def populateFiles(provider: PathProvider)
|
||||
|
||||
override def query(uri: Uri, projection: Array[String], selection: String, selectionArgs: Array[String],
|
||||
sortOrder: String): Cursor = {
|
||||
if (selection != null || selectionArgs != null || sortOrder != null) ???
|
||||
val result = new MatrixCursor(projection.filter(_ == PluginInterface.COLUMN_PATH))
|
||||
populateFiles(new PathProvider(uri, result))
|
||||
result
|
||||
}
|
||||
|
||||
def openFile(uri: Uri): ParcelFileDescriptor
|
||||
override def openFile(uri: Uri, mode: String): ParcelFileDescriptor = {
|
||||
if (mode != "r") ???
|
||||
openFile(uri)
|
||||
}
|
||||
|
||||
// Methods that should not be used
|
||||
override def update(uri: Uri, values: ContentValues, selection: String, selectionArgs: Array[String]): Int = ???
|
||||
override def insert(uri: Uri, values: ContentValues): Uri = ???
|
||||
override def delete(uri: Uri, selection: String, selectionArgs: Array[String]): Int = ???
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
|
||||
/**
|
||||
* Activity that's capable of getting EXTRA_OPTIONS input.
|
||||
*
|
||||
* @author Mygod
|
||||
*/
|
||||
trait OptionsCapableActivity extends Activity {
|
||||
protected def pluginOptions(intent: Intent = getIntent): PluginOptions =
|
||||
try new PluginOptions(intent.getStringExtra(PluginInterface.EXTRA_OPTIONS)) catch {
|
||||
case exc: IllegalArgumentException =>
|
||||
Toast.makeText(this, exc.getMessage, Toast.LENGTH_SHORT).show()
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate args to your user interface.
|
||||
*
|
||||
* @param options PluginOptions parsed.
|
||||
*/
|
||||
protected def onInitializePluginOptions(options: PluginOptions = pluginOptions()): Unit
|
||||
|
||||
override protected def onPostCreate(savedInstanceState: Bundle) {
|
||||
super.onPostCreate(savedInstanceState)
|
||||
if (savedInstanceState == null) onInitializePluginOptions()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.github.shadowsocks.plugin
|
||||
|
||||
import java.io.File
|
||||
|
||||
import android.database.MatrixCursor
|
||||
import android.net.Uri
|
||||
|
||||
/**
|
||||
* Helper class to provide relative paths of files to copy.
|
||||
*
|
||||
* @author Mygod
|
||||
*/
|
||||
final class PathProvider private[plugin](baseUri: Uri, cursor: MatrixCursor) {
|
||||
private val basePath = baseUri.getPath match {
|
||||
case null => ""
|
||||
case p => p.stripPrefix("/").stripSuffix("/")
|
||||
}
|
||||
|
||||
def addPath(path: String): PathProvider = {
|
||||
val stripped = path.stripPrefix("/").stripSuffix("/")
|
||||
if (stripped.startsWith(basePath)) cursor.newRow().add(PluginInterface.COLUMN_PATH, stripped)
|
||||
this
|
||||
}
|
||||
def addTo(file: File, to: String = ""): PathProvider = {
|
||||
var sub = to + file.getName
|
||||
if (basePath.startsWith(sub)) if (file.isDirectory) {
|
||||
sub += '/'
|
||||
file.listFiles().foreach(addTo(_, sub))
|
||||
} else addPath(sub)
|
||||
this
|
||||
}
|
||||
def addAt(file: File, at: String = ""): PathProvider = {
|
||||
if (basePath.startsWith(at)) if (file.isDirectory) file.listFiles().foreach(addTo(_, at)) else addPath(at)
|
||||
this
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user