mirror of
https://github.com/syncthing/syncthing-android.git
synced 2025-01-10 20:15:54 +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
|
||||
public void editNode(final Node node,
|
||||
final OnNodeIdNormalizedListener listener) {
|
||||
public void editNode(Node node, Activity activity, OnNodeIdNormalizedListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteNode(Node node, Context context) {
|
||||
public boolean deleteNode(Node node, Activity activity) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean editRepo(Repo repo, boolean create, Context context) {
|
||||
public boolean editRepo(Repo repo, boolean create, Activity activity) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public boolean deleteRepo(Repo repo, Context context) {
|
||||
@Override
|
||||
public boolean deleteRepo(Repo repo, Activity activity) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
@ -99,4 +99,8 @@ public class MockRestApi extends RestApi {
|
|||
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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
import android.test.suitebuilder.annotation.MediumTest;
|
||||
|
@ -146,7 +147,7 @@ public class RestApiTest extends AndroidTestCase {
|
|||
node.Addresses = "dynamic";
|
||||
node.Name = "my node";
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
mApi.editNode(node, new RestApi.OnNodeIdNormalizedListener() {
|
||||
mApi.editNode(node, new Activity(), new RestApi.OnNodeIdNormalizedListener() {
|
||||
@Override
|
||||
public void onNodeIdNormalized(String normalizedId, String error) {
|
||||
assertEquals(node.NodeID, normalizedId);
|
||||
|
@ -156,7 +157,7 @@ public class RestApiTest extends AndroidTestCase {
|
|||
});
|
||||
latch.await(10, TimeUnit.SECONDS);
|
||||
|
||||
assertTrue(mApi.deleteNode(node, getContext()));
|
||||
assertTrue(mApi.deleteNode(node, new Activity()));
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
|
@ -164,12 +165,12 @@ public class RestApiTest extends AndroidTestCase {
|
|||
RestApi.Repo repo = new RestApi.Repo();
|
||||
repo.Directory = "/my/dir";
|
||||
repo.ID = "my-repo";
|
||||
repo.Nodes = new ArrayList<>();
|
||||
repo.NodeIds = new ArrayList<>();
|
||||
repo.ReadOnly = false;
|
||||
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
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.nutomic.syncthingandroid.syncthing.DeviceStateHolder;
|
|||
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
|
||||
import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder;
|
||||
import com.nutomic.syncthingandroid.test.MockContext;
|
||||
import com.nutomic.syncthingandroid.test.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -41,21 +42,11 @@ public class SyncthingServiceTest extends ServiceTestCase<SyncthingService> {
|
|||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
deleteRecursive(getContext().getFilesDir());
|
||||
Util.deleteRecursive(getContext().getFilesDir());
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext()).edit().clear().commit();
|
||||
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
private void deleteRecursive(File file) {
|
||||
if (file.isDirectory()) {
|
||||
for (File f : file.listFiles()) {
|
||||
deleteRecursive(f);
|
||||
}
|
||||
}
|
||||
file.delete();
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
public void testStartService() throws InterruptedException {
|
||||
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.ID = "id 123";
|
||||
mRepo.Invalid = "all good";
|
||||
mRepo.Nodes = new ArrayList<>();
|
||||
mRepo.NodeIds = new ArrayList<>();
|
||||
mRepo.ReadOnly = false;
|
||||
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_SCAN = "/rest/scan";
|
||||
|
||||
/**
|
||||
* params[0] Syncthing hostname
|
||||
* params[1] URI to call
|
||||
|
|
|
@ -18,11 +18,13 @@ import android.widget.Toast;
|
|||
|
||||
import com.nutomic.syncthingandroid.BuildConfig;
|
||||
import com.nutomic.syncthingandroid.R;
|
||||
import com.nutomic.syncthingandroid.util.RepoObserver;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
@ -33,7 +35,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
/**
|
||||
* 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";
|
||||
|
||||
|
@ -887,4 +890,13 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
|
|||
.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.activities.SettingsActivity;
|
||||
import com.nutomic.syncthingandroid.util.ConfigXml;
|
||||
import com.nutomic.syncthingandroid.util.RepoObserver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* 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 LinkedList<RepoObserver> mObservers = new LinkedList<>();
|
||||
|
||||
private SyncthingRunnable mSyncthingRunnable;
|
||||
|
||||
private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this);
|
||||
|
@ -168,6 +172,10 @@ public class SyncthingService extends Service {
|
|||
mStopScheduled = true;
|
||||
} else if (mApi != null) {
|
||||
mApi.shutdown();
|
||||
for (RepoObserver ro : mObservers) {
|
||||
ro.stopWatching();
|
||||
}
|
||||
mObservers.clear();
|
||||
}
|
||||
}
|
||||
onApiChange();
|
||||
|
@ -241,6 +249,9 @@ public class SyncthingService extends Service {
|
|||
@Override
|
||||
public void onApiAvailable() {
|
||||
onApiChange();
|
||||
for (RestApi.Repo r : mApi.getRepos()) {
|
||||
mObservers.add(new RepoObserver(mApi, r));
|
||||
}
|
||||
}
|
||||
});
|
||||
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