From: Bartek Przybylski Date: Wed, 22 Aug 2012 17:32:42 +0000 (+0200) Subject: complete two way synchronization X-Git-Tag: oc-android-1.4.3~194 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/ba148a82782bba94b95b8acd2ecb19af797fa84a?ds=inline complete two way synchronization --- diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d2663ec0..87fb090d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -33,6 +33,7 @@ + + + + + + + diff --git a/src/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/com/owncloud/android/datamodel/FileDataStorageManager.java index 7bb29d46..244d6d1d 100644 --- a/src/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -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) { diff --git a/src/com/owncloud/android/files/BootupBroadcastReceiver.java b/src/com/owncloud/android/files/BootupBroadcastReceiver.java new file mode 100644 index 00000000..bd822662 --- /dev/null +++ b/src/com/owncloud/android/files/BootupBroadcastReceiver.java @@ -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"); + } + +} diff --git a/src/com/owncloud/android/files/OwnCloudFileObserver.java b/src/com/owncloud/android/files/OwnCloudFileObserver.java new file mode 100644 index 00000000..b01b8fa3 --- /dev/null +++ b/src/com/owncloud/android/files/OwnCloudFileObserver.java @@ -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); + } + +} diff --git a/src/com/owncloud/android/files/services/FileObserverService.java b/src/com/owncloud/android/files/services/FileObserverService.java new file mode 100644 index 00000000..a8fb7025 --- /dev/null +++ b/src/com/owncloud/android/files/services/FileObserverService.java @@ -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 mObservers; + private static List 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(); + mDownloadReceivers = new ArrayList(); + 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); + } + } +} diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java index 298c93b2..10f05c48 100644 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -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; diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index 6605d89b..980e0457 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -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; diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index 59560048..53b6aed4 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -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); diff --git a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java index 979930d1..5c9b122d 100644 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -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: { diff --git a/src/com/owncloud/android/utils/RecursiveFileObserver.java b/src/com/owncloud/android/utils/RecursiveFileObserver.java new file mode 100644 index 00000000..20de3d68 --- /dev/null +++ b/src/com/owncloud/android/utils/RecursiveFileObserver.java @@ -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 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(); + Stack stack = new Stack(); + 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); + } + + } +}