complete two way synchronization

This commit is contained in:
Bartek Przybylski 2012-08-22 19:32:42 +02:00
parent e7ed69aeed
commit ba148a8278
10 changed files with 435 additions and 7 deletions

View file

@ -33,6 +33,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-sdk
android:minSdkVersion="8"
@ -144,6 +145,12 @@
</intent-filter>
</receiver>
<activity android:name="CrashlogSendActivity"></activity>
<receiver android:name=".files.BootupBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<service android:name=".files.services.FileObserverService"/>
</application>
</manifest>

View file

@ -72,7 +72,7 @@ public class FileDataStorageManager implements DataStorageManager {
c.close();
return file;
}
@Override
public OCFile getFileById(long id) {
Cursor c = getCursorForValue(ProviderTableMeta._ID, String.valueOf(id));
@ -83,6 +83,16 @@ public class FileDataStorageManager implements DataStorageManager {
c.close();
return file;
}
public OCFile getFileByLocalPath(String path) {
Cursor c = getCursorForValue(ProviderTableMeta.FILE_STORAGE_PATH, path);
OCFile file = null;
if (c.moveToFirst()) {
file = createFileInstance(c);
}
c.close();
return file;
}
@Override
public boolean fileExists(long id) {

View file

@ -0,0 +1,28 @@
package com.owncloud.android.files;
import com.owncloud.android.files.services.FileObserverService;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class BootupBroadcastReceiver extends BroadcastReceiver {
private static String TAG = "BootupBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
Log.wtf(TAG, "Incorrect action sent " + intent.getAction());
return;
}
Log.d(TAG, "Starting file observer service...");
Intent i = new Intent(context, FileObserverService.class);
i.putExtra(FileObserverService.KEY_FILE_CMD,
FileObserverService.CMD_INIT_OBSERVED_LIST);
context.startService(i);
Log.d(TAG, "DONE");
}
}

View file

@ -0,0 +1,72 @@
package com.owncloud.android.files;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileUploader;
import android.accounts.Account;
import android.content.Context;
import android.content.Intent;
import android.os.FileObserver;
import android.util.Log;
public class OwnCloudFileObserver extends FileObserver {
public static int CHANGES_ONLY = CLOSE_WRITE | MOVED_FROM | MODIFY;
private static String TAG = "OwnCloudFileObserver";
private String mPath;
private int mMask;
FileDataStorageManager mStorage;
Account mOCAccount;
OCFile mFile;
static Context mContext;
public OwnCloudFileObserver(String path) {
this(path, ALL_EVENTS);
}
public OwnCloudFileObserver(String path, int mask) {
super(path, mask);
mPath = path;
mMask = mask;
}
public void setAccount(Account account) {
mOCAccount = account;
}
public void setStorageManager(FileDataStorageManager manager) {
mStorage = manager;
}
public void setOCFile(OCFile file) {
mFile = file;
}
public void setContext(Context context) {
mContext = context;
}
public String getPath() {
return mPath;
}
@Override
public void onEvent(int event, String path) {
if ((event | mMask) == 0) {
Log.wtf(TAG, "Incorrect event " + event + " sent for file " + path +
" with registered for " + mMask + " and original path " +
mPath);
return;
}
Intent i = new Intent(mContext, FileUploader.class);
i.putExtra(FileUploader.KEY_ACCOUNT, mOCAccount);
i.putExtra(FileUploader.KEY_REMOTE_FILE, mFile.getRemotePath());
i.putExtra(FileUploader.KEY_LOCAL_FILE, mPath);
i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
mContext.startService(i);
}
}

View file

