Commit 0ffae32a authored by Mygod's avatar Mygod

Implement plugin interface in mobile app

parent c54745e2
package com.github.shadowsocks.utils; package com.github.shadowsocks.utils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer; import java.util.StringTokenizer;
/** /**
...@@ -32,23 +33,21 @@ public final class Commandline { ...@@ -32,23 +33,21 @@ public final class Commandline {
/** /**
* Quote the parts of the given array in way that makes them * Quote the parts of the given array in way that makes them
* usable as command line arguments. * usable as command line arguments.
* @param line the list of arguments to quote. * @param args the list of arguments to quote.
* @return empty string for null or no command, else every argument split * @return empty string for null or no command, else every argument split
* by spaces and quoted by quoting rules. * by spaces and quoted by quoting rules.
*/ */
public static String toString(String[] line) { public static String toString(Iterable<String> args) {
// empty path return empty string // empty path return empty string
if (line == null || line.length == 0) { if (args == null) {
return ""; return "";
} }
// path containing one or more elements // path containing one or more elements
final StringBuilder result = new StringBuilder(); final StringBuilder result = new StringBuilder();
for (int i = 0; i < line.length; i++) { for (String arg : args) {
if (i > 0) { if (result.length() > 0) result.append(' ');
result.append(' '); for (int j = 0; j < arg.length(); ++j) {
} char ch = arg.charAt(j);
for (int j = 0; j < line[i].length(); ++j) {
char ch = line[i].charAt(j);
switch (ch) { switch (ch) {
case ' ': case '\\': case '"': case '\'': result.append('\\'); // intentionally no break case ' ': case '\\': case '"': case '\'': result.append('\\'); // intentionally no break
default: result.append(ch); default: result.append(ch);
...@@ -57,6 +56,16 @@ public final class Commandline { ...@@ -57,6 +56,16 @@ public final class Commandline {
} }
return result.toString(); return result.toString();
} }
/**
* Quote the parts of the given array in way that makes them
* usable as command line arguments.
* @param args 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[] args) {
return toString(Arrays.asList(args)); // thanks to Java, arrays aren't iterable
}
/** /**
* Crack a command line. * Crack a command line.
......
...@@ -24,7 +24,7 @@ import java.io.IOException ...@@ -24,7 +24,7 @@ import java.io.IOException
import java.net.InetAddress import java.net.InetAddress
import java.util import java.util
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.{Timer, TimerTask} import java.util.{Locale, Timer, TimerTask}
import android.app.Service import android.app.Service
import android.content.{BroadcastReceiver, Context, Intent, IntentFilter} import android.content.{BroadcastReceiver, Context, Intent, IntentFilter}
...@@ -36,6 +36,7 @@ import com.github.shadowsocks.ShadowsocksApplication.app ...@@ -36,6 +36,7 @@ import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.Acl import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback} import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.plugin.{PluginConfiguration, PluginManager, PluginOptions}
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import okhttp3.{Dns, FormBody, OkHttpClient, Request} import okhttp3.{Dns, FormBody, OkHttpClient, Request}
...@@ -46,6 +47,8 @@ trait BaseService extends Service { ...@@ -46,6 +47,8 @@ trait BaseService extends Service {
@volatile private var state = State.STOPPED @volatile private var state = State.STOPPED
@volatile protected var profile: Profile = _ @volatile protected var profile: Profile = _
@volatile private var plugin: PluginOptions = _
@volatile private var pluginPath: String = _
case class NameNotResolvedException() extends IOException case class NameNotResolvedException() extends IOException
case class NullConnectionException() extends NullPointerException case class NullConnectionException() extends NullPointerException
...@@ -149,8 +152,12 @@ trait BaseService extends Service { ...@@ -149,8 +152,12 @@ trait BaseService extends Service {
profile.password = proxy(2).trim profile.password = proxy(2).trim
profile.method = proxy(3).trim profile.method = proxy(3).trim
} }
if (profile.route == Acl.CUSTOM_RULES) // rationalize custom rules if (profile.route == Acl.CUSTOM_RULES) // rationalize custom rules
Acl.save(Acl.CUSTOM_RULES, new Acl().fromId(Acl.CUSTOM_RULES)) Acl.save(Acl.CUSTOM_RULES, new Acl().fromId(Acl.CUSTOM_RULES))
plugin = new PluginConfiguration(profile.plugin).selectedOptions
pluginPath = PluginManager.initPlugin(plugin.id)
} }
def startRunner(profile: Profile) { def startRunner(profile: Profile) {
...@@ -304,4 +311,22 @@ trait BaseService extends Service { ...@@ -304,4 +311,22 @@ trait BaseService extends Service {
case _: Exception => "exclude = " + default + ";" case _: Exception => "exclude = " + default + ";"
} }
} }
protected def buildShadowsocksConfig(file: String, localPortOffset: Int = 0): String = {
val builder = new StringBuilder(
"""{"server": "%s", "server_port": %d, "local_port": %d, "password": "%s", "method": "%s""""
.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + localPortOffset,
profile.password, profile.method))
if (profile.auth) builder.append(""", "auth": true""")
if (pluginPath != null) {
builder.append(""", "plugin": """")
builder.append(pluginPath)
builder.append("""", "plugin_opts": """")
builder.append(plugin.toString)
builder.append('"')
}
builder.append('}')
IOUtils.writeString(file, builder.toString)
file
}
} }
...@@ -26,6 +26,7 @@ import java.util.concurrent.Semaphore ...@@ -26,6 +26,7 @@ import java.util.concurrent.Semaphore
import android.util.Log import android.util.Log
import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils.Commandline
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
import scala.collection.immutable.Stream import scala.collection.immutable.Stream
...@@ -57,7 +58,7 @@ class GuardedProcess(cmd: Seq[String]) { ...@@ -57,7 +58,7 @@ class GuardedProcess(cmd: Seq[String]) {
try { try {
var callback: () => Unit = null var callback: () => Unit = null
while (!isDestroyed) { while (!isDestroyed) {
Log.i(TAG, "start process: " + cmd) Log.i(TAG, "start process: " + Commandline.toString(cmd))
val startTime = currentTimeMillis val startTime = currentTimeMillis
process = new ProcessBuilder(cmd).redirectErrorStream(true).start process = new ProcessBuilder(cmd).redirectErrorStream(true).start
...@@ -75,7 +76,7 @@ class GuardedProcess(cmd: Seq[String]) { ...@@ -75,7 +76,7 @@ class GuardedProcess(cmd: Seq[String]) {
isRestart = false isRestart = false
} else { } else {
if (currentTimeMillis - startTime < 1000) { if (currentTimeMillis - startTime < 1000) {
Log.w(TAG, "process exit too fast, stop guard: " + cmd) Log.w(TAG, "process exit too fast, stop guard: " + Commandline.toString(cmd))
isDestroyed = true isDestroyed = true
} }
} }
...@@ -83,11 +84,11 @@ class GuardedProcess(cmd: Seq[String]) { ...@@ -83,11 +84,11 @@ class GuardedProcess(cmd: Seq[String]) {
} }
} catch { } catch {
case _: InterruptedException => case _: InterruptedException =>
Log.i(TAG, "thread interrupt, destroy process: " + cmd) Log.i(TAG, "thread interrupt, destroy process: " + Commandline.toString(cmd))
process.destroy() process.destroy()
case e: IOException => ioException = e case e: IOException => ioException = e
} finally semaphore.release() } finally semaphore.release()
}, "GuardThread-" + cmd) }, "GuardThread-" + cmd.head)
guardThread.start() guardThread.start()
semaphore.acquire() semaphore.acquire()
......
...@@ -53,21 +53,11 @@ class ShadowsocksNatService extends BaseService { ...@@ -53,21 +53,11 @@ class ShadowsocksNatService extends BaseService {
var su: Shell.Interactive = _ var su: Shell.Interactive = _
def startShadowsocksDaemon() { def startShadowsocksDaemon() {
val conf =
ConfigUtils
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort,
profile.password, profile.method, 600)
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-nat.conf"))(p => {
p.println(conf)
})
val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so" val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so"
, "-b" , "127.0.0.1" , "-b" , "127.0.0.1"
, "-t" , "600" , "-t" , "600"
, "-P", getApplicationInfo.dataDir , "-P", getApplicationInfo.dataDir
, "-c" , getApplicationInfo.dataDir + "/ss-local-nat.conf") , "-c" , buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-local-nat.conf"))
if (profile.auth) cmd += "-A"
if (TcpFastOpen.sendEnabled) cmd += "--fast-open" if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
...@@ -76,55 +66,24 @@ class ShadowsocksNatService extends BaseService { ...@@ -76,55 +66,24 @@ class ShadowsocksNatService extends BaseService {
cmd += Acl.getPath(profile.route) cmd += Acl.getPath(profile.route)
} }
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd))
sslocalProcess = new GuardedProcess(cmd).start() sslocalProcess = new GuardedProcess(cmd).start()
} }
def startDNSTunnel() { def startDNSTunnel() {
if (profile.udpdns) { val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so"
val conf = ConfigUtils , "-t" , "10"
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + 53, , "-b" , "127.0.0.1"
profile.password, profile.method, 10) , "-l" , (profile.localPort + 53).toString
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => { , "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53"
p.println(conf) , "-P", getApplicationInfo.dataDir
}) , "-c" , buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf", 53))
val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so"
, "-u"
, "-t" , "10"
, "-b" , "127.0.0.1"
, "-l" , (profile.localPort + 53).toString
, "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53"
, "-P" , getApplicationInfo.dataDir
, "-c" , getApplicationInfo.dataDir + "/ss-tunnel-nat.conf")
if (profile.auth) cmd += "-A"
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) if (profile.udpdns) cmd.append("-u")
sstunnelProcess = new GuardedProcess(cmd).start() if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd))
} else { sstunnelProcess = new GuardedProcess(cmd).start()
val conf =
ConfigUtils
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + 63,
profile.password, profile.method, 10)
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => {
p.println(conf)
})
val cmdBuf = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so"
, "-t" , "10"
, "-b" , "127.0.0.1"
, "-l" , (profile.localPort + 63).toString
, "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53"
, "-P", getApplicationInfo.dataDir
, "-c" , getApplicationInfo.dataDir + "/ss-tunnel-nat.conf")
if (profile.auth) cmdBuf += "-A"
if (BuildConfig.DEBUG) Log.d(TAG, cmdBuf.mkString(" "))
sstunnelProcess = new GuardedProcess(cmdBuf).start()
}
} }
def startDnsDaemon() { def startDnsDaemon() {
...@@ -150,7 +109,7 @@ class ShadowsocksNatService extends BaseService { ...@@ -150,7 +109,7 @@ class ShadowsocksNatService extends BaseService {
}) })
val cmd = Array(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", getApplicationInfo.dataDir + "/pdnsd-nat.conf") val cmd = Array(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", getApplicationInfo.dataDir + "/pdnsd-nat.conf")
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd))
pdnsdProcess = new GuardedProcess(cmd).start() pdnsdProcess = new GuardedProcess(cmd).start()
} }
...@@ -162,7 +121,7 @@ class ShadowsocksNatService extends BaseService { ...@@ -162,7 +121,7 @@ class ShadowsocksNatService extends BaseService {
p.println(conf) p.println(conf)
}) })
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd))
redsocksProcess = new GuardedProcess(cmd).start() redsocksProcess = new GuardedProcess(cmd).start()
} }
......
...@@ -30,10 +30,11 @@ import android.net.VpnService ...@@ -30,10 +30,11 @@ import android.net.VpnService
import android.os._ import android.os._
import android.util.Log import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{AclSyncJob, Acl} import com.github.shadowsocks.acl.{Acl, AclSyncJob}
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import scala.collection.JavaConversions._
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
class ShadowsocksVpnService extends VpnService with BaseService { class ShadowsocksVpnService extends VpnService with BaseService {
...@@ -162,21 +163,11 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -162,21 +163,11 @@ class ShadowsocksVpnService extends VpnService with BaseService {
} }
def startShadowsocksDaemon() { def startShadowsocksDaemon() {
val conf =
ConfigUtils
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort,
profile.password, profile.method, 600)
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-vpn.conf"))(p => {
p.println(conf)
})
val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", "-V" val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", "-V"
, "-b", "127.0.0.1" , "-b", "127.0.0.1"
, "-t", "600" , "-t", "600"
, "-P", getApplicationInfo.dataDir , "-P", getApplicationInfo.dataDir
, "-c", getApplicationInfo.dataDir + "/ss-local-vpn.conf") , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-local-vpn.conf"))
if (profile.auth) cmd += "-A"
if (profile.udpdns) cmd += "-u" if (profile.udpdns) cmd += "-u"
...@@ -187,31 +178,20 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -187,31 +178,20 @@ class ShadowsocksVpnService extends VpnService with BaseService {
if (TcpFastOpen.sendEnabled) cmd += "--fast-open" if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd))
sslocalProcess = new GuardedProcess(cmd).start() sslocalProcess = new GuardedProcess(cmd).start()
} }
def startDnsTunnel() { def startDnsTunnel() {
val conf = val cmd = Array[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so"
ConfigUtils
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + 63,
profile.password, profile.method, 10)
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf"))(p => {
p.println(conf)
})
val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so"
, "-V" , "-V"
, "-t", "10" , "-t", "10"
, "-b", "127.0.0.1" , "-b", "127.0.0.1"
, "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53" , "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53"
, "-P", getApplicationInfo.dataDir , "-P", getApplicationInfo.dataDir
, "-c", getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf") , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf", 53))
if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd))
if (profile.auth) cmd += "-A"
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
sstunnelProcess = new GuardedProcess(cmd).start() sstunnelProcess = new GuardedProcess(cmd).start()
} }
...@@ -236,7 +216,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -236,7 +216,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
}) })
val cmd = Array(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", getApplicationInfo.dataDir + "/pdnsd-vpn.conf") val cmd = Array(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", getApplicationInfo.dataDir + "/pdnsd-vpn.conf")
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd))
pdnsdProcess = new GuardedProcess(cmd).start() pdnsdProcess = new GuardedProcess(cmd).start()
} }
...@@ -310,7 +290,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -310,7 +290,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"),
profile.localPort + 53)) profile.localPort + 53))
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd))
tun2socksProcess = new GuardedProcess(cmd).start(() => sendFd(fd)) tun2socksProcess = new GuardedProcess(cmd).start(() => sendFd(fd))
......
package com.github.shadowsocks.plugin package com.github.shadowsocks.plugin
import java.io.{File, FileNotFoundException, FileOutputStream, IOException}
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.{BroadcastReceiver, Intent} import android.content.{BroadcastReceiver, ContentResolver, Intent}
import android.net.Uri
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.CloseUtils.autoClose
import com.github.shadowsocks.utils.IOUtils
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
...@@ -22,4 +27,53 @@ object PluginManager { ...@@ -22,4 +27,53 @@ object PluginManager {
}) })
cachedPlugins cachedPlugins
} }
// the following parts are meant to be used by :bg
@throws[Throwable]
def initPlugin(id: String): String = {
if (id.isEmpty) return null
var throwable: Throwable = null
try initNativePlugin(id) match {
case null =>
case path => return path
} catch {
case t: Throwable => if (throwable == null) throwable = t
}
// add other plugin types here
throw if (throwable == null)
new FileNotFoundException(app.getString(com.github.shadowsocks.R.string.plugin_unknown, id)) else throwable
}
@throws[IOException]
@throws[IndexOutOfBoundsException]
@throws[AssertionError]
private def initNativePlugin(id: String): String = {
val builder = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(PluginInterface.getAuthority(id))
val cr = app.getContentResolver
val cursor = cr.query(builder.build(), Array(PluginInterface.COLUMN_PATH), null, null, null)
if (cursor != null) {
var initialized = false
val pluginDir = new File(app.getFilesDir, "plugin")
if (cursor.moveToFirst()) {
IOUtils.deleteRecursively(pluginDir)
if (!pluginDir.mkdirs()) throw new FileNotFoundException("Unable to create plugin directory")
val pluginDirPath = pluginDir.getAbsolutePath + '/'
do {
val path = cursor.getString(0)
val file = new File(pluginDir, path)
assert(file.getAbsolutePath.startsWith(pluginDirPath))
autoClose(cr.openInputStream(builder.path(path).build()))(in =>
autoClose(new FileOutputStream(file))(out => IOUtils.copy(in, out)))
if (path == id) initialized = true
} while (cursor.moveToNext())
}
if (!initialized) throw new IndexOutOfBoundsException("Plugin entry binary not found")
new File(pluginDir, id).getAbsolutePath
} else null
}
} }
...@@ -31,7 +31,6 @@ object Executable { ...@@ -31,7 +31,6 @@ object Executable {
} }
object ConfigUtils { object ConfigUtils {
val SHADOWSOCKS = "{\"server\": \"%s\", \"server_port\": %d, \"local_port\": %d, \"password\": \"%s\", \"method\":\"%s\", \"timeout\": %d}"
val REDSOCKS = "base {\n" + val REDSOCKS = "base {\n" +
" log_debug = off;\n" + " log_debug = off;\n" +
" log_info = off;\n" + " log_info = off;\n" +
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
package com.github.shadowsocks.utils package com.github.shadowsocks.utils
import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.CloseUtils._
import java.io.{FileWriter, InputStream, OutputStream} import java.io._
/** /**
* @author Mygod * @author Mygod
...@@ -39,4 +39,10 @@ object IOUtils { ...@@ -39,4 +39,10 @@ object IOUtils {
def writeString(file: String, content: String): Unit = def writeString(file: String, content: String): Unit =
autoClose(new FileWriter(file))(writer => writer.write(content)) autoClose(new FileWriter(file))(writer => writer.write(content))
@throws[FileNotFoundException]
def deleteRecursively(file: File) {
if (file.isDirectory) file.listFiles().foreach(deleteRecursively)
if (!file.delete()) throw new FileNotFoundException("Failed to delete: " + file.getAbsolutePath)
}
} }
...@@ -46,7 +46,7 @@ abstract class NativePluginProvider extends ContentProvider { ...@@ -46,7 +46,7 @@ abstract class NativePluginProvider extends ContentProvider {
override def query(uri: Uri, projection: Array[String], selection: String, selectionArgs: Array[String], override def query(uri: Uri, projection: Array[String], selection: String, selectionArgs: Array[String],
sortOrder: String): Cursor = { sortOrder: String): Cursor = {
if (selection != null || selectionArgs != null || sortOrder != null) ??? assert(selection == null && selectionArgs == null && sortOrder == null)
val result = new MatrixCursor(projection.filter(_ == PluginInterface.COLUMN_PATH)) val result = new MatrixCursor(projection.filter(_ == PluginInterface.COLUMN_PATH))
populateFiles(new PathProvider(uri, result)) populateFiles(new PathProvider(uri, result))
result result
...@@ -54,7 +54,7 @@ abstract class NativePluginProvider extends ContentProvider { ...@@ -54,7 +54,7 @@ abstract class NativePluginProvider extends ContentProvider {
def openFile(uri: Uri): ParcelFileDescriptor def openFile(uri: Uri): ParcelFileDescriptor
override def openFile(uri: Uri, mode: String): ParcelFileDescriptor = { override def openFile(uri: Uri, mode: String): ParcelFileDescriptor = {
if (mode != "r") ??? assert(mode == "r")
openFile(uri) openFile(uri)
} }
......
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