Эх сурвалжийг харах

complete two way synchronization

Bartek Przybylski 12 жил өмнө
parent
commit
ba148a8278

+ 7 - 0
AndroidManifest.xml

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

+ 11 - 1
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) {

+ 28 - 0
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");
+    }
+
+}

+ 72 - 0
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);
+    }
+    
+}

+ 204 - 0
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<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);
+        }
+    }
+}

+ 9 - 3
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;

+ 2 - 0
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;

+ 9 - 2
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);

+ 10 - 1
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: {

+ 83 - 0
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<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);
+        } 
+        
+    }
+}