1
0
Fork 0
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:
Felix Ableitner 2014-09-24 18:18:04 +03:00
parent 913d251353
commit 924be98aaa
10 changed files with 264 additions and 23 deletions

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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

View file

@ -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));

View file

@ -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();
}
}

View file

@ -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();

View file

@ -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

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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();
}
}
}