Index: libadblockplus-android-tests/src/org/adblockplus/libadblockplus/MockFilterChangeCallback.java
diff --git a/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/MockFilterChangeCallback.java b/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/MockFilterChangeCallback.java
index a300d7c1aa4856fb101b928bef62d20967349fad..974b54da2addaa5e4d8f965ed73a91ca82cecbe3 100644
--- a/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/MockFilterChangeCallback.java
+++ b/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/MockFilterChangeCallback.java
@@ -17,9 +17,15 @@
package org.adblockplus.libadblockplus;
+import android.util.Log;
+
+import org.adblockplus.libadblockplus.android.Utils;
+
public class MockFilterChangeCallback extends FilterChangeCallback
{
- private int timesCalled;
+ private static final String TAG = Utils.getTag(MockFilterChangeCallback.class);
+
+ private volatile int timesCalled;
public MockFilterChangeCallback(int timesCalled)
{
@@ -34,6 +40,7 @@ public class MockFilterChangeCallback extends FilterChangeCallback
@Override
public void filterChangeCallback(String action, JsValue jsValue)
{
+ Log.d(TAG, "callback: action=" + action + ", jsValue=" + jsValue);
timesCalled++;
}
}
Index: libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/AndroidFileSystemTest.java
diff --git a/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/AndroidFileSystemTest.java b/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/AndroidFileSystemTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8b693ac8f59709c320ebf5ea8851063d018f2548
--- /dev/null
+++ b/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/AndroidFileSystemTest.java
@@ -0,0 +1,432 @@
+/*
+ * This file is part of Adblock Plus ,
+ * Copyright (C) 2006-2017 eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see .
+ */
+
+package org.adblockplus.libadblockplus.tests;
+
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import org.adblockplus.libadblockplus.AdblockPlusException;
+import org.adblockplus.libadblockplus.AppInfo;
+import org.adblockplus.libadblockplus.FileSystem;
+import org.adblockplus.libadblockplus.FileSystemUtils;
+import org.adblockplus.libadblockplus.FilterEngine;
+import org.adblockplus.libadblockplus.JsEngine;
+import org.adblockplus.libadblockplus.android.AndroidFileSystem;
+import org.adblockplus.libadblockplus.android.AndroidWebRequest;
+import org.adblockplus.libadblockplus.android.Utils;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+import static org.adblockplus.libadblockplus.FileSystemUtils.byteFromInt;
+
+public class AndroidFileSystemTest extends AndroidTestCase
+{
+ private static final String TAG = Utils.getTag(AndroidFileSystemTest.class);
+
+ protected static final String TEXT_DATA = "12345qwerty";
+ protected static final byte[] EMPTY_DATA_BYTES = {};
+ protected static final byte[] TEXT_DATA_BYTES = TEXT_DATA.getBytes();
+ protected static final byte[] BINARY_DATA_BYTES = new byte[]
+ {
+ byteFromInt(1),
+ byteFromInt(2),
+ byteFromInt(3)
+ };
+
+ protected final FileSystemUtils fileSystemUtils = new FileSystemUtils();
+ protected File basePathFile;
+ protected AndroidFileSystem fileSystem;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ String tmpFolder = FileSystemUtils.generateUniqueFileName("tmp", null);
+ basePathFile = new File(getContext().getCacheDir(), tmpFolder);
+ basePathFile.mkdirs();
+
+ fileSystem = new AndroidFileSystem(basePathFile);
+ }
+
+ protected String generateUniqueFilename()
+ {
+ return fileSystemUtils.generateUniqueFileName("file", ".tmp");
+ }
+
+ protected File getFullPath(String path)
+ {
+ return new File(fileSystem.getBasePath(), path);
+ }
+
+ @Test
+ public void testWriteReadBinaryFile() throws IOException
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ assertFalse(file.exists());
+
+ fileSystem.write(resolvedPath, BINARY_DATA_BYTES);
+
+ assertTrue(file.exists());
+ assertTrue(Arrays.equals(BINARY_DATA_BYTES, fileSystemUtils.readFile(file)));
+
+ byte[] data = fileSystem.read(resolvedPath);
+ assertNotNull(data);
+ assertTrue(Arrays.equals(BINARY_DATA_BYTES, data));
+ }
+
+ @Test
+ public void testWriteNullFile() throws IOException
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ assertFalse(file.exists());
+
+ fileSystem.write(resolvedPath, null);
+
+ assertTrue(file.exists());
+ assertTrue(Arrays.equals(EMPTY_DATA_BYTES, fileSystemUtils.readFile(file)));
+ }
+
+ @Test
+ public void testWriteEmptyFile() throws IOException
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ assertFalse(file.exists());
+
+ fileSystem.write(resolvedPath, EMPTY_DATA_BYTES);
+
+ assertTrue(file.exists());
+ assertTrue(Arrays.equals(EMPTY_DATA_BYTES, fileSystemUtils.readFile(file)));
+ }
+
+ @Test
+ public void testWriteInvalidPath()
+ {
+ String path = "notExistingDirectory/" + generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ assertFalse(file.exists());
+ File notExistingDirectoryFile = file.getParentFile();
+ assertFalse(notExistingDirectoryFile.exists());
+
+ try
+ {
+ fileSystem.write(resolvedPath, TEXT_DATA_BYTES);
+ fail("Exception should be thrown");
+ }
+ catch (AdblockPlusException e)
+ {
+ // ignored
+ }
+ }
+
+ @Test
+ public void testWriteSingleLineData() throws IOException
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ assertFalse(file.exists());
+
+ final String singleLineData = TEXT_DATA.replace("\n", "");
+ fileSystem.write(resolvedPath, singleLineData.getBytes());
+
+ assertTrue(file.exists());
+ assertTrue(Arrays.equals(singleLineData.getBytes(), fileSystemUtils.readFile(file)));
+ }
+
+ @Test
+ public void testWriteMultiLineData() throws IOException
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ assertFalse(file.exists());
+
+ final String multiLineData = TEXT_DATA + "\n" + TEXT_DATA;
+ fileSystem.write(resolvedPath, multiLineData.getBytes());
+
+ assertTrue(file.exists());
+ assertTrue(Arrays.equals(multiLineData.getBytes(), fileSystemUtils.readFile(file)));
+ }
+
+ @Test
+ public void testReadEmptyFile() throws IOException
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ assertFalse(file.exists());
+
+ fileSystemUtils.writeFile(file, EMPTY_DATA_BYTES);
+ assertTrue(file.exists());
+
+ assertTrue(Arrays.equals(EMPTY_DATA_BYTES, fileSystem.read(resolvedPath)));
+ }
+
+ @Test
+ public void testReadSingleLineData() throws IOException
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ assertFalse(file.exists());
+
+ final String singleLineData = TEXT_DATA.replace("\n", "");
+ fileSystemUtils.writeFile(file, singleLineData.getBytes());
+ assertTrue(file.exists());
+
+ assertTrue(Arrays.equals(singleLineData.getBytes(), fileSystem.read(resolvedPath)));
+ }
+
+ @Test
+ public void testReadMultiLineData() throws IOException
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ assertFalse(file.exists());
+
+ final String multiLineData = TEXT_DATA + "\n" + TEXT_DATA;
+ fileSystemUtils.writeFile(file, multiLineData.getBytes());
+ assertTrue(file.exists());
+
+ assertTrue(Arrays.equals(multiLineData.getBytes(), fileSystem.read(resolvedPath)));
+ }
+
+ @Test
+ public void testMoveExistingFile() throws IOException
+ {
+ String fromPath = generateUniqueFilename();
+ String resolvedFromPath= fileSystem.resolve(fromPath);
+ File fromFile = getFullPath(fromPath);
+ String toPath = generateUniqueFilename();
+ String resolvedToPath = fileSystem.resolve(toPath);
+ File toFile = getFullPath(toPath);
+ fileSystemUtils.writeFile(fromFile, TEXT_DATA_BYTES);
+
+ assertTrue(fromFile.exists());
+ assertFalse(toFile.exists());
+
+ fileSystem.move(resolvedFromPath, resolvedToPath);
+
+ assertFalse(fromFile.exists());
+ assertTrue(toFile.exists());
+ assertTrue(Arrays.equals(TEXT_DATA_BYTES, fileSystemUtils.readFile(toFile)));
+ }
+
+ @Test
+ public void testMoveNotExistingFile()
+ {
+ String fromPath = generateUniqueFilename();
+ String resolvedFromPath = fileSystem.resolve(fromPath);
+ File fromFile = getFullPath(fromPath);
+ String toPath = generateUniqueFilename();
+ String resolvedToPath = fileSystem.resolve(toPath);
+ assertFalse(fromFile.exists());
+
+ try
+ {
+ fileSystem.move(resolvedFromPath, resolvedToPath);
+ fail("Exception should be thrown");
+ }
+ catch (AdblockPlusException e)
+ {
+ // ignored
+ }
+ }
+
+ @Test
+ public void testRemoveExistingFile() throws IOException
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ fileSystemUtils.writeFile(file, TEXT_DATA_BYTES);
+
+ assertTrue(file.exists());
+ fileSystem.remove(resolvedPath);
+ assertFalse(file.exists());
+ }
+
+ @Test
+ public void testRemoveNotExistingFile()
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ assertFalse(file.exists());
+
+ fileSystem.remove(resolvedPath);
+ assertFalse(file.exists());
+ }
+
+ @Test
+ public void testStatExistingFile() throws IOException
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ fileSystemUtils.writeFile(file, TEXT_DATA_BYTES);
+ assertTrue(file.exists());
+
+ FileSystem.StatResult stat = fileSystem.stat(resolvedPath);
+ assertNotNull(stat);
+ assertTrue(stat.exists());
+ assertFalse(stat.isDirectory());
+ assertTrue(stat.isFile());
+ assertTrue(stat.getLastModified() > 0);
+ }
+
+ @Test
+ public void testStatExistingDirectory()
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ file.mkdir();
+
+ FileSystem.StatResult stat = fileSystem.stat(resolvedPath);
+ assertNotNull(stat);
+ assertTrue(stat.exists());
+ assertFalse(stat.isFile());
+ assertTrue(stat.isDirectory());
+ assertTrue(stat.getLastModified() > 0);
+ }
+
+ @Test
+ public void testStatNotExistingFile()
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ File file = getFullPath(path);
+ assertFalse(file.exists());
+
+ fileSystem.remove(resolvedPath);
+ assertFalse(file.exists());
+ FileSystem.StatResult notExistingStat = fileSystem.stat(resolvedPath);
+ assertFalse(notExistingStat.exists());
+ assertEquals(0l, notExistingStat.getLastModified());
+ }
+
+ @Test
+ public void testResolveAbsolute()
+ {
+ String path = generateUniqueFilename();
+ String resolvedPath = fileSystem.resolve(path);
+ String fullPath = getFullPath(path).getAbsolutePath();
+ assertEquals(fullPath, resolvedPath);
+ }
+
+ @Test
+ public void testResolveRelative()
+ {
+ String relativePath = "./folder/file2";
+ String resolvedPath = fileSystem.resolve(relativePath);
+ assertNotNull(resolvedPath);
+ assertEquals(new File(basePathFile, relativePath).getAbsolutePath(), resolvedPath);
+ }
+
+ private boolean readInvoked;
+ private boolean writeInvoked;
+ private boolean statInvoked;
+ private boolean resolveInvoked;
+
+ private final class TestAndroidFileSystem extends AndroidFileSystem
+ {
+ public TestAndroidFileSystem(File basePath)
+ {
+ super(basePath);
+ }
+
+ @Override
+ public byte[] read(String path)
+ {
+ readInvoked = true;
+ try
+ {
+ Log.d(TAG, "Reading from " + path);
+ String content = new String(FileSystemUtils.readFile(new File(path)), "UTF-8");
+ Log.d(TAG, "Read from " + path + ":\n" + content);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ return super.read(path);
+ }
+
+ @Override
+ public void write(String path, byte[] data)
+ {
+ writeInvoked = true;
+ Log.d(TAG, "Write " + data.length + " bytes to " + path);
+ super.write(path, data);
+ }
+
+ @Override
+ public StatResult stat(String path)
+ {
+ statInvoked = true;
+ StatResult stat = super.stat(path);
+ Log.d(TAG, "Stat for " + path + " is " + stat);
+ return stat;
+ }
+
+ @Override
+ public String resolve(String path)
+ {
+ resolveInvoked = true;
+
+ String resolvedPath = super.resolve(path);
+ Log.d(TAG, "Resolve " + path + " to " + resolvedPath);
+ return resolvedPath;
+ }
+ }
+
+ @Test
+ public void testInvokeFromNativeCode()
+ {
+ readInvoked = false;
+ writeInvoked = false;
+ statInvoked = false;
+ resolveInvoked = false;
+
+ JsEngine jsEngine = new JsEngine(AppInfo.builder().build());
+ jsEngine.setDefaultLogSystem();
+ jsEngine.setFileSystem(new TestAndroidFileSystem(basePathFile));
+ jsEngine.setWebRequest(new AndroidWebRequest());
+
+ FilterEngine filterEngine = new FilterEngine(jsEngine);
+ SystemClock.sleep(10 * 1000);
+
+ assertTrue(readInvoked);
+ assertTrue(writeInvoked);
+ assertTrue(statInvoked);
+ assertTrue(resolveInvoked);
+ }
+}
Index: libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/FilterEngineGenericTest.java
diff --git a/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/FilterEngineGenericTest.java b/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/FilterEngineGenericTest.java
index 92a098f916017356fff71803647749eab2eefed4..242e1a94ab958405663a7a2aa9f61d125ce1ae4b 100644
--- a/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/FilterEngineGenericTest.java
+++ b/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/FilterEngineGenericTest.java
@@ -17,6 +17,7 @@
package org.adblockplus.libadblockplus.tests;
+import org.adblockplus.libadblockplus.FileSystem;
import org.adblockplus.libadblockplus.FilterEngine;
import org.adblockplus.libadblockplus.LazyWebRequest;
@@ -24,6 +25,74 @@ public abstract class FilterEngineGenericTest extends BaseJsTest
{
protected FilterEngine filterEngine;
+ private static final class PatternsIniStubFileSystem extends FileSystem
+ {
+ private static final String PATTERNS_INI = "patterns.ini";
+
+ private boolean patternsIniExists = true;
+
+ public boolean isPatternsIniExists()
+ {
+ return patternsIniExists;
+ }
+
+ public void setPatternsIniExists(boolean patternsIniExists)
+ {
+ this.patternsIniExists = patternsIniExists;
+ }
+
+ @Override
+ public byte[] read(String path)
+ {
+ String result;
+ if (path.equals(PATTERNS_INI))
+ {
+ result = "# Adblock Plus preferences\n[Subscription]\nurl=~fl~";
+ }
+ else if (path.equals("prefs.json"))
+ {
+ result = "{}";
+ }
+ else
+ {
+ result = "";
+ }
+ return result.getBytes();
+ }
+
+ @Override
+ public void write(String path, byte[] data)
+ {
+
+ }
+
+ @Override
+ public void move(String fromPath, String toPath)
+ {
+
+ }
+
+ @Override
+ public void remove(String path)
+ {
+
+ }
+
+ @Override
+ public FileSystem.StatResult stat(String path)
+ {
+ return path.equals(PATTERNS_INI)
+ ? new FileSystem.StatResult(patternsIniExists, false, true, 0)
+ : new FileSystem.StatResult(false, false, false, 0);
+ }
+
+ @Override
+ public String resolve(String path)
+ {
+ return path;
+ }
+ }
+
@Override
protected void setUp() throws Exception
{
@@ -31,6 +100,7 @@ public abstract class FilterEngineGenericTest extends BaseJsTest
jsEngine.setWebRequest(new LazyWebRequest());
jsEngine.setDefaultLogSystem();
+ jsEngine.setFileSystem(new PatternsIniStubFileSystem());
filterEngine = new FilterEngine(jsEngine);
}
Index: libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/FilterEngineTest.java
diff --git a/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/FilterEngineTest.java b/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/FilterEngineTest.java
index 40d1a24c4e9e12b22c3aeb58a9a515a2b00c482c..644be3cee52ae24266cae27d8df2570def4ea14a 100644
--- a/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/FilterEngineTest.java
+++ b/libadblockplus-android-tests/src/org/adblockplus/libadblockplus/tests/FilterEngineTest.java
@@ -17,6 +17,7 @@
package org.adblockplus.libadblockplus.tests;
+import android.os.SystemClock;
import android.util.Log;
import org.adblockplus.libadblockplus.Filter;
import org.adblockplus.libadblockplus.FilterEngine;
@@ -331,6 +332,12 @@ public class FilterEngineTest extends FilterEngineGenericTest
@Test
public void testSetRemoveFilterChangeCallback()
{
+ // avoid callback with action "load"
+ while (filterEngine.getListedFilters().size() > 0)
+ {
+ filterEngine.getListedFilters().get(0).removeFromList();
+ }
+
MockFilterChangeCallback mockFilterChangeCallback = new MockFilterChangeCallback(0);
filterEngine.setFilterChangeCallback(mockFilterChangeCallback);
Index: libadblockplus-android/jni/Android.mk
diff --git a/libadblockplus-android/jni/Android.mk b/libadblockplus-android/jni/Android.mk
index 913a11c6dd16dc1d616611d0626505fe6e0288e5..3e0ce3a3b48ce0cf659f904b7dcad91d12ee2046 100755
--- a/libadblockplus-android/jni/Android.mk
+++ b/libadblockplus-android/jni/Android.mk
@@ -27,7 +27,7 @@ LOCAL_MODULE := libadblockplus-jni
LOCAL_SRC_FILES := JniLibrary.cpp
LOCAL_SRC_FILES += JniJsEngine.cpp JniFilterEngine.cpp JniJsValue.cpp
LOCAL_SRC_FILES += JniFilter.cpp JniSubscription.cpp JniEventCallback.cpp
-LOCAL_SRC_FILES += JniLogSystem.cpp JniWebRequest.cpp
+LOCAL_SRC_FILES += JniLogSystem.cpp JniFileSystem.cpp JniWebRequest.cpp
LOCAL_SRC_FILES += JniUpdateAvailableCallback.cpp JniUpdateCheckDoneCallback.cpp
LOCAL_SRC_FILES += JniFilterChangeCallback.cpp JniCallbacks.cpp Utils.cpp
LOCAL_SRC_FILES += JniNotification.cpp JniShowNotificationCallback.cpp
Index: libadblockplus-android/jni/JniCallbacks.cpp
diff --git a/libadblockplus-android/jni/JniCallbacks.cpp b/libadblockplus-android/jni/JniCallbacks.cpp
index cf46898426ce9af64a8fec5cc392b1c490fd9c24..3191549f44d2141e1cdc87a8fea9fde7f43afa53 100644
--- a/libadblockplus-android/jni/JniCallbacks.cpp
+++ b/libadblockplus-android/jni/JniCallbacks.cpp
@@ -55,12 +55,15 @@ void JniCallbackBase::LogException(JNIEnv* env, jthrowable throwable) const
}
}
-void JniCallbackBase::CheckAndLogJavaException(JNIEnv* env) const
+bool JniCallbackBase::CheckAndLogJavaException(JNIEnv* env) const
{
if (env->ExceptionCheck())
{
JniLocalReference throwable(env, env->ExceptionOccurred());
env->ExceptionClear();
LogException(env, *throwable);
+
+ return true;
}
+ return false;
}
Index: libadblockplus-android/jni/JniCallbacks.h
diff --git a/libadblockplus-android/jni/JniCallbacks.h b/libadblockplus-android/jni/JniCallbacks.h
index 27269b7cb59419fcc46188a56605d0d60bdc2924..1d12274b14da1f84747d9d917d553cffe5cca343 100644
--- a/libadblockplus-android/jni/JniCallbacks.h
+++ b/libadblockplus-android/jni/JniCallbacks.h
@@ -32,7 +32,7 @@ public:
JniCallbackBase(JNIEnv* env, jobject callbackObject);
virtual ~JniCallbackBase();
void LogException(JNIEnv* env, jthrowable throwable) const;
- void CheckAndLogJavaException(JNIEnv* env) const;
+ bool CheckAndLogJavaException(JNIEnv* env) const;
JavaVM* GetJavaVM() const
{
@@ -84,6 +84,18 @@ public:
void operator()(AdblockPlus::LogSystem::LogLevel logLevel, const std::string& message, const std::string& source);
};
+class JniFileSystemCallback : public JniCallbackBase, public AdblockPlus::FileSystem
+{
+public:
+ JniFileSystemCallback(JNIEnv* env, jobject callbackObject);
+ std::shared_ptr Read(const std::string& path) const;
+ void Write(const std::string& path, std::shared_ptr data);
+ void Move(const std::string& fromPath, const std::string& toPath);
+ void Remove(const std::string& path);
+ AdblockPlus::FileSystem::StatResult Stat(const std::string& path) const;
+ std::string Resolve(const std::string& path) const;
+};
+
class JniShowNotificationCallback : public JniCallbackBase
{
public:
Index: libadblockplus-android/jni/JniFileSystem.cpp
diff --git a/libadblockplus-android/jni/JniFileSystem.cpp b/libadblockplus-android/jni/JniFileSystem.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f87e05752683b8331851dd0a2045061e4e48f574
--- /dev/null
+++ b/libadblockplus-android/jni/JniFileSystem.cpp
@@ -0,0 +1,228 @@
+/*
+ * This file is part of Adblock Plus ,
+ * Copyright (C) 2006-2017 eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see .
+ */
+
+#include "JniCallbacks.h"
+#include "AdblockPlus/FileSystem.h"
+#include "Utils.h"
+#include
+#include
+
+class RuntimeErrorWithErrno : public std::runtime_error
+{
+public:
+ explicit RuntimeErrorWithErrno(const std::string& message)
+ : std::runtime_error(message + " (" + strerror(errno) + ")")
+ {
+ }
+};
+
+struct membuf: std::streambuf {
+ membuf(char const* base, size_t size) {
+ char* p(const_cast(base));
+ this->setg(p, p, p + size);
+ }
+};
+
+struct imemstream: virtual membuf, std::istream {
+ imemstream(char const* base, size_t size)
+ : membuf(base, size)
+ , std::istream(static_cast(this)) {
+ }
+};
+
+// precached in JNI_OnLoad and released in JNI_OnUnload
+JniGlobalReference* statResultClass;
+jmethodID existsMethod;
+jmethodID isDirectoryMethod;
+jmethodID isFileMethod;
+jmethodID getLastModifiedMethod;
+
+void JniFileSystem_OnLoad(JavaVM* vm, JNIEnv* env, void* reserved)
+{
+ statResultClass = new JniGlobalReference(env, env->FindClass(PKG("FileSystem$StatResult")));
+ existsMethod = env->GetMethodID(statResultClass->Get(), "exists", "()Z");
+ isDirectoryMethod = env->GetMethodID(statResultClass->Get(), "isDirectory", "()Z");
+ isFileMethod = env->GetMethodID(statResultClass->Get(), "isFile", "()Z");
+ getLastModifiedMethod = env->GetMethodID(statResultClass->Get(), "getLastModified", "()J");
+}
+
+void JniFileSystem_OnUnload(JavaVM* vm, JNIEnv* env, void* reserved)
+{
+ if (statResultClass)
+ {
+ delete statResultClass;
+ statResultClass = NULL;
+ }
+}
+
+static jlong JNICALL JniCtor(JNIEnv* env, jclass clazz, jobject callbackObject)
+{
+ try
+ {
+ return JniPtrToLong(new AdblockPlus::FileSystemPtr(new JniFileSystemCallback(env, callbackObject)));
+ }
+ CATCH_THROW_AND_RETURN(env, 0)
+}
+
+static void JNICALL JniDtor(JNIEnv* env, jclass clazz, jlong ptr)
+{
+ delete JniLongToTypePtr(ptr);
+}
+
+JniFileSystemCallback::JniFileSystemCallback(JNIEnv* env, jobject callbackObject)
+ : JniCallbackBase(env, callbackObject)
+{
+}
+
+std::shared_ptr JniFileSystemCallback::Read(const std::string& path) const
+{
+ JNIEnvAcquire env(GetJavaVM());
+
+ jmethodID method = env->GetMethodID(
+ *JniLocalReference(*env, env->GetObjectClass(GetCallbackObject())),
+ "read",
+ "(Ljava/lang/String;)[B");
+
+ JniLocalReference jPath(*env, env->NewStringUTF(path.c_str()));
+ jbyteArray jData = (jbyteArray)env->CallObjectMethod(GetCallbackObject(), method, *jPath);
+ if (CheckAndLogJavaException(*env))
+ throw new RuntimeErrorWithErrno("Failed to open file (File not found)");
+
+ int dataLength = env->GetArrayLength(jData);
+ char* cData = new char[dataLength];
+ env->GetByteArrayRegion(jData, 0, dataLength, reinterpret_cast(cData));
+
+ std::shared_ptr cSharedStream(new imemstream(cData, dataLength));
+ return cSharedStream;
+}
+
+void JniFileSystemCallback::Write(const std::string& path, std::shared_ptr dataStreamPtr)
+{
+ JNIEnvAcquire env(GetJavaVM());
+
+ jmethodID method = env->GetMethodID(
+ *JniLocalReference(*env, env->GetObjectClass(GetCallbackObject())),
+ "write",
+ "(Ljava/lang/String;[B)V");
+
+ JniLocalReference jPath(*env, env->NewStringUTF(path.c_str()));
+
+ // read all the data from the stream into buffer (no appropriate way to pass streams over JNI)
+ std::istream* dataStream = dataStreamPtr.get();
+ dataStream->seekg(0, std::ios::end);
+ int dataLength = dataStream->tellg();
+ char* cData = new char[dataLength];
+ dataStream->seekg(0, std::ios::beg);
+ dataStream->read(cData, dataLength);
+
+ jbyteArray jData = env->NewByteArray(dataLength);
+ env->SetByteArrayRegion(jData, 0, dataLength, reinterpret_cast(cData));
+
+ env->CallVoidMethod(GetCallbackObject(), method, *jPath, jData);
+ CheckAndLogJavaException(*env);
+ delete[] cData;
+}
+
+void JniFileSystemCallback::Move(const std::string& fromPath, const std::string& toPath)
+{
+ JNIEnvAcquire env(GetJavaVM());
+
+ jmethodID method = env->GetMethodID(
+ *JniLocalReference(*env, env->GetObjectClass(GetCallbackObject())),
+ "move",
+ "(Ljava/lang/String;Ljava/lang/String;)V");
+
+ JniLocalReference jFromPath(*env, env->NewStringUTF(fromPath.c_str()));
+ JniLocalReference jToPath(*env, env->NewStringUTF(toPath.c_str()));
+
+ env->CallVoidMethod(GetCallbackObject(), method, *jFromPath, *jToPath);
+ CheckAndLogJavaException(*env);
+}
+
+void JniFileSystemCallback::Remove(const std::string& path)
+{
+ JNIEnvAcquire env(GetJavaVM());
+
+ jmethodID method = env->GetMethodID(
+ *JniLocalReference(*env, env->GetObjectClass(GetCallbackObject())),
+ "remove",
+ "(Ljava/lang/String;)V");
+
+ JniLocalReference jPath(*env, env->NewStringUTF(path.c_str()));
+
+ env->CallVoidMethod(GetCallbackObject(), method, *jPath);
+ CheckAndLogJavaException(*env);
+}
+
+AdblockPlus::FileSystem::StatResult JniFileSystemCallback::Stat(const std::string& path) const
+{
+ JNIEnvAcquire env(GetJavaVM());
+
+ jmethodID method = env->GetMethodID(
+ *JniLocalReference(*env, env->GetObjectClass(GetCallbackObject())),
+ "stat",
+ "(Ljava/lang/String;)" TYP("FileSystem$StatResult"));
+
+ JniLocalReference jPath(*env, env->NewStringUTF(path.c_str()));
+
+ jobject jStatResult = env->CallObjectMethod(GetCallbackObject(), method, *jPath);
+ CheckAndLogJavaException(*env);
+
+ AdblockPlus::FileSystem::StatResult statResult;
+
+ statResult.exists = env->CallBooleanMethod(jStatResult, existsMethod) ? JNI_TRUE : JNI_FALSE;
+ CheckAndLogJavaException(*env);
+
+ statResult.isDirectory = env->CallBooleanMethod(jStatResult, isDirectoryMethod) ? JNI_TRUE : JNI_FALSE;
+ CheckAndLogJavaException(*env);
+
+ statResult.isFile = env->CallBooleanMethod(jStatResult, isFileMethod) ? JNI_TRUE : JNI_FALSE;
+ CheckAndLogJavaException(*env);
+
+ statResult.lastModified = env->CallLongMethod(jStatResult, getLastModifiedMethod);
+ CheckAndLogJavaException(*env);
+
+ return statResult;
+}
+
+std::string JniFileSystemCallback::Resolve(const std::string& path) const
+{
+ JNIEnvAcquire env(GetJavaVM());
+
+ jmethodID method = env->GetMethodID(
+ *JniLocalReference(*env, env->GetObjectClass(GetCallbackObject())),
+ "resolve",
+ "(Ljava/lang/String;)Ljava/lang/String;");
+
+ JniLocalReference jPath(*env, env->NewStringUTF(path.c_str()));
+
+ jstring jRet = (jstring)env->CallObjectMethod(GetCallbackObject(), method, *jPath);
+ CheckAndLogJavaException(*env);
+
+ return JniJavaToStdString(*env, jRet);
+}
+
+static JNINativeMethod methods[] =
+{
+ { (char*)"ctor", (char*)"(Ljava/lang/Object;)J", (void*)JniCtor },
+ { (char*)"dtor", (char*)"(J)V", (void*)JniDtor }
+};
+
+extern "C" JNIEXPORT void JNICALL Java_org_adblockplus_libadblockplus_FileSystem_registerNatives(JNIEnv *env, jclass clazz)
+{
+ env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0]));
+}
Index: libadblockplus-android/jni/JniFileSystem.h
diff --git a/libadblockplus-android/jni/JniWebRequest.h b/libadblockplus-android/jni/JniFileSystem.h
similarity index 78%
copy from libadblockplus-android/jni/JniWebRequest.h
copy to libadblockplus-android/jni/JniFileSystem.h
index 76f36b24c1df9f4dea45afb28d83e17943a3c78f..a5dc3de93fb1c35979f85917ef82f3e8cbe6ce1a 100644
--- a/libadblockplus-android/jni/JniWebRequest.h
+++ b/libadblockplus-android/jni/JniFileSystem.h
@@ -15,13 +15,13 @@
* along with Adblock Plus. If not, see .
*/
-#ifndef JNIWEBREQUEST_H
-#define JNIWEBREQUEST_H
+#ifndef JNIFILESYSTEM_H
+#define JNIFILESYSTEM_H
#include
-void JniWebRequest_OnLoad(JavaVM* vm, JNIEnv* env, void* reserved);
+void JniFileSystem_OnLoad(JavaVM* vm, JNIEnv* env, void* reserved);
-void JniWebRequest_OnUnload(JavaVM* vm, JNIEnv* env, void* reserved);
+void JniFileSystem_OnUnload(JavaVM* vm, JNIEnv* env, void* reserved);
-#endif /* JNIWEBREQUEST_H */
\ No newline at end of file
+#endif /* JNIFILESYSTEM_H */
\ No newline at end of file
Index: libadblockplus-android/jni/JniJsEngine.cpp
diff --git a/libadblockplus-android/jni/JniJsEngine.cpp b/libadblockplus-android/jni/JniJsEngine.cpp
index d30cb3376830a5ea43fea013a4e6c4b08b58bea4..ad170a7ce58714005c52922b79b187a949cd92ab 100644
--- a/libadblockplus-android/jni/JniJsEngine.cpp
+++ b/libadblockplus-android/jni/JniJsEngine.cpp
@@ -136,6 +136,18 @@ static void JNICALL JniSetDefaultFileSystem(JNIEnv* env, jclass clazz, jlong ptr
CATCH_AND_THROW(env)
}
+static void JNICALL JniSetFileSystem(JNIEnv* env, jclass clazz, jlong ptr, jlong fileSystemPtr)
+{
+ AdblockPlus::JsEnginePtr& engine = *JniLongToTypePtr(ptr);
+
+ try
+ {
+ AdblockPlus::FileSystemPtr fileSystem = *JniLongToTypePtr(fileSystemPtr);
+ engine->SetFileSystem(fileSystem);
+ }
+ CATCH_AND_THROW(env)
+}
+
static void JNICALL JniSetDefaultWebRequest(JNIEnv* env, jclass clazz, jlong ptr)
{
AdblockPlus::JsEnginePtr& engine = *JniLongToTypePtr(ptr);
@@ -242,6 +254,7 @@ static JNINativeMethod methods[] =
{ (char*)"evaluate", (char*)"(JLjava/lang/String;Ljava/lang/String;)" TYP("JsValue"), (void*)JniEvaluate },
+ { (char*)"setFileSystem", (char*)"(JJ)V", (void*)JniSetFileSystem },
{ (char*)"setDefaultFileSystem", (char*)"(JLjava/lang/String;)V", (void*)JniSetDefaultFileSystem },
{ (char*)"setLogSystem", (char*)"(JJ)V", (void*)JniSetLogSystem },
{ (char*)"setDefaultLogSystem", (char*)"(J)V", (void*)JniSetDefaultLogSystem },
Index: libadblockplus-android/jni/JniLibrary.cpp
diff --git a/libadblockplus-android/jni/JniLibrary.cpp b/libadblockplus-android/jni/JniLibrary.cpp
index 27da9be2588237c1b0abd3524882c6c7a2f83978..2e5812b6efba9ad3e5df46ea94ad68813a3d5491 100644
--- a/libadblockplus-android/jni/JniLibrary.cpp
+++ b/libadblockplus-android/jni/JniLibrary.cpp
@@ -21,6 +21,7 @@
#include "JniCallbacks.h"
#include "JniNotification.h"
#include "JniWebRequest.h"
+#include "JniFileSystem.h"
#include "Utils.h"
jint JNI_OnLoad(JavaVM* vm, void* reserved)
@@ -37,6 +38,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
JniCallbacks_OnLoad(vm, env, reserved);
JniNotification_OnLoad(vm, env, reserved);
JniWebRequest_OnLoad(vm, env, reserved);
+ JniFileSystem_OnLoad(vm, env, reserved);
JniUtils_OnLoad(vm, env, reserved);
return ABP_JNI_VERSION;
@@ -56,5 +58,6 @@ void JNI_OnUnload(JavaVM* vm, void* reserved)
JniCallbacks_OnUnload(vm, env, reserved);
JniNotification_OnUnload(vm, env, reserved);
JniWebRequest_OnUnload(vm, env, reserved);
+ JniFileSystem_OnUnload(vm, env, reserved);
JniUtils_OnUnload(vm, env, reserved);
}
\ No newline at end of file
Index: libadblockplus-android/jni/Utils.h
diff --git a/libadblockplus-android/jni/Utils.h b/libadblockplus-android/jni/Utils.h
index a79518cacc76b3541dbcd649a77fdb1bc9ccdc01..46a6bdef1d30f608fb45a106fbb2de1f84ea4eb7 100644
--- a/libadblockplus-android/jni/Utils.h
+++ b/libadblockplus-android/jni/Utils.h
@@ -31,6 +31,14 @@
#define ABP_JNI_VERSION JNI_VERSION_1_6
+namespace AdblockPlus
+{
+ namespace Utils
+ {
+ std::string Slurp(std::istream& stream);
+ }
+}
+
void JniUtils_OnLoad(JavaVM* vm, JNIEnv* env, void* reserved);
void JniUtils_OnUnload(JavaVM* vm, JNIEnv* env, void* reserved);
Index: libadblockplus-android/src/org/adblockplus/libadblockplus/FileSystem.java
diff --git a/libadblockplus-android/src/org/adblockplus/libadblockplus/FileSystem.java b/libadblockplus-android/src/org/adblockplus/libadblockplus/FileSystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..bd24dc481e42604a70a02926ab7129d0417e93f7
--- /dev/null
+++ b/libadblockplus-android/src/org/adblockplus/libadblockplus/FileSystem.java
@@ -0,0 +1,155 @@
+/*
+ * This file is part of Adblock Plus ,
+ * Copyright (C) 2006-2017 eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see .
+ */
+
+package org.adblockplus.libadblockplus;
+
+public abstract class FileSystem implements Disposable
+{
+ private final Disposer disposer;
+ protected final long ptr;
+
+ static
+ {
+ System.loadLibrary("adblockplus-jni");
+ registerNatives();
+ }
+
+ public FileSystem()
+ {
+ this.ptr = ctor(this);
+ this.disposer = new Disposer(this, new DisposeWrapper(this.ptr));
+ }
+
+ public static final class StatResult
+ {
+ private boolean exists;
+
+ public boolean exists()
+ {
+ return exists;
+ }
+
+ private boolean isDirectory;
+
+ public boolean isDirectory()
+ {
+ return isDirectory;
+ }
+
+ private boolean isFile;
+
+ public boolean isFile()
+ {
+ return isFile;
+ }
+
+ private long lastModified;
+
+ public long getLastModified()
+ {
+ return lastModified;
+ }
+
+ public StatResult(boolean exists, boolean isDirectory, boolean isFile, long lastModified)
+ {
+ this.exists = exists;
+ this.isDirectory = isDirectory;
+ this.isFile = isFile;
+ this.lastModified = lastModified;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "StatResult{" +
+ "exists=" + exists +
+ ", isDirectory=" + isDirectory +
+ ", isFile=" + isFile +
+ ", lastModified=" + lastModified +
+ '}';
+ }
+ }
+
+ /**
+ * Reads from a file.
+ * @param path File path.
+ * @return File's binary data.
+ */
+ public abstract byte[] read(String path);
+
+ /**
+ * Writes to a file.
+ * @param path File path.
+ * @param data File's binary data to write.
+ */
+ public abstract void write(String path, byte[] data);
+
+ /**
+ * Moves a file (i.e.\ renames it).
+ * @param fromPath Current path to the file.
+ * @param toPath New path to the file.
+ */
+ public abstract void move(String fromPath, String toPath);
+
+ /**
+ * Removes a file.
+ * @param path File path.
+ */
+ public abstract void remove(String path);
+
+ /**
+ * Retrieves information about a file.
+ * @param path File path.
+ * @return File information.
+ */
+ public abstract StatResult stat(String path);
+
+ /**
+ * Returns the absolute path to a file.
+ * @param path File path (can be relative or absolute).
+ * @return Absolute file path.
+ */
+ public abstract String resolve(String path);
+
+ @Override
+ public void dispose()
+ {
+ this.disposer.dispose();
+ }
+
+ private final static class DisposeWrapper implements Disposable
+ {
+ private final long ptr;
+
+ public DisposeWrapper(final long ptr)
+ {
+ this.ptr = ptr;
+ }
+
+ @Override
+ public void dispose()
+ {
+ dtor(this.ptr);
+ }
+ }
+
+ private final static native void registerNatives();
+
+ private final static native long ctor(Object callbackObject);
+
+ private final static native void dtor(long ptr);
+}
Index: libadblockplus-android/src/org/adblockplus/libadblockplus/FileSystemUtils.java
diff --git a/libadblockplus-android/src/org/adblockplus/libadblockplus/FileSystemUtils.java b/libadblockplus-android/src/org/adblockplus/libadblockplus/FileSystemUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..d529209a08a5b15c39ad59c2e1ed756b3beba1ff
--- /dev/null
+++ b/libadblockplus-android/src/org/adblockplus/libadblockplus/FileSystemUtils.java
@@ -0,0 +1,135 @@
+/*
+ * This file is part of Adblock Plus ,
+ * Copyright (C) 2006-2017 eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see .
+ */
+
+package org.adblockplus.libadblockplus;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.UUID;
+
+public class FileSystemUtils
+{
+
+ private static byte[] readStream(InputStream inputStream, int length) throws IOException
+ {
+ byte[] data = new byte[length];
+ int bytesRead, totalBytesRead = 0;
+ while ((bytesRead = inputStream.read(data, totalBytesRead, length - totalBytesRead)) > 0)
+ {
+ totalBytesRead += bytesRead;
+ }
+ return data;
+ }
+
+ /**
+ * Convert java signed int to byte
+ * @param b int representation of byte
+ * @return byte representation of byte
+ */
+ public static byte byteFromInt(int b)
+ {
+ return (byte)(b & 0xFF);
+ }
+
+ /**
+ * Read all the file data to string
+ *
+ * @param file path to read data
+ * @return file data
+ * @throws java.io.IOException
+ */
+ public static byte[] readFile(File file) throws IOException
+ {
+ FileInputStream fileInputStream = new FileInputStream(file);
+ try
+ {
+ return readStream(fileInputStream, (int) file.length());
+ }
+ finally
+ {
+ try
+ {
+ fileInputStream.close();
+ }
+ catch (IOException e)
+ {
+ // ignored
+ }
+ }
+ }
+
+ /**
+ * Write data to file (with rewriting)
+ *
+ * @param file file
+ * @param data file data
+ */
+ public static void writeFile(File file, byte[] data) throws IOException
+ {
+ FileOutputStream fos = null;
+ try
+ {
+ fos = new FileOutputStream(file);
+ if (data != null)
+ {
+ fos.write(data);
+ }
+ }
+ finally
+ {
+ if (fos != null)
+ {
+ try
+ {
+ fos.close();
+ }
+ catch (IOException e)
+ {
+ // ignored
+ }
+ }
+ }
+ }
+
+ /**
+ * Generate unique filename
+ *
+ * @param prefix prefix
+ * @param suffix suffix
+ * @return generated unique filename
+ */
+ public static String generateUniqueFileName(String prefix, String suffix)
+ {
+ StringBuilder sb = new StringBuilder();
+ if (prefix != null)
+ {
+ sb.append(prefix);
+ }
+
+ sb.append(UUID.randomUUID().toString());
+
+ if (suffix != null)
+ {
+ sb.append(suffix);
+ }
+
+ return sb.toString();
+ }
+}
Index: libadblockplus-android/src/org/adblockplus/libadblockplus/JsEngine.java
diff --git a/libadblockplus-android/src/org/adblockplus/libadblockplus/JsEngine.java b/libadblockplus-android/src/org/adblockplus/libadblockplus/JsEngine.java
index 3b806122759d8dd8b2d496868bbf401703cf0a1b..a0eed87459be205faaf55dd562125eb0a81529db 100644
--- a/libadblockplus-android/src/org/adblockplus/libadblockplus/JsEngine.java
+++ b/libadblockplus-android/src/org/adblockplus/libadblockplus/JsEngine.java
@@ -78,6 +78,11 @@ public final class JsEngine implements Disposable
triggerEvent(this.ptr, eventName, null);
}
+ public void setFileSystem(FileSystem fileSystem)
+ {
+ setFileSystem(this.ptr, fileSystem.ptr);
+ }
+
public void setDefaultFileSystem(final String basePath)
{
setDefaultFileSystem(this.ptr, basePath);
@@ -152,6 +157,8 @@ public final class JsEngine implements Disposable
private final static native void triggerEvent(long ptr, String eventName, long[] args);
+ private final static native void setFileSystem(long ptr, long fileSystemPtr);
+
private final static native void setDefaultFileSystem(long ptr, String basePath);
private final static native void setLogSystem(long ptr, long logSystemPtr);
Index: libadblockplus-android/src/org/adblockplus/libadblockplus/android/AndroidFileSystem.java
diff --git a/libadblockplus-android/src/org/adblockplus/libadblockplus/android/AndroidFileSystem.java b/libadblockplus-android/src/org/adblockplus/libadblockplus/android/AndroidFileSystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1f3bf709a57420890ab7cb909c11ace960a00ab
--- /dev/null
+++ b/libadblockplus-android/src/org/adblockplus/libadblockplus/android/AndroidFileSystem.java
@@ -0,0 +1,140 @@
+/*
+ * This file is part of Adblock Plus ,
+ * Copyright (C) 2006-2017 eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see .
+ */
+
+package org.adblockplus.libadblockplus.android;
+
+import org.adblockplus.libadblockplus.AdblockPlusException;
+import org.adblockplus.libadblockplus.FileSystem;
+import org.adblockplus.libadblockplus.FileSystemUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * AndroidFileSystem is inefficient for production files routines
+ * as C++ streams can't pass JNI layer and be converted into Java streams and vice versa.
+ *
+ * So in case of any stream routines full stream content is read in either C++ or Java side
+ * and it's being passed as bytes array though JNI layer
+ *
+ * AndroidFileSystem is meant to be used in tests mostly
+ *
+ * All paths are considered relative to the base path, or to be absolute
+ * (see `resolve(String path)`) if no base path is set (see `AndroidFileSystem(File basePath)`)
+ */
+public class AndroidFileSystem extends FileSystem
+{
+ private File basePath;
+
+ public File getBasePath()
+ {
+ return basePath;
+ }
+
+ public AndroidFileSystem()
+ {
+ }
+
+ /*
+ * Sets the base path, all paths are considered relative to it.
+ * @param basePath base path
+ */
+ public AndroidFileSystem(File basePath)
+ {
+ this();
+ this.basePath = basePath;
+ }
+
+ @Override
+ public byte[] read(String path)
+ {
+ File file = new File(path);
+ if (!file.exists())
+ {
+ return null;
+ }
+
+ try
+ {
+ return FileSystemUtils.readFile(file);
+ }
+ catch (IOException e)
+ {
+ throw new AdblockPlusException(e);
+ }
+ }
+
+ @Override
+ public void write(String path, byte[] data)
+ {
+ File file = new File(path);
+ if (file.exists())
+ {
+ file.delete();
+ }
+
+ try
+ {
+ FileSystemUtils.writeFile(file, data);
+ }
+ catch (IOException e)
+ {
+ throw new AdblockPlusException(e);
+ }
+ }
+
+ @Override
+ public void move(String fromPath, String toPath)
+ {
+ File fromFile = new File(fromPath);
+ if (!fromFile.exists())
+ throw new AdblockPlusException("File does not exist: " + fromPath);
+
+ File toFile = new File(toPath);
+ if (!fromFile.renameTo(toFile))
+ {
+ throw new AdblockPlusException("Failed to move " + fromPath + " to " + toFile);
+ }
+ }
+
+ @Override
+ public void remove(String path)
+ {
+ File file = new File(path);
+ if (file.exists())
+ {
+ file.delete();
+ }
+ }
+
+ @Override
+ public StatResult stat(String path)
+ {
+ File file = new File(path);
+ return new StatResult(
+ file.exists(),
+ file.isDirectory(),
+ file.isFile(),
+ file.lastModified());
+ }
+
+ @Override
+ public String resolve(String path)
+ {
+ return (basePath != null ? new File(basePath, path).getAbsolutePath() : path);
+ }
+}