mirror of
https://github.com/syncthing/syncthing-android.git
synced 2025-01-11 04:25:53 +00:00
Added inotifiy support.
This commit is contained in:
parent
913d251353
commit
924be98aaa
10 changed files with 264 additions and 23 deletions
|
@ -69,22 +69,22 @@ public class MockRestApi extends RestApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void editNode(final Node node,
|
public void editNode(Node node, Activity activity, OnNodeIdNormalizedListener listener) {
|
||||||
final OnNodeIdNormalizedListener listener) {
|
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteNode(Node node, Context context) {
|
public boolean deleteNode(Node node, Activity activity) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean editRepo(Repo repo, boolean create, Context context) {
|
public boolean editRepo(Repo repo, boolean create, Activity activity) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean deleteRepo(Repo repo, Context context) {
|
@Override
|
||||||
|
public boolean deleteRepo(Repo repo, Activity activity) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,4 +99,8 @@ public class MockRestApi extends RestApi {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRepoFileChange(String repoId, String relativePath) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.nutomic.syncthingandroid.test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class Util {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given folder and all contents.
|
||||||
|
*/
|
||||||
|
public static void deleteRecursive(File file) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
for (File f : file.listFiles()) {
|
||||||
|
deleteRecursive(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package com.nutomic.syncthingandroid.test.syncthing;
|
package com.nutomic.syncthingandroid.test.syncthing;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.test.AndroidTestCase;
|
import android.test.AndroidTestCase;
|
||||||
import android.test.suitebuilder.annotation.LargeTest;
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
import android.test.suitebuilder.annotation.MediumTest;
|
import android.test.suitebuilder.annotation.MediumTest;
|
||||||
|
@ -146,7 +147,7 @@ public class RestApiTest extends AndroidTestCase {
|
||||||
node.Addresses = "dynamic";
|
node.Addresses = "dynamic";
|
||||||
node.Name = "my node";
|
node.Name = "my node";
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
mApi.editNode(node, new RestApi.OnNodeIdNormalizedListener() {
|
mApi.editNode(node, new Activity(), new RestApi.OnNodeIdNormalizedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onNodeIdNormalized(String normalizedId, String error) {
|
public void onNodeIdNormalized(String normalizedId, String error) {
|
||||||
assertEquals(node.NodeID, normalizedId);
|
assertEquals(node.NodeID, normalizedId);
|
||||||
|
@ -156,7 +157,7 @@ public class RestApiTest extends AndroidTestCase {
|
||||||
});
|
});
|
||||||
latch.await(10, TimeUnit.SECONDS);
|
latch.await(10, TimeUnit.SECONDS);
|
||||||
|
|
||||||
assertTrue(mApi.deleteNode(node, getContext()));
|
assertTrue(mApi.deleteNode(node, new Activity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
|
@ -164,12 +165,12 @@ public class RestApiTest extends AndroidTestCase {
|
||||||
RestApi.Repo repo = new RestApi.Repo();
|
RestApi.Repo repo = new RestApi.Repo();
|
||||||
repo.Directory = "/my/dir";
|
repo.Directory = "/my/dir";
|
||||||
repo.ID = "my-repo";
|
repo.ID = "my-repo";
|
||||||
repo.Nodes = new ArrayList<>();
|
repo.NodeIds = new ArrayList<>();
|
||||||
repo.ReadOnly = false;
|
repo.ReadOnly = false;
|
||||||
repo.Versioning = new RestApi.Versioning();
|
repo.Versioning = new RestApi.Versioning();
|
||||||
assertTrue(mApi.editRepo(repo, true, getContext()));
|
assertTrue(mApi.editRepo(repo, true, new Activity()));
|
||||||
|
|
||||||
assertTrue(mApi.deleteRepo(repo, getContext()));
|
assertTrue(mApi.deleteRepo(repo, new Activity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
|
|
|
@ -12,6 +12,7 @@ import com.nutomic.syncthingandroid.syncthing.DeviceStateHolder;
|
||||||
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
||||||
import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder;
|
import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder;
|
||||||
import com.nutomic.syncthingandroid.test.MockContext;
|
import com.nutomic.syncthingandroid.test.MockContext;
|
||||||
|
import com.nutomic.syncthingandroid.test.Util;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -41,21 +42,11 @@ public class SyncthingServiceTest extends ServiceTestCase<SyncthingService> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void tearDown() throws Exception {
|
protected void tearDown() throws Exception {
|
||||||
deleteRecursive(getContext().getFilesDir());
|
Util.deleteRecursive(getContext().getFilesDir());
|
||||||
PreferenceManager.getDefaultSharedPreferences(getContext()).edit().clear().commit();
|
PreferenceManager.getDefaultSharedPreferences(getContext()).edit().clear().commit();
|
||||||
|
|
||||||
super.tearDown();
|
super.tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteRecursive(File file) {
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
for (File f : file.listFiles()) {
|
|
||||||
deleteRecursive(f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
public void testStartService() throws InterruptedException {
|
public void testStartService() throws InterruptedException {
|
||||||
startService(new Intent(mContext, SyncthingService.class));
|
startService(new Intent(mContext, SyncthingService.class));
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
package com.nutomic.syncthingandroid.test.util;
|
||||||
|
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
import android.test.suitebuilder.annotation.MediumTest;
|
||||||
|
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||||
|
import com.nutomic.syncthingandroid.test.MockContext;
|
||||||
|
import com.nutomic.syncthingandroid.test.Util;
|
||||||
|
import com.nutomic.syncthingandroid.util.RepoObserver;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class RepoObserverTest extends AndroidTestCase
|
||||||
|
implements RepoObserver.OnRepoFileChangeListener {
|
||||||
|
|
||||||
|
private File mTestFolder;
|
||||||
|
|
||||||
|
private String mCurrentTest;
|
||||||
|
|
||||||
|
private CountDownLatch mLatch;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
mTestFolder = new File(new MockContext(getContext()).getFilesDir(), "observer-test");
|
||||||
|
mTestFolder.mkdir();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void tearDown() throws Exception {
|
||||||
|
Util.deleteRecursive(mTestFolder);
|
||||||
|
super.tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRepoFileChange(String repoId, String relativePath) {
|
||||||
|
mLatch.countDown();
|
||||||
|
assertEquals(mCurrentTest, repoId);
|
||||||
|
assertFalse(relativePath.endsWith("should-not-notifiy"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RestApi.Repo createRepo(String id) {
|
||||||
|
RestApi.Repo r = new RestApi.Repo();
|
||||||
|
r.Directory = mTestFolder.getAbsolutePath();
|
||||||
|
r.ID = id;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testRecursion() throws IOException, InterruptedException {
|
||||||
|
mCurrentTest = "testRecursion";
|
||||||
|
File subFolder = new File(mTestFolder, "subfolder");
|
||||||
|
subFolder.mkdir();
|
||||||
|
RepoObserver ro = new RepoObserver(this, createRepo(mCurrentTest));
|
||||||
|
File testFile = new File(subFolder, "test");
|
||||||
|
mLatch = new CountDownLatch(1);
|
||||||
|
testFile.createNewFile();
|
||||||
|
mLatch.await(1, TimeUnit.SECONDS);
|
||||||
|
ro.stopWatching();
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testRemoveDirectory() throws IOException {
|
||||||
|
mCurrentTest = "testRemoveDirectory";
|
||||||
|
File subFolder = new File(mTestFolder, "subfolder");
|
||||||
|
subFolder.mkdir();
|
||||||
|
RepoObserver ro = new RepoObserver(this, createRepo(mCurrentTest));
|
||||||
|
File movedSubFolder = new File(getContext().getFilesDir(), subFolder.getName());
|
||||||
|
subFolder.renameTo(movedSubFolder);
|
||||||
|
File testFile = new File(movedSubFolder, "should-not-notifiy");
|
||||||
|
mLatch = new CountDownLatch(1);
|
||||||
|
testFile.createNewFile();
|
||||||
|
ro.stopWatching();
|
||||||
|
Util.deleteRecursive(subFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
public void testAddDirectory() throws IOException, InterruptedException {
|
||||||
|
mCurrentTest = "testAddDirectory";
|
||||||
|
File subFolder = new File(mTestFolder, "subfolder");
|
||||||
|
RepoObserver ro = new RepoObserver(this, createRepo(mCurrentTest));
|
||||||
|
subFolder.mkdir();
|
||||||
|
File testFile = new File(subFolder, "test");
|
||||||
|
mLatch = new CountDownLatch(1);
|
||||||
|
testFile.createNewFile();
|
||||||
|
mLatch.await(1, TimeUnit.SECONDS);
|
||||||
|
ro.stopWatching();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ public class ReposAdapterTest extends AndroidTestCase {
|
||||||
mRepo.Directory = "/my/dir/";
|
mRepo.Directory = "/my/dir/";
|
||||||
mRepo.ID = "id 123";
|
mRepo.ID = "id 123";
|
||||||
mRepo.Invalid = "all good";
|
mRepo.Invalid = "all good";
|
||||||
mRepo.Nodes = new ArrayList<>();
|
mRepo.NodeIds = new ArrayList<>();
|
||||||
mRepo.ReadOnly = false;
|
mRepo.ReadOnly = false;
|
||||||
mRepo.Versioning = new RestApi.Versioning();
|
mRepo.Versioning = new RestApi.Versioning();
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ public class PostTask extends AsyncTask<String, Void, Boolean> {
|
||||||
|
|
||||||
public static final String URI_SHUTDOWN = "/rest/shutdown";
|
public static final String URI_SHUTDOWN = "/rest/shutdown";
|
||||||
|
|
||||||
|
public static final String URI_SCAN = "/rest/scan";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* params[0] Syncthing hostname
|
* params[0] Syncthing hostname
|
||||||
* params[1] URI to call
|
* params[1] URI to call
|
||||||
|
|
|
@ -18,11 +18,13 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import com.nutomic.syncthingandroid.BuildConfig;
|
import com.nutomic.syncthingandroid.BuildConfig;
|
||||||
import com.nutomic.syncthingandroid.R;
|
import com.nutomic.syncthingandroid.R;
|
||||||
|
import com.nutomic.syncthingandroid.util.RepoObserver;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -33,7 +35,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
/**
|
/**
|
||||||
* Provides functions to interact with the syncthing REST API.
|
* Provides functions to interact with the syncthing REST API.
|
||||||
*/
|
*/
|
||||||
public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
|
||||||
|
RepoObserver.OnRepoFileChangeListener {
|
||||||
|
|
||||||
private static final String TAG = "RestApi";
|
private static final String TAG = "RestApi";
|
||||||
|
|
||||||
|
@ -887,4 +890,13 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force a rescan of the given subdirectory in repository.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onRepoFileChange(String repoId, String relativePath) {
|
||||||
|
new PostTask().execute(mUrl, PostTask.URI_SCAN, mApiKey, "repo", repoId, "sub",
|
||||||
|
relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,14 @@ import android.util.Log;
|
||||||
import com.nutomic.syncthingandroid.R;
|
import com.nutomic.syncthingandroid.R;
|
||||||
import com.nutomic.syncthingandroid.activities.SettingsActivity;
|
import com.nutomic.syncthingandroid.activities.SettingsActivity;
|
||||||
import com.nutomic.syncthingandroid.util.ConfigXml;
|
import com.nutomic.syncthingandroid.util.ConfigXml;
|
||||||
|
import com.nutomic.syncthingandroid.util.RepoObserver;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the native syncthing instance and provides an API to access it.
|
* Holds the native syncthing instance and provides an API to access it.
|
||||||
|
@ -62,6 +64,8 @@ public class SyncthingService extends Service {
|
||||||
|
|
||||||
private RestApi mApi;
|
private RestApi mApi;
|
||||||
|
|
||||||
|
private LinkedList<RepoObserver> mObservers = new LinkedList<>();
|
||||||
|
|
||||||
private SyncthingRunnable mSyncthingRunnable;
|
private SyncthingRunnable mSyncthingRunnable;
|
||||||
|
|
||||||
private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this);
|
private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this);
|
||||||
|
@ -168,6 +172,10 @@ public class SyncthingService extends Service {
|
||||||
mStopScheduled = true;
|
mStopScheduled = true;
|
||||||
} else if (mApi != null) {
|
} else if (mApi != null) {
|
||||||
mApi.shutdown();
|
mApi.shutdown();
|
||||||
|
for (RepoObserver ro : mObservers) {
|
||||||
|
ro.stopWatching();
|
||||||
|
}
|
||||||
|
mObservers.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onApiChange();
|
onApiChange();
|
||||||
|
@ -241,6 +249,9 @@ public class SyncthingService extends Service {
|
||||||
@Override
|
@Override
|
||||||
public void onApiAvailable() {
|
public void onApiAvailable() {
|
||||||
onApiChange();
|
onApiChange();
|
||||||
|
for (RestApi.Repo r : mApi.getRepos()) {
|
||||||
|
mObservers.add(new RepoObserver(mApi, r));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
registerOnWebGuiAvailableListener(mApi);
|
registerOnWebGuiAvailableListener(mApi);
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
package com.nutomic.syncthingandroid.util;
|
||||||
|
|
||||||
|
import android.os.FileObserver;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.nutomic.syncthingandroid.syncthing.RestApi;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FilenameFilter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively watches a directory and all subfolders.
|
||||||
|
*/
|
||||||
|
public class RepoObserver extends FileObserver {
|
||||||
|
|
||||||
|
private static final String TAG = "RepoObserver";
|
||||||
|
|
||||||
|
private final OnRepoFileChangeListener mListener;
|
||||||
|
|
||||||
|
private final RestApi.Repo mRepo;
|
||||||
|
|
||||||
|
private final String mPath;
|
||||||
|
|
||||||
|
private final ArrayList<RepoObserver> mChilds;
|
||||||
|
|
||||||
|
public interface OnRepoFileChangeListener {
|
||||||
|
public void onRepoFileChange(String repoId, String relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RepoObserver(OnRepoFileChangeListener listener, RestApi.Repo repo) {
|
||||||
|
this(listener, repo, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs watcher and starts watching the given directory recursively.
|
||||||
|
*
|
||||||
|
* @param listener The listener where changes should be sent to.
|
||||||
|
* @param repo The repository where this folder belongs to.
|
||||||
|
* @param path Path to the monitored folder, relative to repo root.
|
||||||
|
*/
|
||||||
|
private RepoObserver(OnRepoFileChangeListener listener, RestApi.Repo repo, String path) {
|
||||||
|
super(repo.Directory + "/" + path,
|
||||||
|
ATTRIB | CLOSE_WRITE | CREATE | DELETE | DELETE_SELF | MODIFY | MOVED_FROM |
|
||||||
|
MOVED_TO | MOVE_SELF);
|
||||||
|
mListener = listener;
|
||||||
|
mRepo = repo;
|
||||||
|
mPath = path;
|
||||||
|
Log.v(TAG, "observer created for " + path + " in " + repo.ID);
|
||||||
|
startWatching();
|
||||||
|
|
||||||
|
File[] directories = new File(repo.Directory, path).listFiles(new FilenameFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File current, String name) {
|
||||||
|
return new File(current, name).isDirectory();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mChilds = new ArrayList<>(directories.length);
|
||||||
|
for (File f : directories) {
|
||||||
|
mChilds.add(new RepoObserver(mListener, mRepo, path + "/" + f.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles incoming events for changed files.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onEvent(int event, String path) {
|
||||||
|
// Ignore some weird events that we may receive.
|
||||||
|
event &= FileObserver.ALL_EVENTS;
|
||||||
|
if (event == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
case MOVED_FROM:
|
||||||
|
// fall through
|
||||||
|
case DELETE_SELF:
|
||||||
|
// fall through
|
||||||
|
case DELETE:
|
||||||
|
for (RepoObserver ro : mChilds) {
|
||||||
|
if (ro.mPath.equals(path)) {
|
||||||
|
mChilds.remove(ro);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MOVED_TO:
|
||||||
|
// fall through
|
||||||
|
case CREATE:
|
||||||
|
mChilds.add(new RepoObserver(mListener, mRepo, path));
|
||||||
|
// fall through
|
||||||
|
default:
|
||||||
|
mListener.onRepoFileChange(mRepo.ID, new File(mPath, path).getPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively stops watching the directory.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void stopWatching() {
|
||||||
|
super.stopWatching();
|
||||||
|
for (RepoObserver ro : mChilds) {
|
||||||
|
ro.stopWatching();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue