浏览代码

Download button in file details view upgraded to sync file content in both directions

David A. Velasco 12 年之前
父节点
当前提交
ff2271a8c5

+ 7 - 4
res/values/strings.xml

@@ -169,10 +169,10 @@
     <string name="common_rename">Rename</string>
     <string name="common_remove">Remove</string>
     
-	  <string name="confirmation_remove_alert">"Do you really want to remove %1$s ?"</string>
-	  <string name="confirmation_remove_local">Local only</string>
-	  <string name="confirmation_remove_remote">Remove from server</string>
-	  <string name="confirmation_remove_remote_and_local">Remote and local</string>
+	<string name="confirmation_remove_alert">"Do you really want to remove %1$s ?"</string>
+	<string name="confirmation_remove_local">Local only</string>
+	<string name="confirmation_remove_remote">Remove from server</string>
+	<string name="confirmation_remove_remote_and_local">Remote and local</string>
 
     <string name="remove_success_msg">"Successful removal"</string>
     <string name="remove_fail_msg">"Removal could not be completed"</string>
@@ -180,6 +180,9 @@
     <string name="rename_local_fail_msg">"Local copy could not be renamed; try a differente new name"</string>
     <string name="rename_server_fail_msg">"Rename could not be completed"</string>
         
+    <string name="sync_file_fail_msg">Remote file could not be checked</string> 
+    <string name="sync_file_nothing_to_do_msg">File contents already synchronized</string> 
+    
     <string name="create_dir_fail_msg">Directory could not be created</string>
     
     <string name="wait_a_moment">Wait a moment</string>

+ 7 - 3
src/com/owncloud/android/datamodel/FileDataStorageManager.java

@@ -118,7 +118,8 @@ public class FileDataStorageManager implements DataStorageManager {
         if (!file.isDirectory())
             cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
         cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
-        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDate());
+        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties());
+        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData());
         cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0);
 
         if (fileExists(file.getRemotePath())) {
@@ -197,7 +198,8 @@ public class FileDataStorageManager implements DataStorageManager {
             if (!file.isDirectory())
                 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
             cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
-            cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDate());
+            cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties());
+            cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData());
             cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0);
 
             if (fileExists(file.getRemotePath())) {
@@ -401,8 +403,10 @@ public class FileDataStorageManager implements DataStorageManager {
                     .getColumnIndex(ProviderTableMeta.FILE_CREATION)));
             file.setModificationTimestamp(c.getLong(c
                     .getColumnIndex(ProviderTableMeta.FILE_MODIFIED)));
-            file.setLastSyncDate(c.getLong(c
+            file.setLastSyncDateForProperties(c.getLong(c
                     .getColumnIndex(ProviderTableMeta.FILE_LAST_SYNC_DATE)));
+            file.setLastSyncDateForData(c.getLong(c.
+                    getColumnIndex(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA)));
             file.setKeepInSync(c.getInt(
                                 c.getColumnIndex(ProviderTableMeta.FILE_KEEP_IN_SYNC)) == 1 ? true : false);
         }

+ 34 - 8
src/com/owncloud/android/datamodel/OCFile.java

@@ -48,9 +48,12 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     private String mLocalPath;
     private String mMimeType;
     private boolean mNeedsUpdating;
-    private long mLastSyncDate;
+    private long mLastSyncDateForProperties;
+    private long mLastSyncDateForData;
     private boolean mKeepInSync;
 
+    private String mEtag;
+
     /**
      * Create new {@link OCFile} with given path.
      * 
@@ -83,7 +86,8 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         mMimeType = source.readString();
         mNeedsUpdating = source.readInt() == 0;
         mKeepInSync = source.readInt() == 1;
-        mLastSyncDate = source.readLong();
+        mLastSyncDateForProperties = source.readLong();
+        mLastSyncDateForData = source.readLong();
     }
 
     @Override
@@ -98,7 +102,8 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         dest.writeString(mMimeType);
         dest.writeInt(mNeedsUpdating ? 1 : 0);
         dest.writeInt(mKeepInSync ? 1 : 0);
-        dest.writeLong(mLastSyncDate);
+        dest.writeLong(mLastSyncDateForProperties);
+        dest.writeLong(mLastSyncDateForData);
     }
     
     /**
@@ -254,7 +259,8 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         mLength = 0;
         mCreationTimestamp = 0;
         mModifiedTimestamp = 0;
-        mLastSyncDate = 0;
+        mLastSyncDateForProperties = 0;
+        mLastSyncDateForData = 0;
         mKeepInSync = false;
         mNeedsUpdating = false;
     }
@@ -322,12 +328,20 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         return mNeedsUpdating;
     }
     
-    public long getLastSyncDate() {
-        return mLastSyncDate;
+    public long getLastSyncDateForProperties() {
+        return mLastSyncDateForProperties;
+    }
+    
+    public void setLastSyncDateForProperties(long lastSyncDate) {
+        mLastSyncDateForProperties = lastSyncDate;
     }
     
-    public void setLastSyncDate(long lastSyncDate) {
-        mLastSyncDate = lastSyncDate;
+    public long getLastSyncDateForData() {
+        return mLastSyncDateForData;
+    }
+
+    public void setLastSyncDateForData(long lastSyncDate) {
+        mLastSyncDateForData = lastSyncDate;
     }
 
     public void setKeepInSync(boolean keepInSync) {
@@ -374,4 +388,16 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         return asString;
     }
 
+    public String getEtag() {
+        return mEtag;
+    }
+
+    public long getLocalModificationTimestamp() {
+        if (mLocalPath != null && mLocalPath.length() > 0) {
+            File f = new File(mLocalPath);
+            return f.lastModified();
+        }
+        return 0;
+    }
+
 }

+ 4 - 2
src/com/owncloud/android/db/ProviderMeta.java

@@ -31,7 +31,8 @@ public class ProviderMeta {
     public static final String AUTHORITY_FILES = "org.owncloud";
     public static final String DB_FILE = "owncloud.db";
     public static final String DB_NAME = "filelist";
-    public static final int DB_VERSION = 2;
+    //public static final int DB_VERSION = 2;
+    public static final int DB_VERSION = 3;
 
     private ProviderMeta() {
     }
@@ -57,7 +58,8 @@ public class ProviderMeta {
         public static final String FILE_STORAGE_PATH = "media_path";
         public static final String FILE_PATH = "path";
         public static final String FILE_ACCOUNT_OWNER = "file_owner";
-        public static final String FILE_LAST_SYNC_DATE = "last_sync_date";
+        public static final String FILE_LAST_SYNC_DATE = "last_sync_date";  // _for_properties, but let's keep it as it is
+        public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data";
         public static final String FILE_KEEP_IN_SYNC = "keep_in_sync";
 
         public static final String DEFAULT_SORT_ORDER = FILE_NAME

+ 7 - 28
src/com/owncloud/android/files/OwnCloudFileObserver.java

@@ -24,8 +24,6 @@ import java.util.List;
 
 import com.owncloud.android.datamodel.DataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.OwnCloudFileObserver.FileObserverStatusListener.Status;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.network.OwnCloudClientUtils;
 import com.owncloud.android.operations.RemoteOperationResult;
 import com.owncloud.android.operations.SynchronizeFileOperation;
@@ -34,7 +32,6 @@ import eu.alefzero.webdav.WebdavClient;
 
 import android.accounts.Account;
 import android.content.Context;
-import android.content.Intent;
 import android.os.FileObserver;
 import android.util.Log;
 
@@ -97,44 +94,26 @@ public class OwnCloudFileObserver extends FileObserver {
             Log.wtf(TAG, "Incorrect event " + event + " sent for file " + mPath + ((path != null) ? File.separator + path : "") +
                          " with registered for " + mMask + " and original path " +
                          mPath);
+            /* Unexpected event that will be ignored; no reason to propagate it 
             for (FileObserverStatusListener l : mListeners)
                 l.OnObservedFileStatusUpdate(mPath, getRemotePath(), mOCAccount, Status.INCORRECT_MASK);
+            */
             return;
         }
         WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mOCAccount, mContext);
-        SynchronizeFileOperation sfo = new SynchronizeFileOperation(mFile.getRemotePath(), mStorage, mOCAccount);
+        SynchronizeFileOperation sfo = new SynchronizeFileOperation(getRemotePath(), mStorage, mOCAccount, true, false, mContext);
         RemoteOperationResult result = sfo.execute(wc);
-        
-        if (result.getExtraData() == Boolean.TRUE) {
-            // inform user about conflict and let him decide what to do
-            for (FileObserverStatusListener l : mListeners)
-                l.OnObservedFileStatusUpdate(mPath, getRemotePath(), mOCAccount, Status.CONFLICT);
-            return;
+        for (FileObserverStatusListener l : mListeners) {
+            l.onObservedFileStatusUpdate(mPath, getRemotePath(), mOCAccount, result);
         }
-
-        for (FileObserverStatusListener l : mListeners)
-            l.OnObservedFileStatusUpdate(mPath, getRemotePath(), mOCAccount, Status.SENDING_TO_UPLOADER);
         
-        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);
     }
     
     public interface FileObserverStatusListener {
-        public enum Status {
-            SENDING_TO_UPLOADER,
-            CONFLICT,
-            INCORRECT_MASK
-        }
-        
-        public void OnObservedFileStatusUpdate(String localPath,
+        public void onObservedFileStatusUpdate(String localPath,
                                                String remotePath,
                                                Account account,
-                                               FileObserverStatusListener.Status status);
+                                               RemoteOperationResult result);
     }
 
     public OCFile getOCFile() {

+ 4 - 1
src/com/owncloud/android/files/services/FileDownloader.java

@@ -59,6 +59,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     public static final String EXTRA_ACCOUNT = "ACCOUNT";
     public static final String EXTRA_FILE = "FILE";
     
+    public static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
     public static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
     public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";    
     public static final String EXTRA_FILE_PATH = "FILE_PATH";
@@ -282,7 +283,9 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
      */
     private void saveDownloadedFile() {
         OCFile file = mCurrentDownload.getFile();
-        file.setLastSyncDate(System.currentTimeMillis());
+        long syncDate = System.currentTimeMillis();
+        file.setLastSyncDateForProperties(syncDate);
+        file.setLastSyncDateForData(syncDate);
         file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp());
         // file.setEtag(mCurrentDownload.getEtag());    // TODO Etag, where available
         file.setMimetype(mCurrentDownload.getMimeType());

+ 9 - 11
src/com/owncloud/android/files/services/FileObserverService.java

@@ -27,6 +27,8 @@ import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 import com.owncloud.android.files.OwnCloudFileObserver;
 import com.owncloud.android.files.OwnCloudFileObserver.FileObserverStatusListener;
+import com.owncloud.android.operations.RemoteOperationResult;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.ui.activity.ConflictsResolveActivity;
 import com.owncloud.android.utils.FileStorageUtils;
 
@@ -302,10 +304,9 @@ public class FileObserverService extends Service implements FileObserverStatusLi
     }
 
     @Override
-    public void OnObservedFileStatusUpdate(String localPath, String remotePath, Account account, Status status) {
-        switch (status) {
-            case CONFLICT:
-            {
+    public void onObservedFileStatusUpdate(String localPath, String remotePath, Account account, RemoteOperationResult result) {
+        if (!result.isSuccess()) {
+            if (result.getCode() == ResultCode.SYNC_CONFLICT) {
                 // ISSUE 5: if the user is not running the app (this is a service!), this can be very intrusive; a notification should be preferred
                 Intent i = new Intent(getApplicationContext(), ConflictsResolveActivity.class);
                 i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -313,14 +314,11 @@ public class FileObserverService extends Service implements FileObserverStatusLi
                 i.putExtra("localpath", localPath);
                 i.putExtra("account", account);
                 startActivity(i);
-                break;
+                
+            } else {
+                // TODO send notification to the notification bar?
             }
-            case SENDING_TO_UPLOADER:
-            case INCORRECT_MASK:
-                break;
-            default:
-                Log.wtf(TAG, "Unhandled status " + status);
-        }
+        } // else, nothing else to do; now it's duty of FileUploader service 
     }
 
     private class DownloadCompletedReceiver extends BroadcastReceiver {

+ 24 - 10
src/com/owncloud/android/files/services/FileUploader.java

@@ -74,14 +74,16 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
     public static final String EXTRA_UPLOAD_RESULT = "RESULT";
     public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
     public static final String EXTRA_FILE_PATH = "FILE_PATH";
+    public static final String ACCOUNT_NAME = "ACCOUNT_NAME";    
     
     public static final String KEY_LOCAL_FILE = "LOCAL_FILE";
     public static final String KEY_REMOTE_FILE = "REMOTE_FILE";
+    public static final String KEY_MIME_TYPE = "MIME_TYPE";
+
     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";
 
     public static final int UPLOAD_SINGLE_FILE = 0;
@@ -211,7 +213,14 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         try {
             for (int i=0; i < localPaths.length; i++) {
                 uploadKey = buildRemoteName(account, remotePaths[i]);
-                file = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], ((mimeTypes!=null)?mimeTypes[i]:(String)null), isInstant, forceOverwrite, storageManager);
+                file = storageManager.getFileByLocalPath(remotePaths[i]);
+                if (file != null) {
+                    Log.d(TAG, "Upload of file already in server: " + remotePaths[i]);
+                    // TODO - review handling of input OCFiles in FileDownloader and FileUploader ; some times retrieving them from database can be necessary, some times not; we should make something consistent
+                } else {
+                    Log.d(TAG, "Upload of new file: " + remotePaths[i]);
+                    file = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], ((mimeTypes!=null)?mimeTypes[i]:(String)null), isInstant, storageManager);
+                }
                 if (chunked) {
                     newUpload = new ChunkedUploadFileOperation(account, file, isInstant, forceOverwrite);
                 } else {
@@ -427,7 +436,11 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
                 propfind.releaseConnection();
         }
 
-        if (!result.isSuccess()) {
+        long syncDate = System.currentTimeMillis();
+        if (result.isSuccess()) {
+            file.setLastSyncDateForProperties(syncDate);
+            
+        } else {
             // file was successfully uploaded, but the new time stamp and Etag in the server could not be read; 
             // just keeping old values :(
             if (!mCurrentUpload.getRemotePath().equals(file.getRemotePath())) {
@@ -436,13 +449,14 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
                 newFile.setCreationTimestamp(file.getCreationTimestamp());
                 newFile.setFileLength(file.getFileLength());
                 newFile.setMimetype(file.getMimetype());
-                newFile.setModificationTimestamp(file.getModificationTimestamp());  // this is specially BAD
+                newFile.setModificationTimestamp(file.getModificationTimestamp());
+                newFile.setLastSyncDateForProperties(file.getLastSyncDateForProperties());
+                newFile.setKeepInSync(file.keepInSync());
                 // newFile.setEtag(file.getEtag()) // TODO and this is still worse 
                 file = newFile;
             }
         }
-        
-        file.setLastSyncDate(System.currentTimeMillis());
+        file.setLastSyncDateForData(syncDate);
         mStorageManager.saveFile(file);
     }
 
@@ -472,11 +486,11 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
     }
 
     
-    private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, boolean isInstant, boolean forceOverwrite, FileDataStorageManager storageManager) {
+    private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, boolean isInstant, FileDataStorageManager storageManager) {
         OCFile newFile = new OCFile(remotePath);
         newFile.setStoragePath(localPath);
-        newFile.setLastSyncDate(0);
-        newFile.setKeepInSync(forceOverwrite);
+        newFile.setLastSyncDateForProperties(0);
+        newFile.setLastSyncDateForData(0);
         
         // size
         if (localPath != null && localPath.length() > 0) {

+ 4 - 3
src/com/owncloud/android/operations/RemoteOperationResult.java

@@ -44,8 +44,8 @@ import com.owncloud.android.network.CertificateCombinedException;
  */
 public class RemoteOperationResult implements Serializable {
     
-    /** Generated - to refresh every time the class changes */
-    private static final long serialVersionUID = -7805531062432602444L;
+    /** Generated - should be refreshed every time the class changes!! */
+    private static final long serialVersionUID = 5336333154035462033L;
 
     
     public enum ResultCode { 
@@ -69,7 +69,8 @@ public class RemoteOperationResult implements Serializable {
         CANCELLED, 
         INVALID_LOCAL_FILE_NAME, 
         INVALID_OVERWRITE,
-        CONFLICT
+        CONFLICT, 
+        SYNC_CONFLICT
     }
 
     private boolean mSuccess = false;

+ 2 - 1
src/com/owncloud/android/operations/RenameFileOperation.java

@@ -191,7 +191,8 @@ public class RenameFileOperation extends RemoteOperation {
         file.setFileId(mFile.getFileId());
         file.setFileLength(mFile.getFileLength());
         file.setKeepInSync(mFile.keepInSync());
-        file.setLastSyncDate(mFile.getLastSyncDate());
+        file.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
+        file.setLastSyncDateForData(mFile.getLastSyncDateForData());
         file.setMimetype(mFile.getMimetype());
         file.setModificationTimestamp(mFile.getModificationTimestamp());
         file.setParentId(mFile.getParentId());

+ 129 - 24
src/com/owncloud/android/operations/SynchronizeFileOperation.java

@@ -23,10 +23,15 @@ import org.apache.jackrabbit.webdav.MultiStatus;
 import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
 
 import android.accounts.Account;
+import android.content.Context;
+import android.content.Intent;
 import android.util.Log;
 
 import com.owncloud.android.datamodel.DataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
 
 import eu.alefzero.webdav.WebdavClient;
 import eu.alefzero.webdav.WebdavEntry;
@@ -35,49 +40,111 @@ import eu.alefzero.webdav.WebdavUtils;
 public class SynchronizeFileOperation extends RemoteOperation {
 
     private String TAG = SynchronizeFileOperation.class.getSimpleName();
-    
     private String mRemotePath;
-    
     private DataStorageManager mStorageManager;
-    
     private Account mAccount;
+    private boolean mSyncFileContents;
+    private boolean mLocalChangeAlreadyKnown;
+    private Context mContext;
+    
+    private boolean mTransferWasRequested = false;
     
     public SynchronizeFileOperation(
             String remotePath, 
             DataStorageManager dataStorageManager, 
-            Account account) {
+            Account account, 
+            boolean syncFileContents,
+            boolean localChangeAlreadyKnown,
+            Context context) {
+        
         mRemotePath = remotePath;
         mStorageManager = dataStorageManager;
         mAccount = account;
+        mSyncFileContents = syncFileContents;
+        mLocalChangeAlreadyKnown = localChangeAlreadyKnown;
+        mContext = context;
     }
 
+    
     @Override
     protected RemoteOperationResult run(WebdavClient client) {
+        
         PropFindMethod propfind = null;
         RemoteOperationResult result = null;
+        mTransferWasRequested = false;
         try {
-          propfind = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath));
-          int status = client.executeMethod(propfind);
-          boolean isMultiStatus = status == HttpStatus.SC_MULTI_STATUS;
-          Boolean isConflict = Boolean.FALSE;
-          if (isMultiStatus) {
-              MultiStatus resp = propfind.getResponseBodyAsMultiStatus();
-              WebdavEntry we = new WebdavEntry(resp.getResponses()[0],
+            OCFile localFile = mStorageManager.getFileByPath(mRemotePath);
+            
+            if (!localFile.isDown()) {
+                /// easy decision
+                requestForDownload(localFile);
+                result = new RemoteOperationResult(ResultCode.OK);
+                
+            } else {
+                /// local copy in the device -> need to think a bit more before do nothing
+                
+                propfind = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath));
+                int status = client.executeMethod(propfind);
+                boolean isMultiStatus = status == HttpStatus.SC_MULTI_STATUS;
+                if (isMultiStatus) {
+                    MultiStatus resp = propfind.getResponseBodyAsMultiStatus();
+                    WebdavEntry we = new WebdavEntry(resp.getResponses()[0],
                                                client.getBaseUri().getPath());
-              OCFile file = fillOCFile(we);
-              OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath());
-              if (oldFile.getFileLength() != file.getFileLength() ||
-                  oldFile.getModificationTimestamp() != file.getModificationTimestamp()) {
-                  isConflict = Boolean.TRUE;
-              }
+                    OCFile serverFile = fillOCFile(we);
+              
+                    /// check changes in server and local file
+                    boolean serverChanged = false;
+                    if (serverFile.getEtag() != null) {
+                        serverChanged = (!serverFile.getEtag().equals(localFile.getEtag()));
+                    } else {
+                        // server without etags
+                        serverChanged = (serverFile.getModificationTimestamp() > localFile.getModificationTimestamp());
+                    }
+                    boolean localChanged = (mLocalChangeAlreadyKnown || localFile.getLocalModificationTimestamp() > localFile.getLastSyncDateForData());
+              
+                    /// decide action to perform depending upon changes
+                    if (localChanged && serverChanged) {
+                        // conflict
+                        result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
+                  
+                    } else if (localChanged) {
+                        if (mSyncFileContents) {
+                            requestForUpload(localFile);
+                            // the local update of file properties will be done by the FileUploader service when the upload finishes
+                        } else {
+                            // NOTHING TO DO HERE: updating the properties of the file in the server without uploading the contents would be stupid; 
+                            // So, an instance of SynchronizeFileOperation created with syncFileContents == false is completely useless when we suspect
+                            // that an upload is necessary (for instance, in FileObserverService).
+                        }
+                        result = new RemoteOperationResult(ResultCode.OK);
+                  
+                    } else if (serverChanged) {
+                        if (mSyncFileContents) {
+                            requestForDownload(serverFile);
+                            // the update of local data will be done later by the FileUploader service when the upload finishes
+                        } else {
+                            // TODO CHECK: is this really useful in some point in the code?
+                            serverFile.setKeepInSync(localFile.keepInSync());
+                            serverFile.setParentId(localFile.getParentId());
+                            mStorageManager.saveFile(serverFile);
+                            
+                        }
+                        result = new RemoteOperationResult(ResultCode.OK);
               
-          } else {
-              client.exhaustResponse(propfind.getResponseBodyAsStream());
-          }
+                    } else {
+                        // nothing changed, nothing to do
+                        result = new RemoteOperationResult(ResultCode.OK);
+                    }
+              
+                } else {
+                    client.exhaustResponse(propfind.getResponseBodyAsStream());
+                    result = new RemoteOperationResult(false, status);
+                }
+          
+            }
+            
+            Log.i(TAG, "Synchronizing " + mAccount.name + ", file " + mRemotePath + ": " + result.getLogMessage());
           
-          result = new RemoteOperationResult(isMultiStatus, status);
-          result.setExtraData(isConflict);
-          Log.i(TAG, "Synchronizing " + mAccount.name + ", file " + mRemotePath + ": " + result.getLogMessage());
         } catch (Exception e) {
             result = new RemoteOperationResult(e);
             Log.e(TAG, "Synchronizing " + mAccount.name + ", file " + mRemotePath + ": " + result.getLogMessage(), result.getException());
@@ -88,7 +155,39 @@ public class SynchronizeFileOperation extends RemoteOperation {
         }
         return result;
     }
+
     
+    /**
+     * Requests for an upload to the FileUploader service
+     * 
+     * @param localFile     OCFile object representing the file to upload
+     */
+    private void requestForUpload(OCFile localFile) {
+        Intent i = new Intent(mContext, FileUploader.class);
+        i.putExtra(FileUploader.KEY_ACCOUNT, mAccount);
+        i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath);
+        i.putExtra(FileUploader.KEY_LOCAL_FILE, localFile.getStoragePath());
+        i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
+        i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
+        mContext.startService(i);
+        mTransferWasRequested = true;
+    }
+
+
+    /**
+     * Requests for a download to the FileDownloader service
+     * 
+     * @param file     OCFile object representing the file to download
+     */
+    private void requestForDownload(OCFile file) {
+        Intent i = new Intent(mContext, FileDownloader.class);
+        i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
+        i.putExtra(FileDownloader.EXTRA_FILE, file);
+        mContext.startService(i);
+        mTransferWasRequested = true;
+    }
+
+
     /**
      * Creates and populates a new {@link OCFile} object with the data read from the server.
      * 
@@ -101,8 +200,14 @@ public class SynchronizeFileOperation extends RemoteOperation {
         file.setFileLength(we.contentLength());
         file.setMimetype(we.contentType());
         file.setModificationTimestamp(we.modifiedTimesamp());
-        file.setLastSyncDate(System.currentTimeMillis());
+        file.setLastSyncDateForProperties(System.currentTimeMillis());
+        file.setLastSyncDateForData(0);
         return file;
     }
 
+
+    public boolean transferWasRequested() {
+        return mTransferWasRequested;
+    }
+
 }

+ 2 - 2
src/com/owncloud/android/operations/SynchronizeFolderOperation.java

@@ -151,7 +151,7 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                 String currentSavePath = FileStorageUtils.getSavePath(mAccount.name);
                 for (int i=0; i < mChildren.size(); ) {
                     file = mChildren.get(i);
-                    if (file.getLastSyncDate() != mCurrentSyncTime) {
+                    if (file.getLastSyncDateForProperties() != mCurrentSyncTime) {
                         Log.d(TAG, "removing file: " + file);
                         mStorageManager.removeFile(file, (file.isDown() && file.getStoragePath().startsWith(currentSavePath)));
                         mChildren.remove(i);
@@ -199,7 +199,7 @@ public class SynchronizeFolderOperation extends RemoteOperation {
         file.setFileLength(we.contentLength());
         file.setMimetype(we.contentType());
         file.setModificationTimestamp(we.modifiedTimesamp());
-        file.setLastSyncDate(mCurrentSyncTime);
+        file.setLastSyncDateForProperties(mCurrentSyncTime);
         return file;
     }
     

+ 18 - 3
src/com/owncloud/android/providers/FileContentProvider.java

@@ -70,6 +70,8 @@ public class FileContentProvider extends ContentProvider {
                 ProviderTableMeta.FILE_STORAGE_PATH);
         mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE,
                 ProviderTableMeta.FILE_LAST_SYNC_DATE);
+        mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA,
+                ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA);
         mProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC,
                 ProviderTableMeta.FILE_KEEP_IN_SYNC);
         mProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER,
@@ -221,18 +223,31 @@ public class FileContentProvider extends ContentProvider {
                     + ProviderTableMeta.FILE_STORAGE_PATH + " TEXT, "
                     + ProviderTableMeta.FILE_ACCOUNT_OWNER + " TEXT, "
                     + ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, "
-                    + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER );");
+                    + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, "
+                    + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER );"
+                    );
         }
 
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             Log.i("SQL", "Entering in onUpgrade");
+            boolean upgraded = false; 
             if (oldVersion == 1 && newVersion >= 2) {
-                Log.i("SQL", "Entering in the ADD in onUpgrade");
+                Log.i("SQL", "Entering in the #1 ADD in onUpgrade");
                 db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +
                            " ADD COLUMN " + ProviderTableMeta.FILE_KEEP_IN_SYNC  + " INTEGER " +
                            " DEFAULT 0");
-            } else Log.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion);
+                upgraded = true;
+            }
+            if (oldVersion < 3 && newVersion >= 3) {
+                Log.i("SQL", "Entering in the #2 ADD in onUpgrade");
+                db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +
+                           " ADD COLUMN " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA  + " INTEGER " +
+                           " DEFAULT 0");
+                upgraded = true;
+            }
+            if (!upgraded)
+                Log.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion);
         }
 
     }

+ 48 - 13
src/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -82,6 +82,8 @@ import com.owncloud.android.operations.RemoteOperationResult;
 import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
+import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.ui.activity.ConflictsResolveActivity;
 import com.owncloud.android.ui.activity.FileDetailActivity;
 import com.owncloud.android.ui.activity.FileDisplayActivity;
 import com.owncloud.android.ui.activity.TransferServiceGetter;
@@ -119,7 +121,7 @@ public class FileDetailFragment extends SherlockFragment implements
     private Handler mHandler;
     private RemoteOperation mLastRemoteOperation;
 
-    private static final String TAG = "FileDetailFragment";
+    private static final String TAG = FileDetailFragment.class.getSimpleName();
     public static final String FTAG = "FileDetails"; 
     public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT";
 
@@ -255,6 +257,7 @@ public class FileDetailFragment extends SherlockFragment implements
     
     @Override
     public void onClick(View v) {
+        FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());
         switch (v.getId()) {
             case R.id.fdDownloadBtn: {
                 //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath())) {
@@ -289,26 +292,21 @@ public class FileDetailFragment extends SherlockFragment implements
                     }
                     
                 } else {
-                    // ISSUE 6: this button should be promoted to 'synchronize' if the file is DOWN, not just redownload
-                    Intent i = new Intent(getActivity(), FileDownloader.class);
-                    i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
-                    i.putExtra(FileDownloader.EXTRA_FILE, mFile);
-                    /*i.putExtra(FileDownloader.EXTRA_REMOTE_PATH, mFile.getRemotePath());
-                    i.putExtra(FileDownloader.EXTRA_FILE_PATH, mFile.getRemotePath());
-                    i.putExtra(FileDownloader.EXTRA_FILE_SIZE, mFile.getFileLength());*/
+                    mLastRemoteOperation = new SynchronizeFileOperation(mFile.getRemotePath(), fdsm, mAccount, true, false, getActivity());
+                    WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
+                    mLastRemoteOperation.execute(wc, this, mHandler);
                 
                     // update ui 
-                    setButtonsForTransferring();
-                
-                    getActivity().startService(i);
-                    mContainerActivity.onFileStateChanged();    // this is not working; it is performed before the fileDownloadService registers it as 'in progress'
+                    boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+                    getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+                    setButtonsForTransferring(); // disable button immediately, although the synchronization does not result in a file transference
+                    
                 }
                 break;
             }
             case R.id.fdKeepInSync: {
                 CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync);
                 mFile.setKeepInSync(cb.isChecked());
-                FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());
                 fdsm.saveFile(mFile);
                 
                 /* NOT HERE
@@ -923,6 +921,9 @@ public class FileDetailFragment extends SherlockFragment implements
                 
             } else if (operation instanceof RenameFileOperation) {
                 onRenameFileOperationFinish((RenameFileOperation)operation, result);
+                
+            } else if (operation instanceof SynchronizeFileOperation) {
+                onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result);
             }
         }
     }
@@ -977,5 +978,39 @@ public class FileDetailFragment extends SherlockFragment implements
         }
     }
     
+    private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) {
+        boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+        getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+
+        if (!result.isSuccess()) {
+            if (result.getCode() == ResultCode.SYNC_CONFLICT) {
+                Intent i = new Intent(getActivity(), ConflictsResolveActivity.class);
+                //i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
+                i.putExtra("remotepath", mFile.getRemotePath());
+                i.putExtra("localpath", mFile.getStoragePath());
+                i.putExtra("account", mAccount);
+                startActivity(i);
+                
+            } else {
+                Toast msg = Toast.makeText(getActivity(), R.string.sync_file_fail_msg, Toast.LENGTH_LONG); 
+                msg.show();
+            }
+            
+        } else {
+            if (operation.transferWasRequested()) {
+                mContainerActivity.onFileStateChanged();    // this is not working; FileDownloader won't do NOTHING at all until this method finishes, so 
+                                                            // checking the service to see if the file is downloading results in FALSE
+            } else {
+                Toast msg = Toast.makeText(getActivity(), R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); 
+                msg.show();
+                if (mFile.isDown()) {
+                    setButtonsForDown();
+                    
+                } else {
+                    setButtonsForRemote();
+                }
+            }
+        }
+    }
 
 }