@ -0,0 +1,204 @@
package com.owncloud.android.files.services;
import java.util.ArrayList;
import java.util.List;
import com.owncloud.android.AccountUtils;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
import com.owncloud.android.files.OwnCloudFileObserver;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class FileObserverService extends Service {
public final static String KEY_FILE_CMD = "KEY_FILE_CMD";
public final static String KEY_CMD_ARG = "KEY_CMD_ARG";
public final static int CMD_INIT_OBSERVED_LIST = 1;
public final static int CMD_ADD_OBSERVED_FILE = 2;
public final static int CMD_DEL_OBSERVED_FILE = 3;
private static String TAG = "FileObserverService";
private static List<OwnCloudFileObserver> mObservers;
private static List<DownloadCompletedReceiver> mDownloadReceivers;
private static Object mReceiverListLock = new Object();
private IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
FileObserverService getService() {
return FileObserverService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!intent.hasExtra(KEY_FILE_CMD)) {
Log.e(TAG, "No KEY_FILE_CMD argument given");
return Service.START_STICKY;
}
switch (intent.getIntExtra(KEY_FILE_CMD, -1)) {
case CMD_INIT_OBSERVED_LIST:
initializeObservedList();
break;
case CMD_ADD_OBSERVED_FILE:
addObservedFile(intent.getStringExtra(KEY_CMD_ARG));
break;
case CMD_DEL_OBSERVED_FILE:
removeObservedFile(intent.getStringExtra(KEY_CMD_ARG));
break;
default:
Log.wtf(TAG, "Incorrect key given");
}
return Service.START_STICKY;
}
private void initializeObservedList() {
if (mObservers != null) return; // nothing to do here
mObservers = new ArrayList<OwnCloudFileObserver>();
mDownloadReceivers = new ArrayList<DownloadCompletedReceiver>();
Cursor c = getContentResolver().query(
ProviderTableMeta.CONTENT_URI,
null,
ProviderTableMeta.FILE_KEEP_IN_SYNC + " = ?",
new String[] {String.valueOf(1)},
null);
if (!c.moveToFirst()) return;
AccountManager acm = AccountManager.get(this);
Account[] accounts = acm.getAccounts();
do {
Account account = null;
for (Account a : accounts)
if (a.name.equals(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_ACCOUNT_OWNER)))) {
account = a;
break;
}
if (account == null) continue;
FileDataStorageManager storage =
new FileDataStorageManager(account, getContentResolver());
if (!storage.fileExists(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH))))
continue;
String path = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH));
OwnCloudFileObserver observer =
new OwnCloudFileObserver(path, OwnCloudFileObserver.CHANGES_ONLY);
observer.setContext(getBaseContext());
observer.setAccount(account);
observer.setStorageManager(storage);
observer.setOCFile(storage.getFileByPath(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH))));
observer.startWatching();
mObservers.add(observer);
Log.d(TAG, "Started watching file " + path);
} while (c.moveToNext());
c.close();
}
private void addObservedFile(String path) {
if (path == null) return;
if (mObservers == null) {
// this is very rare case when service was killed by system
// and observers list was deleted in that procedure
initializeObservedList();
}
boolean duplicate = false;
OwnCloudFileObserver observer = null;
for (int i = 0; i < mObservers.size(); ++i) {
observer = mObservers.get(i);
if (observer.getPath().equals(path))
duplicate = true;
observer.setContext(getBaseContext());
}
if (duplicate) return;
observer = new OwnCloudFileObserver(path, OwnCloudFileObserver.CHANGES_ONLY);
observer.setContext(getBaseContext());
Account account = AccountUtils.getCurrentOwnCloudAccount(getBaseContext());
observer.setAccount(account);
FileDataStorageManager storage =
new FileDataStorageManager(account, getContentResolver());
observer.setStorageManager(storage);
observer.setOCFile(storage.getFileByLocalPath(path));
DownloadCompletedReceiver receiver = new DownloadCompletedReceiver(path, observer);
registerReceiver(receiver, new IntentFilter(FileDownloader.DOWNLOAD_FINISH_MESSAGE));
mObservers.add(observer);
Log.d(TAG, "Observer added for path " + path);
}
private void removeObservedFile(String path) {
if (path == null) return;
if (mObservers == null) {
initializeObservedList();
return;
}
for (int i = 0; i < mObservers.size(); ++i) {
OwnCloudFileObserver observer = mObservers.get(i);
if (observer.getPath().equals(path)) {
observer.stopWatching();
mObservers.remove(i);
break;
}
}
Log.d(TAG, "Stopped watching " + path);
}
private static void addReceiverToList(DownloadCompletedReceiver r) {
synchronized(mReceiverListLock) {
mDownloadReceivers.add(r);
}
}
private static void removeReceiverFromList(DownloadCompletedReceiver r) {
synchronized(mReceiverListLock) {
mDownloadReceivers.remove(r);
}
}
private class DownloadCompletedReceiver extends BroadcastReceiver {
String mPath;
OwnCloudFileObserver mObserver;
public DownloadCompletedReceiver(String path, OwnCloudFileObserver observer) {
mPath = path;
mObserver = observer;
addReceiverToList(this);
}
@Override
public void onReceive(Context context, Intent intent) {
if (mPath.equals(intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH))) {
context.unregisterReceiver(this);
removeReceiverFromList(this);
mObserver.startWatching();
Log.d(TAG, "Started watching " + mPath);
return;
}
}
@Override
public boolean equals(Object o) {
if (o instanceof DownloadCompletedReceiver)
return mPath.equals(((DownloadCompletedReceiver)o).mPath);
return super.equals(o);
}
}
}

View file

@ -42,6 +42,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
public static final String KEY_REMOTE_FILE = "REMOTE_FILE";
public static final String KEY_ACCOUNT = "ACCOUNT";
public static final String KEY_UPLOAD_TYPE = "UPLOAD_TYPE";
public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE";
public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
public static final String KEY_MIME_TYPE = "MIME_TYPE";
public static final String KEY_INSTANT_UPLOAD = "INSTANT_UPLOAD";
@ -97,7 +98,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
@Override
public void handleMessage(Message msg) {
uploadFile();
uploadFile(msg.arg2==1?true:false);
stopSelf(msg.arg1);
}
}
@ -146,6 +147,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.arg2 = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false)?1:0;
mServiceHandler.sendMessage(msg);
return Service.START_NOT_STICKY;
@ -155,7 +157,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
/**
* Core upload method: sends the file(s) to upload
*/
public void uploadFile() {
public void uploadFile(boolean force_override) {
FileDataStorageManager storageManager = new FileDataStorageManager(mAccount, getContentResolver());
mTotalDataToSend = mSendData = mPreviousPercent = 0;
@ -213,7 +215,9 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
mCurrentIndexUpload = i;
long parentDirId = -1;
boolean uploadResult = false;
String availablePath = getAvailableRemotePath(wc, mRemotePaths[i]);
String availablePath = mRemotePaths[i];
if (!force_override)
availablePath = getAvailableRemotePath(wc, mRemotePaths[i]);
try {
File f = new File(mRemotePaths[i]);
parentDirId = storageManager.getFileByPath(f.getParent().endsWith("/")?f.getParent():f.getParent()+"/").getFileId();
@ -228,6 +232,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
new_file.setLastSyncDate(0);
new_file.setStoragePath(mLocalPaths[i]);
new_file.setParentId(parentDirId);
if (force_override)
new_file.setKeepInSync(true);
storageManager.saveFile(new_file);
mSuccessCounter++;
uploadResult = true;

View file

@ -72,6 +72,8 @@ public class FileContentProvider extends ContentProvider {
ProviderTableMeta.FILE_LAST_SYNC_DATE);
mProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC,
ProviderTableMeta.FILE_KEEP_IN_SYNC);
mProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER,
ProviderTableMeta.FILE_ACCOUNT_OWNER);
}
private static final int SINGLE_FILE = 1;

View file

@ -40,6 +40,7 @@ import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
@ -65,7 +66,10 @@ import com.owncloud.android.authenticator.AccountAuthenticator;
import com.owncloud.android.datamodel.DataStorageManager;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
import com.owncloud.android.files.OwnCloudFileObserver;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileObserverService;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.syncadapter.FileSyncService;
import com.owncloud.android.ui.fragment.FileDetailFragment;
@ -108,12 +112,11 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
private static final String TAG = "FileDisplayActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(getClass().toString(), "onCreate() start");
super.onCreate(savedInstanceState);
Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(getApplicationContext()));
/// saved instance state: keep this always before initDataFromCurrentAccount()
@ -137,6 +140,10 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
requestPinCode();
}
// file observer
Intent observer_intent = new Intent(this, FileObserverService.class);
observer_intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_INIT_OBSERVED_LIST);
startService(observer_intent);
/// USER INTERFACE
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);

View file

@ -79,6 +79,7 @@ import com.owncloud.android.authenticator.AccountAuthenticator;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileObserverService;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.ui.activity.FileDetailActivity;
import com.owncloud.android.ui.activity.FileDisplayActivity;
@ -263,9 +264,17 @@ public class FileDetailFragment extends SherlockFragment implements
fdsm.saveFile(mFile);
if (mFile.keepInSync()) {
onClick(getView().findViewById(R.id.fdDownloadBtn));
} else {
} else {
mContainerActivity.onFileStateChanged(); // put inside 'else' to not call it twice (here, and in the virtual click on fdDownloadBtn)
}
Intent intent = new Intent(getActivity().getApplicationContext(),
FileObserverService.class);
intent.putExtra(FileObserverService.KEY_FILE_CMD,
(cb.isChecked()?
FileObserverService.CMD_ADD_OBSERVED_FILE:
FileObserverService.CMD_DEL_OBSERVED_FILE));
intent.putExtra(FileObserverService.KEY_CMD_ARG, mFile.getStoragePath());
getActivity().startService(intent);
break;
}
case R.id.fdRenameBtn: {

View file

@ -0,0 +1,83 @@
package com.owncloud.android.utils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import android.os.FileObserver;
public class RecursiveFileObserver extends FileObserver {
public static int CHANGES_ONLY = CLOSE_WRITE | MOVE_SELF | MOVED_FROM;
List<SingleFileObserver> mObservers;
String mPath;
int mMask;
public RecursiveFileObserver(String path) {
this(path, ALL_EVENTS);
}
public RecursiveFileObserver(String path, int mask) {
super(path, mask);
mPath = path;
mMask = mask;
}
@Override
public void startWatching() {
if (mObservers != null) return;
mObservers = new ArrayList<SingleFileObserver>();
Stack<String> stack = new Stack<String>();
stack.push(mPath);
while (!stack.empty()) {
String parent = stack.pop();
mObservers.add(new SingleFileObserver(parent, mMask));
File path = new File(parent);
File[] files = path.listFiles();
if (files == null) continue;
for (int i = 0; i < files.length; ++i) {
if (files[i].isDirectory() && !files[i].getName().equals(".")
&& !files[i].getName().equals("..")) {
stack.push(files[i].getPath());
}
}
}
for (int i = 0; i < mObservers.size(); i++)
mObservers.get(i).startWatching();
}
@Override
public void stopWatching() {
if (mObservers == null) return;
for (int i = 0; i < mObservers.size(); ++i)
mObservers.get(i).stopWatching();
mObservers.clear();
mObservers = null;
}
@Override
public void onEvent(int event, String path) {
}
private class SingleFileObserver extends FileObserver {
private String mPath;
public SingleFileObserver(String path, int mask) {
super(path, mask);
mPath = path;
}
@Override
public void onEvent(int event, String path) {
String newPath = mPath + "/" + path;
RecursiveFileObserver.this.onEvent(event, newPath);
}
}
}