From: David A. Velasco Date: Fri, 23 Nov 2012 09:25:06 +0000 (+0100) Subject: Merge of master after sync_review was merged into it X-Git-Tag: oc-android-1.4.3~88^2~1 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/4c50eb4d2dd1fba177d743315ae52223430db8cb?hp=60d0a121b7ccffd0d1668f771ba4061ab0254b2e Merge ... master after sync_review was merged into it --- diff --git a/actionbarsherlock b/actionbarsherlock index 9598f2bb..90939dc3 160000 --- a/actionbarsherlock +++ b/actionbarsherlock @@ -1 +1 @@ -Subproject commit 9598f2bb2ceed4a834cd5586a903f270ca4c0ccc +Subproject commit 90939dc3925ffaaa0de269bbbe1b35e274968ea1 diff --git a/res/values/strings.xml b/res/values/strings.xml index bf9ab002..378bb00e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -74,8 +74,10 @@ Created: Modified: Download - Refresh + Refresh + Redownload Open + File was renamed to %1$s during upload Yes No OK @@ -102,7 +104,7 @@ Upload failed: %1$d/%2$d files were upload Downloading … %1$d%% Downloading %2$s - Download suceeded + Download succeeded %1$s was successfully download Download failed Download of %1$s could not be completed @@ -110,6 +112,10 @@ Contacts Synchronization failed Synchronization of %1$s could not be completed + Conflicts found + %1$d kept-in-sync files could not be sync\'ed + Kept-in-sync files failed + Contents of %1$d files could not be sync\'ed (%2$d conflicts) Use Secure Connection ownCloud cannot track your device. Please check your location settings @@ -185,6 +191,9 @@ "Local copy could not be renamed; try a differente new name" "Rename could not be completed" + Remote file could not be checked + File contents already synchronized + Directory could not be created Wait a moment diff --git a/src/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/com/owncloud/android/datamodel/FileDataStorageManager.java index 4ed425e1..26404045 100644 --- a/src/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -27,7 +27,7 @@ import java.util.Vector; import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; -import com.owncloud.android.files.services.FileDownloader; +import com.owncloud.android.utils.FileStorageUtils; import android.accounts.Account; import android.content.ContentProviderClient; @@ -118,16 +118,18 @@ 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())) { - OCFile oldFile = getFileByPath(file.getRemotePath()); - if (file.getStoragePath() == null && oldFile.getStoragePath() != null) - file.setStoragePath(oldFile.getStoragePath()); - if (!file.isDirectory()); - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); - file.setFileId(oldFile.getFileId()); + boolean sameRemotePath = fileExists(file.getRemotePath()); + if (sameRemotePath || + fileExists(file.getFileId()) ) { // for renamed files; no more delete and create + + if (sameRemotePath) { + OCFile oldFile = getFileByPath(file.getRemotePath()); + file.setFileId(oldFile.getFileId()); + } overriden = true; if (getContentResolver() != null) { @@ -145,29 +147,6 @@ public class FileDataStorageManager implements DataStorageManager { + e.getMessage()); } } - } else if (fileExists(file.getFileId())) { // for renamed files; no more delete and create - OCFile oldFile = getFileById(file.getFileId()); - if (file.getStoragePath() == null && oldFile.getStoragePath() != null) - file.setStoragePath(oldFile.getStoragePath()); - if (!file.isDirectory()); - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); - - overriden = true; - if (getContentResolver() != null) { - getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, - ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(file.getFileId()) }); - } else { - try { - getContentProvider().update(ProviderTableMeta.CONTENT_URI, - cv, ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(file.getFileId()) }); - } catch (RemoteException e) { - Log.e(TAG, - "Fail to insert insert file to database " - + e.getMessage()); - } - } } else { Uri result_uri = null; if (getContentResolver() != null) { @@ -220,17 +199,13 @@ 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())) { OCFile oldFile = getFileByPath(file.getRemotePath()); - if (file.getStoragePath() == null && oldFile.getStoragePath() != null) - file.setStoragePath(oldFile.getStoragePath()); - if (!file.isDirectory()); - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); file.setFileId(oldFile.getFileId()); - operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). withValues(cv). withSelection( ProviderTableMeta._ID + "=?", @@ -426,10 +401,12 @@ public class FileDataStorageManager implements DataStorageManager { file.setStoragePath(c.getString(c .getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH))); if (file.getStoragePath() == null) { - // try to find existing file and bind it with current account - File f = new File(FileDownloader.getSavePath(mAccount.name) + file.getRemotePath()); - if (f.exists()) + // try to find existing file and bind it with current account; - with the current update of SynchronizeFolderOperation, this won't be necessary anymore after a full synchronization of the account + File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); + if (f.exists()) { file.setStoragePath(f.getAbsolutePath()); + file.setLastSyncDateForData(f.lastModified()); + } } } file.setFileLength(c.getLong(c @@ -438,8 +415,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); } @@ -466,7 +445,7 @@ public class FileDataStorageManager implements DataStorageManager { new File(file.getStoragePath()).delete(); } if (file.isDirectory() && removeLocalCopy) { - File f = new File(FileDownloader.getSavePath(mAccount.name) + file.getRemotePath()); + File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); if (f.exists() && f.isDirectory() && (f.list() == null || f.list().length == 0)) { f.delete(); } @@ -534,7 +513,7 @@ public class FileDataStorageManager implements DataStorageManager { /// 2. prepare a batch of update operations to change all the descendants ArrayList operations = new ArrayList(c.getCount()); int lengthOfOldPath = dir.getRemotePath().length(); - String defaultSavePath = FileDownloader.getSavePath(mAccount.name); + String defaultSavePath = FileStorageUtils.getSavePath(mAccount.name); int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath; if (c.moveToFirst()) { do { diff --git a/src/com/owncloud/android/datamodel/OCFile.java b/src/com/owncloud/android/datamodel/OCFile.java index 21b8c375..ec4e749b 100644 --- a/src/com/owncloud/android/datamodel/OCFile.java +++ b/src/com/owncloud/android/datamodel/OCFile.java @@ -51,9 +51,12 @@ public class OCFile implements Parcelable, Comparable { 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. * @@ -86,7 +89,8 @@ public class OCFile implements Parcelable, Comparable { mMimeType = source.readString(); mNeedsUpdating = source.readInt() == 0; mKeepInSync = source.readInt() == 1; - mLastSyncDate = source.readLong(); + mLastSyncDateForProperties = source.readLong(); + mLastSyncDateForData = source.readLong(); } @Override @@ -101,7 +105,8 @@ public class OCFile implements Parcelable, Comparable { dest.writeString(mMimeType); dest.writeInt(mNeedsUpdating ? 1 : 0); dest.writeInt(mKeepInSync ? 1 : 0); - dest.writeLong(mLastSyncDate); + dest.writeLong(mLastSyncDateForProperties); + dest.writeLong(mLastSyncDateForData); } /** @@ -275,7 +280,8 @@ public class OCFile implements Parcelable, Comparable { mLength = 0; mCreationTimestamp = 0; mModifiedTimestamp = 0; - mLastSyncDate = 0; + mLastSyncDateForProperties = 0; + mLastSyncDateForData = 0; mKeepInSync = false; mNeedsUpdating = false; } @@ -343,12 +349,20 @@ public class OCFile implements Parcelable, Comparable { 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) { @@ -395,4 +409,16 @@ public class OCFile implements Parcelable, Comparable { 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; + } + } diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java index c7f86b63..faa16d7c 100644 --- a/src/com/owncloud/android/db/ProviderMeta.java +++ b/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 diff --git a/src/com/owncloud/android/files/OwnCloudFileObserver.java b/src/com/owncloud/android/files/OwnCloudFileObserver.java index ee48b699..8a03fee6 100644 --- a/src/com/owncloud/android/files/OwnCloudFileObserver.java +++ b/src/com/owncloud/android/files/OwnCloudFileObserver.java @@ -18,16 +18,15 @@ package com.owncloud.android.files; -import java.util.LinkedList; -import java.util.List; +import java.io.File; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; 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; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.ui.activity.ConflictsResolveActivity; import eu.alefzero.webdav.WebdavClient; @@ -41,99 +40,66 @@ public class OwnCloudFileObserver extends FileObserver { public static int CHANGES_ONLY = CLOSE_WRITE; - private static String TAG = "OwnCloudFileObserver"; + private static String TAG = OwnCloudFileObserver.class.getSimpleName(); + private String mPath; private int mMask; - DataStorageManager mStorage; - Account mOCAccount; - OCFile mFile; - static Context mContext; - List mListeners; - - public OwnCloudFileObserver(String path) { - this(path, ALL_EVENTS); - } + private Account mOCAccount; + //private OCFile mFile; + private Context mContext; + - public OwnCloudFileObserver(String path, int mask) { + public OwnCloudFileObserver(String path, Account account, Context context, int mask) { super(path, mask); + if (path == null) + throw new IllegalArgumentException("NULL path argument received"); + /*if (file == null) + throw new IllegalArgumentException("NULL file argument received");*/ + if (account == null) + throw new IllegalArgumentException("NULL account argument received"); + if (context == null) + throw new IllegalArgumentException("NULL context argument received"); + /*if (!path.equals(file.getStoragePath()) && !path.equals(FileStorageUtils.getDefaultSavePathFor(account.name, file))) + throw new IllegalArgumentException("File argument is not linked to the local file set in path argument"); */ mPath = path; - mMask = mask; - mListeners = new LinkedList(); - } - - public void setAccount(Account account) { + //mFile = file; mOCAccount = account; - } - - public void setStorageManager(DataStorageManager manager) { - mStorage = manager; - } - - public void setOCFile(OCFile file) { - mFile = file; - } - - public void setContext(Context context) { - mContext = context; - } - - public String getPath() { - return mPath; - } - - public String getRemotePath() { - return mFile.getRemotePath(); - } - - public void addObserverStatusListener(FileObserverStatusListener listener) { - mListeners.add(listener); + mContext = context; + mMask = mask; } @Override public void onEvent(int event, String path) { - Log.d(TAG, "Got file modified with event " + event + " and path " + path); + Log.d(TAG, "Got file modified with event " + event + " and path " + mPath + ((path != null) ? File.separator + path : "")); if ((event & mMask) == 0) { - Log.wtf(TAG, "Incorrect event " + event + " sent for file " + path + + Log.wtf(TAG, "Incorrect event " + event + " sent for file " + mPath + ((path != null) ? File.separator + path : "") + " with registered for " + mMask + " and original path " + mPath); - 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, mContext); + FileDataStorageManager storageManager = new FileDataStorageManager(mOCAccount, mContext.getContentResolver()); + OCFile file = storageManager.getFileByLocalPath(mPath); // a fresh object is needed; many things could have occurred to the file since it was registered to observe + // again, assuming that local files are linked to a remote file AT MOST, SOMETHING TO BE DONE; + SynchronizeFileOperation sfo = new SynchronizeFileOperation(file, + null, + storageManager, + mOCAccount, + true, + true, + 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, 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 + 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(mContext, ConflictsResolveActivity.class); + i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); + i.putExtra(ConflictsResolveActivity.EXTRA_FILE, file); + i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mOCAccount); + mContext.startActivity(i); } - - public void OnObservedFileStatusUpdate(String localPath, - String remotePath, - Account account, - FileObserverStatusListener.Status status); + // TODO save other errors in some point where the user can inspect them later; + // or maybe just toast them; + // or nothing, very strange fails } } diff --git a/src/com/owncloud/android/files/services/FileDownloader.java b/src/com/owncloud/android/files/services/FileDownloader.java index 0f2d6742..5e80f5d3 100644 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@ -25,8 +25,8 @@ import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; import eu.alefzero.webdav.OnDatatransferProgressListener; import com.owncloud.android.network.OwnCloudClientUtils; @@ -40,11 +40,8 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; -import android.content.ContentValues; import android.content.Intent; -import android.net.Uri; import android.os.Binder; -import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -62,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"; @@ -75,6 +73,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis private IBinder mBinder; private WebdavClient mDownloadClient = null; private Account mLastAccount = null; + private FileDataStorageManager mStorageManager; private ConcurrentMap mPendingDownloads = new ConcurrentHashMap(); private DownloadFileOperation mCurrentDownload = null; @@ -93,18 +92,6 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis private String buildRemoteName(Account account, OCFile file) { return account.name + file.getRemotePath(); } - - public static final String getSavePath(String accountName) { - File sdCard = Environment.getExternalStorageDirectory(); - return sdCard.getAbsolutePath() + "/owncloud/" + Uri.encode(accountName, "@"); - // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B - } - - public static final String getTemporalPath(String accountName) { - File sdCard = Environment.getExternalStorageDirectory(); - return sdCard.getAbsolutePath() + "/owncloud/tmp/" + Uri.encode(accountName, "@"); - // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B - } /** @@ -149,6 +136,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis mPendingDownloads.putIfAbsent(downloadKey, newDownload); newDownload.addDatatransferProgressListener(this); requestedDownloads.add(downloadKey); + sendBroadcastNewDownload(newDownload); } catch (IllegalArgumentException e) { Log.e(TAG, "Not enough information provided in intent: " + e.getMessage()); @@ -277,6 +265,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis /// prepare client object to send the request to the ownCloud server if (mDownloadClient == null || !mLastAccount.equals(mCurrentDownload.getAccount())) { mLastAccount = mCurrentDownload.getAccount(); + mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); mDownloadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); } @@ -285,16 +274,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis try { downloadResult = mCurrentDownload.execute(mDownloadClient); if (downloadResult.isSuccess()) { - ContentValues cv = new ContentValues(); - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, mCurrentDownload.getSavePath()); - getContentResolver().update( - ProviderTableMeta.CONTENT_URI, - cv, - ProviderTableMeta.FILE_NAME + "=? AND " - + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?", - new String[] { - mCurrentDownload.getSavePath().substring(mCurrentDownload.getSavePath().lastIndexOf('/') + 1), - mLastAccount.name }); + saveDownloadedFile(); } } finally { @@ -307,11 +287,28 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis /// notify result notifyDownloadResult(mCurrentDownload, downloadResult); - sendFinalBroadcast(mCurrentDownload, downloadResult); + sendBroadcastDownloadFinished(mCurrentDownload, downloadResult); } } - + + /** + * Updates the OC File after a successful download. + */ + private void saveDownloadedFile() { + OCFile file = mCurrentDownload.getFile(); + 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()); + file.setStoragePath(mCurrentDownload.getSavePath()); + file.setFileLength((new File(mCurrentDownload.getSavePath()).length())); + mStorageManager.saveFile(file); + } + + /** * Creates a status notification to show the download progress * @@ -385,20 +382,32 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis /** - * Sends a broadcast in order to the interested activities can update their view + * Sends a broadcast when a download finishes in order to the interested activities can update their view * * @param download Finished download operation * @param downloadResult Result of the download operation */ - private void sendFinalBroadcast(DownloadFileOperation download, RemoteOperationResult downloadResult) { + private void sendBroadcastDownloadFinished(DownloadFileOperation download, RemoteOperationResult downloadResult) { Intent end = new Intent(DOWNLOAD_FINISH_MESSAGE); end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess()); end.putExtra(ACCOUNT_NAME, download.getAccount().name); end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath()); - if (downloadResult.isSuccess()) { - end.putExtra(EXTRA_FILE_PATH, download.getSavePath()); - } - sendBroadcast(end); + end.putExtra(EXTRA_FILE_PATH, download.getSavePath()); + sendStickyBroadcast(end); + } + + + /** + * Sends a broadcast when a new download is added to the queue. + * + * @param download Added download operation + */ + private void sendBroadcastNewDownload(DownloadFileOperation download) { + Intent added = new Intent(DOWNLOAD_ADDED_MESSAGE); + /*added.putExtra(ACCOUNT_NAME, download.getAccount().name); + added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());*/ + added.putExtra(EXTRA_FILE_PATH, download.getSavePath()); + sendStickyBroadcast(added); } } diff --git a/src/com/owncloud/android/files/services/FileObserverService.java b/src/com/owncloud/android/files/services/FileObserverService.java index 88bf0b91..3cadc568 100644 --- a/src/com/owncloud/android/files/services/FileObserverService.java +++ b/src/com/owncloud/android/files/services/FileObserverService.java @@ -18,15 +18,16 @@ package com.owncloud.android.files.services; -import java.util.ArrayList; -import java.util.List; +import java.io.File; +import java.util.HashMap; +import java.util.Map; -import com.owncloud.android.AccountUtils; 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.OwnCloudFileObserver.FileObserverStatusListener; -import com.owncloud.android.ui.activity.ConflictsResolveActivity; +import com.owncloud.android.operations.SynchronizeFileOperation; +import com.owncloud.android.utils.FileStorageUtils; import android.accounts.Account; import android.accounts.AccountManager; @@ -40,20 +41,20 @@ import android.os.Binder; import android.os.IBinder; import android.util.Log; -public class FileObserverService extends Service implements FileObserverStatusListener { - - public final static String KEY_FILE_CMD = "KEY_FILE_CMD"; - public final static String KEY_CMD_ARG = "KEY_CMD_ARG"; +public class FileObserverService extends Service { 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; - public final static int CMD_ADD_DOWNLOADING_FILE = 4; - private static String TAG = "FileObserverService"; - private static List mObservers; - private static List mDownloadReceivers; - private static Object mReceiverListLock = new Object(); + public final static String KEY_FILE_CMD = "KEY_FILE_CMD"; + public final static String KEY_CMD_ARG_FILE = "KEY_CMD_ARG_FILE"; + public final static String KEY_CMD_ARG_ACCOUNT = "KEY_CMD_ARG_ACCOUNT"; + + private static String TAG = FileObserverService.class.getSimpleName(); + + private static Map mObserversMap; + private static DownloadCompletedReceiverBis mDownloadReceiver; private IBinder mBinder = new LocalBinder(); public class LocalBinder extends Binder { @@ -63,6 +64,29 @@ public class FileObserverService extends Service implements FileObserverStatusLi } @Override + public void onCreate() { + super.onCreate(); + mDownloadReceiver = new DownloadCompletedReceiverBis(); + IntentFilter filter = new IntentFilter(); + filter.addAction(FileDownloader.DOWNLOAD_ADDED_MESSAGE); + filter.addAction(FileDownloader.DOWNLOAD_FINISH_MESSAGE); + registerReceiver(mDownloadReceiver, filter); + + mObserversMap = new HashMap(); + //initializeObservedList(); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + unregisterReceiver(mDownloadReceiver); + mObserversMap = null; // TODO study carefully the life cycle of Services to grant the best possible observance + Log.d(TAG, "Bye, bye"); + } + + + @Override public IBinder onBind(Intent intent) { return mBinder; } @@ -86,13 +110,12 @@ public class FileObserverService extends Service implements FileObserverStatusLi initializeObservedList(); break; case CMD_ADD_OBSERVED_FILE: - addObservedFile(intent.getStringExtra(KEY_CMD_ARG)); + addObservedFile( (OCFile)intent.getParcelableExtra(KEY_CMD_ARG_FILE), + (Account)intent.getParcelableExtra(KEY_CMD_ARG_ACCOUNT)); break; case CMD_DEL_OBSERVED_FILE: - removeObservedFile(intent.getStringExtra(KEY_CMD_ARG)); - break; - case CMD_ADD_DOWNLOADING_FILE: - addDownloadingFile(intent.getStringExtra(KEY_CMD_ARG)); + removeObservedFile( (OCFile)intent.getParcelableExtra(KEY_CMD_ARG_FILE), + (Account)intent.getParcelableExtra(KEY_CMD_ARG_ACCOUNT)); break; default: Log.wtf(TAG, "Incorrect key given"); @@ -101,10 +124,13 @@ public class FileObserverService extends Service implements FileObserverStatusLi return Service.START_STICKY; } + + /** + * Read from the local database the list of files that must to be kept synchronized and + * starts file observers to monitor local changes on them + */ private void initializeObservedList() { - if (mObservers != null) return; // nothing to do here - mObservers = new ArrayList(); - mDownloadReceivers = new ArrayList(); + mObserversMap.clear(); Cursor c = getContentResolver().query( ProviderTableMeta.CONTENT_URI, null, @@ -129,148 +155,122 @@ public class FileObserverService extends Service implements FileObserverStatusLi continue; String path = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH)); + if (path == null || path.length() <= 0) + continue; OwnCloudFileObserver observer = - new OwnCloudFileObserver(path, OwnCloudFileObserver.CHANGES_ONLY); - observer.setContext(getApplicationContext()); - observer.setAccount(account); - observer.setStorageManager(storage); - observer.setOCFile(storage.getFileByPath(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH)))); - observer.addObserverStatusListener(this); - observer.startWatching(); - mObservers.add(observer); - Log.d(TAG, "Started watching file " + path); + new OwnCloudFileObserver( path, + account, + getApplicationContext(), + OwnCloudFileObserver.CHANGES_ONLY); + mObserversMap.put(path, observer); + if (new File(path).exists()) { + observer.startWatching(); + 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)); - observer.addObserverStatusListener(this); - - 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(); + /** + * Registers the local copy of a remote file to be observed for local changes, + * an automatically updated in the ownCloud server. + * + * This method does NOT perform a {@link SynchronizeFileOperation} over the file. + * + * TODO We are ignoring that, currently, a local file can be linked to different files + * in ownCloud if it's uploaded several times. That's something pending to update: we + * will avoid that the same local file is linked to different remote files. + * + * @param file Object representing a remote file which local copy must be observed. + * @param account OwnCloud account containing file. + */ + private void addObservedFile(OCFile file, Account account) { + if (file == null) { + Log.e(TAG, "Trying to add a NULL file to observer"); 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 void addDownloadingFile(String remotePath) { - OwnCloudFileObserver observer = null; - for (OwnCloudFileObserver o : mObservers) { - if (o.getRemotePath().equals(remotePath)) { - observer = o; - break; - } + String localPath = file.getStoragePath(); + if (localPath == null || localPath.length() <= 0) { // file downloading / to be download for the first time + localPath = FileStorageUtils.getDefaultSavePathFor(account.name, file); } + OwnCloudFileObserver observer = mObserversMap.get(localPath); if (observer == null) { - Log.e(TAG, "Couldn't find observer for remote file " + remotePath); - return; + /// the local file was never registered to observe before + observer = new OwnCloudFileObserver( localPath, + account, + getApplicationContext(), + OwnCloudFileObserver.CHANGES_ONLY); + mObserversMap.put(localPath, observer); + Log.d(TAG, "Observer added for path " + localPath); + + if (file.isDown()) { + observer.startWatching(); + Log.d(TAG, "Started watching " + localPath); + } // else - the observance can't be started on a file not already down; mDownloadReceiver will get noticed when the download of the file finishes } - observer.stopWatching(); - DownloadCompletedReceiver dcr = new DownloadCompletedReceiver(observer.getPath(), observer); - registerReceiver(dcr, new IntentFilter(FileDownloader.DOWNLOAD_FINISH_MESSAGE)); + } - private static void addReceiverToList(DownloadCompletedReceiver r) { - synchronized(mReceiverListLock) { - mDownloadReceivers.add(r); + /** + * Unregisters the local copy of a remote file to be observed for local changes. + * + * Starts to watch it, if the file has a local copy to watch. + * + * TODO We are ignoring that, currently, a local file can be linked to different files + * in ownCloud if it's uploaded several times. That's something pending to update: we + * will avoid that the same local file is linked to different remote files. + * + * @param file Object representing a remote file which local copy must be not observed longer. + * @param account OwnCloud account containing file. + */ + private void removeObservedFile(OCFile file, Account account) { + if (file == null) { + Log.e(TAG, "Trying to remove a NULL file"); + return; } - } - - private static void removeReceiverFromList(DownloadCompletedReceiver r) { - synchronized(mReceiverListLock) { - mDownloadReceivers.remove(r); + String localPath = file.getStoragePath(); + if (localPath == null || localPath.length() <= 0) { + localPath = FileStorageUtils.getDefaultSavePathFor(account.name, file); } - } - - @Override - public void OnObservedFileStatusUpdate(String localPath, String remotePath, Account account, Status status) { - switch (status) { - case CONFLICT: - { - Intent i = new Intent(getApplicationContext(), ConflictsResolveActivity.class); - i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - i.putExtra("remotepath", remotePath); - i.putExtra("localpath", localPath); - i.putExtra("account", account); - startActivity(i); - break; - } - case SENDING_TO_UPLOADER: - case INCORRECT_MASK: - break; - default: - Log.wtf(TAG, "Unhandled status " + status); + + OwnCloudFileObserver observer = mObserversMap.get(localPath); + if (observer != null) { + observer.stopWatching(); + mObserversMap.remove(observer); + Log.d(TAG, "Stopped watching " + localPath); } + } - private class DownloadCompletedReceiver extends BroadcastReceiver { - String mPath; - OwnCloudFileObserver mObserver; - - public DownloadCompletedReceiver(String path, OwnCloudFileObserver observer) { - mPath = path; - mObserver = observer; - addReceiverToList(this); - } + + /** + * Private receiver listening to events broadcast by the FileDownloader service. + * + * Starts and stops the observance on registered files when they are being download, + * in order to avoid to start unnecessary synchronizations. + */ + private class DownloadCompletedReceiverBis extends BroadcastReceiver { @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; + String downloadPath = intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH); + OwnCloudFileObserver observer = mObserversMap.get(downloadPath); + if (observer != null) { + if (intent.getAction().equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE) && + new File(downloadPath).exists()) { // the download could be successful, or not; in both cases, the file could be down, due to a former download or upload + observer.startWatching(); + Log.d(TAG, "Watching again " + downloadPath); + + } else if (intent.getAction().equals(FileDownloader.DOWNLOAD_ADDED_MESSAGE)) { + observer.stopWatching(); + Log.d(TAG, "Disabling observance of " + downloadPath); + } } } - @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 7956a99b..958036a7 100644 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -25,6 +25,10 @@ import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import org.apache.http.HttpStatus; +import org.apache.jackrabbit.webdav.MultiStatus; +import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; + import com.owncloud.android.authenticator.AccountAuthenticator; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; @@ -34,9 +38,12 @@ import com.owncloud.android.operations.RemoteOperationResult; import com.owncloud.android.operations.UploadFileOperation; import com.owncloud.android.ui.activity.FileDetailActivity; import com.owncloud.android.ui.fragment.FileDetailFragment; +import com.owncloud.android.utils.FileStorageUtils; import com.owncloud.android.utils.OwnCloudVersion; import eu.alefzero.webdav.OnDatatransferProgressListener; +import eu.alefzero.webdav.WebdavEntry; +import eu.alefzero.webdav.WebdavUtils; import com.owncloud.android.network.OwnCloudClientUtils; @@ -66,15 +73,19 @@ public class FileUploader extends Service implements OnDatatransferProgressListe public static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH"; public static final String EXTRA_UPLOAD_RESULT = "RESULT"; public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH"; + public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH"; public static final String EXTRA_FILE_PATH = "FILE_PATH"; + public static final String ACCOUNT_NAME = "ACCOUNT_NAME"; + public static final String KEY_FILE = "FILE"; 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; @@ -149,7 +160,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe */ @Override public int onStartCommand(Intent intent, int flags, int startId) { - if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_UPLOAD_TYPE)) { + if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_UPLOAD_TYPE) || !(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) { Log.e(TAG, "Not enough information provided in intent"); return Service.START_NOT_STICKY; } @@ -160,55 +171,76 @@ public class FileUploader extends Service implements OnDatatransferProgressListe } Account account = intent.getParcelableExtra(KEY_ACCOUNT); - String[] localPaths, remotePaths, mimeTypes; + String[] localPaths = null, remotePaths = null, mimeTypes = null; + OCFile[] files = null; if (uploadType == UPLOAD_SINGLE_FILE) { - localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) }; - remotePaths = new String[] { intent - .getStringExtra(KEY_REMOTE_FILE) }; - mimeTypes = new String[] { intent.getStringExtra(KEY_MIME_TYPE) }; + + if (intent.hasExtra(KEY_FILE)) { + files = new OCFile[] {intent.getParcelableExtra(KEY_FILE) }; + + } else { + localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) }; + remotePaths = new String[] { intent.getStringExtra(KEY_REMOTE_FILE) }; + mimeTypes = new String[] { intent.getStringExtra(KEY_MIME_TYPE) }; + } } else { // mUploadType == UPLOAD_MULTIPLE_FILES - localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE); - remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE); - mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE); + + if (intent.hasExtra(KEY_FILE)) { + files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE); // TODO will this casting work fine? + + } else { + localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE); + remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE); + mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE); + } } - if (localPaths == null) { - Log.e(TAG, "Incorrect array for local paths provided in upload intent"); - return Service.START_NOT_STICKY; + FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver()); + + boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false); + boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false); + boolean fixed = false; + if (isInstant) { + fixed = checkAndFixInstantUploadDirectory(storageManager); // MUST be done BEFORE calling obtainNewOCFileToUpload } - if (remotePaths == null) { - Log.e(TAG, "Incorrect array for remote paths provided in upload intent"); + + if (intent.hasExtra(KEY_FILE) && files == null) { + Log.e(TAG, "Incorrect array for OCFiles provided in upload intent"); return Service.START_NOT_STICKY; - } - if (localPaths.length != remotePaths.length) { - Log.e(TAG, "Different number of remote paths and local paths!"); - return Service.START_NOT_STICKY; + } else if (!intent.hasExtra(KEY_FILE)) { + if (localPaths == null) { + Log.e(TAG, "Incorrect array for local paths provided in upload intent"); + return Service.START_NOT_STICKY; + } + if (remotePaths == null) { + Log.e(TAG, "Incorrect array for remote paths provided in upload intent"); + return Service.START_NOT_STICKY; + } + if (localPaths.length != remotePaths.length) { + Log.e(TAG, "Different number of remote paths and local paths!"); + return Service.START_NOT_STICKY; + } + + files = new OCFile[localPaths.length]; + for (int i=0; i < localPaths.length; i++) { + files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], ((mimeTypes!=null)?mimeTypes[i]:(String)null), storageManager); + } } - - boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false); - boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false); - + OwnCloudVersion ocv = new OwnCloudVersion(AccountManager.get(this).getUserData(account, AccountAuthenticator.KEY_OC_VERSION)); boolean chunked = FileUploader.chunkedUploadIsSupported(ocv); AbstractList requestedUploads = new Vector(); String uploadKey = null; UploadFileOperation newUpload = null; - OCFile file = null; - FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver()); - boolean fixed = false; - if (isInstant) { - fixed = checkAndFixInstantUploadDirectory(storageManager); - } 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); + for (int i=0; i < files.length; i++) { + uploadKey = buildRemoteName(account, files[i].getRemotePath()); if (chunked) { - newUpload = new ChunkedUploadFileOperation(account, file, isInstant, forceOverwrite); + newUpload = new ChunkedUploadFileOperation(account, files[i], isInstant, forceOverwrite); } else { - newUpload = new UploadFileOperation(account, file, isInstant, forceOverwrite); + newUpload = new UploadFileOperation(account, files[i], isInstant, forceOverwrite); } if (fixed && i==0) { newUpload.setRemoteFolderToBeCreated(); @@ -372,7 +404,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe try { uploadResult = mCurrentUpload.execute(mUploadClient); if (uploadResult.isSuccess()) { - saveUploadedFile(mCurrentUpload.getFile(), mStorageManager); + saveUploadedFile(); } } finally { @@ -391,15 +423,89 @@ public class FileUploader extends Service implements OnDatatransferProgressListe } /** - * Saves a new OC File after a successful upload. + * Saves a OC File after a successful upload. + * + * A PROPFIND is necessary to keep the props in the local database synchronized with the server, + * specially the modification time and Etag (where available) * - * @param file OCFile describing the uploaded file - * @param storageManager Interface to the database where the new OCFile has to be stored. - * @param parentDirId Id of the parent OCFile. + * TODO refactor this ugly thing */ - private void saveUploadedFile(OCFile file, FileDataStorageManager storageManager) { - file.setModificationTimestamp(System.currentTimeMillis()); - storageManager.saveFile(file); + private void saveUploadedFile() { + OCFile file = mCurrentUpload.getFile(); + long syncDate = System.currentTimeMillis(); + file.setLastSyncDateForData(syncDate); + + /// new PROPFIND to keep data consistent with server in theory, should return the same we already have + PropFindMethod propfind = null; + RemoteOperationResult result = null; + try { + propfind = new PropFindMethod(mUploadClient.getBaseUri() + WebdavUtils.encodePath(mCurrentUpload.getRemotePath())); + int status = mUploadClient.executeMethod(propfind); + boolean isMultiStatus = (status == HttpStatus.SC_MULTI_STATUS); + if (isMultiStatus) { + MultiStatus resp = propfind.getResponseBodyAsMultiStatus(); + WebdavEntry we = new WebdavEntry(resp.getResponses()[0], + mUploadClient.getBaseUri().getPath()); + updateOCFile(file, we); + file.setLastSyncDateForProperties(syncDate); + + } else { + mUploadClient.exhaustResponse(propfind.getResponseBodyAsStream()); + } + + result = new RemoteOperationResult(isMultiStatus, status); + Log.i(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " + result.getLogMessage()); + + } catch (Exception e) { + result = new RemoteOperationResult(e); + Log.i(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " + result.getLogMessage(), e); + + } finally { + if (propfind != null) + propfind.releaseConnection(); + } + + + if (mCurrentUpload.wasRenamed()) { + OCFile oldFile = mCurrentUpload.getOldFile(); + if (!oldFile.fileExists()) { + // just a name coincidence + file.setStoragePath(oldFile.getStoragePath()); + + } else { + // conflict resolved with 'Keep both' by the user + File localFile = new File(oldFile.getStoragePath()); + File newLocalFile = new File(FileStorageUtils.getDefaultSavePathFor(mCurrentUpload.getAccount().name, file)); + boolean renameSuccessed = localFile.renameTo(newLocalFile); + if (renameSuccessed) { + file.setStoragePath(newLocalFile.getAbsolutePath()); + + } else { + // poor solution + Log.d(TAG, "DAMN IT: local rename failed after uploading a file with a new name already existing both in the remote account and the local database (should be due to a conflict solved with 'keep both'"); + file.setStoragePath(null); + // not so fine: + // - local file will be kept there as 'trash' until is download (and overwritten) again from the server; + // - user will see as 'not down' a file that was just upload + // BUT: + // - no loss of data happened + // - when the user downloads again the renamed and original file from the server, local file names and contents will be correctly synchronized with names and contents in server + } + oldFile.setStoragePath(null); + mStorageManager.saveFile(oldFile); + } + } + + mStorageManager.saveFile(file); + } + + + private void updateOCFile(OCFile file, WebdavEntry we) { + file.setCreationTimestamp(we.createTimestamp()); + file.setFileLength(we.contentLength()); + file.setMimetype(we.contentType()); + file.setModificationTimestamp(we.modifiedTimesamp()); + // file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available } @@ -417,16 +523,17 @@ 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, 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) { File localFile = new File(localPath); newFile.setFileLength(localFile.length()); + newFile.setLastSyncDateForData(localFile.lastModified()); } // don't worry about not assigning size, the problems with localPath are checked when the UploadFileOperation instance is created // MIME type @@ -581,10 +688,13 @@ public class FileUploader extends Service implements OnDatatransferProgressListe private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) { Intent end = new Intent(UPLOAD_FINISH_MESSAGE); end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote path, after possible automatic renaming + if (upload.wasRenamed()) { + end.putExtra(EXTRA_OLD_REMOTE_PATH, upload.getOldFile().getRemotePath()); + } end.putExtra(EXTRA_FILE_PATH, upload.getStoragePath()); end.putExtra(ACCOUNT_NAME, upload.getAccount().name); end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess()); - sendBroadcast(end); + sendStickyBroadcast(end); } diff --git a/src/com/owncloud/android/operations/DownloadFileOperation.java b/src/com/owncloud/android/operations/DownloadFileOperation.java index 03c99041..59343d15 100644 --- a/src/com/owncloud/android/operations/DownloadFileOperation.java +++ b/src/com/owncloud/android/operations/DownloadFileOperation.java @@ -22,19 +22,21 @@ import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.http.HttpStatus; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.files.services.FileDownloader; import com.owncloud.android.operations.RemoteOperation; import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.utils.FileStorageUtils; import eu.alefzero.webdav.OnDatatransferProgressListener; import eu.alefzero.webdav.WebdavClient; @@ -56,6 +58,7 @@ public class DownloadFileOperation extends RemoteOperation { private OCFile mFile; private Set mDataTransferListeners = new HashSet(); private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); + private long mModificationTimestamp = 0; public DownloadFileOperation(Account account, OCFile file) { @@ -78,11 +81,15 @@ public class DownloadFileOperation extends RemoteOperation { } public String getSavePath() { - return FileDownloader.getSavePath(mAccount.name) + mFile.getRemotePath(); + String path = mFile.getStoragePath(); // re-downloads should be done over the original file + if (path != null && path.length() > 0) { + return path; + } + return FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); } public String getTmpPath() { - return FileDownloader.getTemporalPath(mAccount.name) + mFile.getRemotePath(); + return FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath(); } public String getRemotePath() { @@ -90,7 +97,7 @@ public class DownloadFileOperation extends RemoteOperation { } public String getMimeType() { - String mimeType = mFile.getMimetype(); // TODO fix the mime types in OCFiles FOREVER + String mimeType = mFile.getMimetype(); if (mimeType == null || mimeType.length() <= 0) { try { mimeType = MimeTypeMap.getSingleton() @@ -110,6 +117,10 @@ public class DownloadFileOperation extends RemoteOperation { return mFile.getFileLength(); } + public long getModificationTimestamp() { + return (mModificationTimestamp > 0) ? mModificationTimestamp : mFile.getModificationTimestamp(); + } + public void addDatatransferProgressListener (OnDatatransferProgressListener listener) { mDataTransferListeners.add(listener); @@ -185,6 +196,11 @@ public class DownloadFileOperation extends RemoteOperation { } } savedFile = true; + Header modificationTime = get.getResponseHeader("Last-Modified"); + if (modificationTime != null) { + Date d = WebdavUtils.parseResponseDate((String) modificationTime.getValue()); + mModificationTimestamp = (d != null) ? d.getTime() : 0; + } } else { client.exhaustResponse(get.getResponseBodyAsStream()); diff --git a/src/com/owncloud/android/operations/RemoteOperationResult.java b/src/com/owncloud/android/operations/RemoteOperationResult.java index d8fbe460..557c9cd2 100644 --- a/src/com/owncloud/android/operations/RemoteOperationResult.java +++ b/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; diff --git a/src/com/owncloud/android/operations/RenameFileOperation.java b/src/com/owncloud/android/operations/RenameFileOperation.java index a2d3b300..f4ec7b0a 100644 --- a/src/com/owncloud/android/operations/RenameFileOperation.java +++ b/src/com/owncloud/android/operations/RenameFileOperation.java @@ -29,8 +29,8 @@ 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.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.utils.FileStorageUtils; import eu.alefzero.webdav.WebdavClient; import eu.alefzero.webdav.WebdavUtils; @@ -154,10 +154,10 @@ public class RenameFileOperation extends RemoteOperation { private void saveLocalDirectory() { mStorageManager.moveDirectory(mFile, mNewRemotePath); - String localPath = FileDownloader.getSavePath(mAccount.name) + mFile.getRemotePath(); + String localPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); File localDir = new File(localPath); if (localDir.exists()) { - localDir.renameTo(new File(FileDownloader.getSavePath(mAccount.name) + mNewRemotePath)); + localDir.renameTo(new File(FileStorageUtils.getSavePath(mAccount.name) + mNewRemotePath)); // TODO - if renameTo fails, children files that are already down will result unlinked } } @@ -201,7 +201,7 @@ public class RenameFileOperation extends RemoteOperation { return false; } // create a test file - String tmpFolder = FileDownloader.getTemporalPath(""); + String tmpFolder = FileStorageUtils.getTemporalPath(""); File testFile = new File(tmpFolder + mNewName); try { testFile.createNewFile(); // return value is ignored; it could be 'false' because the file already existed, that doesn't invalidate the name @@ -218,8 +218,7 @@ public class RenameFileOperation extends RemoteOperation { } - - // move operation - TODO: find out why org.apache.jackrabbit.webdav.client.methods.MoveMethod is not used instead ¿? + // move operation private class LocalMoveMethod extends DavMethodBase { public LocalMoveMethod(String uri, String dest) { diff --git a/src/com/owncloud/android/operations/SynchronizeFileOperation.java b/src/com/owncloud/android/operations/SynchronizeFileOperation.java index 62f61931..f98de115 100644 --- a/src/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java @@ -24,10 +24,14 @@ 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; @@ -36,53 +40,128 @@ import eu.alefzero.webdav.WebdavUtils; public class SynchronizeFileOperation extends RemoteOperation { private String TAG = SynchronizeFileOperation.class.getSimpleName(); + private static final int SYNC_READ_TIMEOUT = 10000; + private static final int SYNC_CONNECTION_TIMEOUT = 5000; - private String mRemotePath; - + private OCFile mLocalFile; + private OCFile mServerFile; 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, + OCFile localFile, + OCFile serverFile, // make this null to let the operation checks the server; added to reuse info from SynchronizeFolderOperation + DataStorageManager storageManager, Account account, - Context context ) { - mRemotePath = remotePath; - mStorageManager = dataStorageManager; + boolean syncFileContents, + boolean localChangeAlreadyKnown, + Context context) { + + mLocalFile = localFile; + mServerFile = serverFile; + mStorageManager = storageManager; 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], + if (!mLocalFile.isDown()) { + /// easy decision + requestForDownload(mLocalFile); + result = new RemoteOperationResult(ResultCode.OK); + + } else { + /// local copy in the device -> need to think a bit more before do anything + + if (mServerFile == null) { + /// take the duty of check the server for the current state of the file there + propfind = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mLocalFile.getRemotePath())); + int status = client.executeMethod(propfind, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT); + 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; - } + mServerFile = fillOCFile(we); + mServerFile.setLastSyncDateForProperties(System.currentTimeMillis()); + + } else { + client.exhaustResponse(propfind.getResponseBodyAsStream()); + result = new RemoteOperationResult(false, status); + } + } + + if (result == null) { // true if the server was not checked, or nothing was wrong with the remote request + + /// check changes in server and local file + boolean serverChanged = false; + if (mServerFile.getEtag() != null) { + serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag())); // TODO could this be dangerous when the user upgrades the server from non-tagged to tagged? + } else { + // server without etags + serverChanged = (mServerFile.getModificationTimestamp() > mLocalFile.getModificationTimestamp()); + } + boolean localChanged = (mLocalChangeAlreadyKnown || mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()); + // TODO this will be always true after the app is upgraded to database version 3; will result in unnecessary uploads + + /// decide action to perform depending upon changes + if (localChanged && serverChanged) { + result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); + + } else if (localChanged) { + if (mSyncFileContents) { + requestForUpload(mLocalFile); + // 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(mLocalFile); // local, not server; we won't to keep the value of keepInSync! + // 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? + mServerFile.setKeepInSync(mLocalFile.keepInSync()); + mServerFile.setLastSyncDateForData(mLocalFile.getLastSyncDateForData()); + mServerFile.setStoragePath(mLocalFile.getStoragePath()); + mServerFile.setParentId(mLocalFile.getParentId()); + mStorageManager.saveFile(mServerFile); + + } + result = new RemoteOperationResult(ResultCode.OK); - } else { - client.exhaustResponse(propfind.getResponseBodyAsStream()); - } + } else { + // nothing changed, nothing to do + result = new RemoteOperationResult(ResultCode.OK); + } + + } + + } + + Log.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + 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()); + Log.e(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage(), result.getException()); } finally { if (propfind != null) @@ -90,8 +169,41 @@ public class SynchronizeFileOperation extends RemoteOperation { } return result; } + /** + * Requests for an upload to the FileUploader service + * + * @param file OCFile object representing the file to upload + */ + private void requestForUpload(OCFile file) { + Intent i = new Intent(mContext, FileUploader.class); + i.putExtra(FileUploader.KEY_ACCOUNT, mAccount); + i.putExtra(FileUploader.KEY_FILE, file); + /*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath); // doing this we would lose the value of keepInSync in the road, and maybe it's not updated in the database when the FileUploader service gets it! + 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. * * @param we WebDAV entry read from the server for a WebDAV resource (remote file or folder). @@ -103,8 +215,12 @@ public class SynchronizeFileOperation extends RemoteOperation { file.setFileLength(we.contentLength()); file.setMimetype(we.contentType()); file.setModificationTimestamp(we.modifiedTimesamp()); - file.setLastSyncDate(System.currentTimeMillis()); return file; } + + public boolean transferWasRequested() { + return mTransferWasRequested; + } + } diff --git a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java index 823a36eb..bfff6800 100644 --- a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java @@ -18,6 +18,7 @@ package com.owncloud.android.operations; +import java.io.File; import java.util.List; import java.util.Vector; @@ -27,13 +28,12 @@ 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.FileObserverService; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.utils.FileStorageUtils; import eu.alefzero.webdav.WebdavClient; import eu.alefzero.webdav.WebdavEntry; @@ -69,6 +69,10 @@ public class SynchronizeFolderOperation extends RemoteOperation { /** Files and folders contained in the synchronized folder */ private List mChildren; + + private int mConflictsFound; + + private int mFailsInFavouritesFound; public SynchronizeFolderOperation( String remotePath, @@ -86,6 +90,14 @@ public class SynchronizeFolderOperation extends RemoteOperation { } + public int getConflictsFound() { + return mConflictsFound; + } + + public int getFailsInFavouritesFound() { + return mFailsInFavouritesFound; + } + /** * Returns the list of files and folders contained in the synchronized folder, if called after synchronization is complete. * @@ -99,6 +111,8 @@ public class SynchronizeFolderOperation extends RemoteOperation { @Override protected RemoteOperationResult run(WebdavClient client) { RemoteOperationResult result = null; + mFailsInFavouritesFound = 0; + mConflictsFound = 0; // code before in FileSyncAdapter.fetchData PropFindMethod query = null; @@ -117,24 +131,47 @@ public class SynchronizeFolderOperation extends RemoteOperation { if (mParentId == DataStorageManager.ROOT_PARENT_ID) { WebdavEntry we = new WebdavEntry(resp.getResponses()[0], client.getBaseUri().getPath()); OCFile parent = fillOCFile(we); - parent.setParentId(mParentId); mStorageManager.saveFile(parent); mParentId = parent.getFileId(); } // read contents in folder List updatedFiles = new Vector(resp.getResponses().length - 1); + List filesToSyncContents = new Vector(); for (int i = 1; i < resp.getResponses().length; ++i) { + /// new OCFile instance with the data from the server WebdavEntry we = new WebdavEntry(resp.getResponses()[i], client.getBaseUri().getPath()); OCFile file = fillOCFile(we); - file.setParentId(mParentId); + + /// set data about local state, keeping unchanged former data if existing + file.setLastSyncDateForProperties(mCurrentSyncTime); OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath()); if (oldFile != null) { - if (oldFile.keepInSync() && file.getModificationTimestamp() > oldFile.getModificationTimestamp()) { - disableObservance(file); // first disable observer so we won't get file upload right after download - requestContentDownload(file); - } file.setKeepInSync(oldFile.keepInSync()); + file.setLastSyncDateForData(oldFile.getLastSyncDateForData()); + file.setStoragePath(oldFile.getStoragePath()); + } + + /// scan default location if local copy of file is not linked in OCFile instance + if (file.getStoragePath() == null && !file.isDirectory()) { + File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); + if (f.exists()) { + file.setStoragePath(f.getAbsolutePath()); + file.setLastSyncDateForData(f.lastModified()); + } + } + + /// prepare content synchronization for kept-in-sync files + if (file.keepInSync()) { + SynchronizeFileOperation operation = new SynchronizeFileOperation( oldFile, + file, + mStorageManager, + mAccount, + true, + false, + mContext + ); + filesToSyncContents.add(operation); } updatedFiles.add(file); @@ -142,15 +179,35 @@ public class SynchronizeFolderOperation extends RemoteOperation { // save updated contents in local database; all at once, trying to get a best performance in database update (not a big deal, indeed) mStorageManager.saveFiles(updatedFiles); - + // request for the synchronization of files AFTER saving last properties + SynchronizeFileOperation op = null; + RemoteOperationResult contentsResult = null; + for (int i=0; i < filesToSyncContents.size(); i++) { + op = filesToSyncContents.get(i); + contentsResult = op.execute(client); // returns without waiting for upload or download finishes + if (!contentsResult.isSuccess()) { + if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) { + mConflictsFound++; + } else { + mFailsInFavouritesFound++; + if (contentsResult.getException() != null) { + Log.d(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage(), contentsResult.getException()); + } else { + Log.d(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage()); + } + } + } // won't let these fails break the synchronization process + } + + // removal of obsolete files mChildren = mStorageManager.getDirectoryContent(mStorageManager.getFileById(mParentId)); OCFile file; - String currentSavePath = FileDownloader.getSavePath(mAccount.name); + 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); @@ -164,7 +221,16 @@ public class SynchronizeFolderOperation extends RemoteOperation { } // prepare result object - result = new RemoteOperationResult(isMultiStatus(status), status); + if (isMultiStatus(status)) { + if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { + result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); // should be different result, but will do the job + + } else { + result = new RemoteOperationResult(true, status); + } + } else { + result = new RemoteOperationResult(false, status); + } Log.i(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage()); @@ -198,37 +264,9 @@ public class SynchronizeFolderOperation extends RemoteOperation { file.setFileLength(we.contentLength()); file.setMimetype(we.contentType()); file.setModificationTimestamp(we.modifiedTimesamp()); - file.setLastSyncDate(mCurrentSyncTime); + file.setParentId(mParentId); return file; } - - /** - * Request to stop the observance of local updates for a file. - * - * @param file OCFile representing the remote file to stop to monitor for local updates - */ - private void disableObservance(OCFile file) { - Log.d(TAG, "Disabling observation of remote file" + file.getRemotePath()); - Intent intent = new Intent(mContext, FileObserverService.class); - intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_ADD_DOWNLOADING_FILE); - intent.putExtra(FileObserverService.KEY_CMD_ARG, file.getRemotePath()); - mContext.startService(intent); - - } - - - /** - * Requests a download to the file download service - * - * @param file OCFile representing the remote file to download - */ - private void requestContentDownload(OCFile file) { - Intent intent = new Intent(mContext, FileDownloader.class); - intent.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount); - intent.putExtra(FileDownloader.EXTRA_FILE, file); - mContext.startService(intent); - } - } diff --git a/src/com/owncloud/android/operations/UpdateOCVersionOperation.java b/src/com/owncloud/android/operations/UpdateOCVersionOperation.java index 356b9e62..7072d39f 100644 --- a/src/com/owncloud/android/operations/UpdateOCVersionOperation.java +++ b/src/com/owncloud/android/operations/UpdateOCVersionOperation.java @@ -42,7 +42,7 @@ import eu.alefzero.webdav.WebdavClient; */ public class UpdateOCVersionOperation extends RemoteOperation { - private static final String TAG = UploadFileOperation.class.getSimpleName(); + private static final String TAG = UpdateOCVersionOperation.class.getSimpleName(); private Account mAccount; private Context mContext; diff --git a/src/com/owncloud/android/operations/UploadFileOperation.java b/src/com/owncloud/android/operations/UploadFileOperation.java index 36f70c3c..afdd3cbe 100644 --- a/src/com/owncloud/android/operations/UploadFileOperation.java +++ b/src/com/owncloud/android/operations/UploadFileOperation.java @@ -50,13 +50,16 @@ public class UploadFileOperation extends RemoteOperation { private Account mAccount; private OCFile mFile; + private OCFile mOldFile; private String mRemotePath = null; private boolean mIsInstant = false; private boolean mRemoteFolderToBeCreated = false; private boolean mForceOverwrite = false; + private boolean mWasRenamed = false; PutMethod mPutMethod = null; private Set mDataTransferListeners = new HashSet(); private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); + public UploadFileOperation( Account account, OCFile file, @@ -86,13 +89,16 @@ public class UploadFileOperation extends RemoteOperation { return mFile; } + public OCFile getOldFile() { + return mOldFile; + } + public String getStoragePath() { return mFile.getStoragePath(); } public String getRemotePath() { - //return mFile.getRemotePath(); // DON'T MAKE THIS ; the remotePath used can be different to mFile.getRemotePath() if mForceOverwrite is 'false'; see run(...) - return mRemotePath; + return mFile.getRemotePath(); } public String getMimeType() { @@ -115,6 +121,9 @@ public class UploadFileOperation extends RemoteOperation { return mForceOverwrite; } + public boolean wasRenamed() { + return mWasRenamed; + } public Set getDataTransferListeners() { return mDataTransferListeners; @@ -132,7 +141,11 @@ public class UploadFileOperation extends RemoteOperation { try { /// rename the file to upload, if necessary if (!mForceOverwrite) { - mRemotePath = getAvailableRemotePath(client, mRemotePath); + String remotePath = getAvailableRemotePath(client, mRemotePath); + mWasRenamed = !remotePath.equals(mRemotePath); + if (mWasRenamed) { + createNewOCFile(remotePath); + } } /// perform the upload @@ -162,6 +175,24 @@ public class UploadFileOperation extends RemoteOperation { } + private void createNewOCFile(String newRemotePath) { + // a new OCFile instance must be created for a new remote path + OCFile newFile = new OCFile(newRemotePath); + newFile.setCreationTimestamp(mFile.getCreationTimestamp()); + newFile.setFileLength(mFile.getFileLength()); + newFile.setMimetype(mFile.getMimetype()); + newFile.setModificationTimestamp(mFile.getModificationTimestamp()); + // newFile.setEtag(mFile.getEtag()) + newFile.setKeepInSync(mFile.keepInSync()); + newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties()); + newFile.setLastSyncDateForData(mFile.getLastSyncDateForData()); + newFile.setStoragePath(mFile.getStoragePath()); + newFile.setParentId(mFile.getParentId()); + mOldFile = mFile; + mFile = newFile; + } + + public boolean isSuccess(int status) { return ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED || status == HttpStatus.SC_NO_CONTENT)); } diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index 980e0457..c99616ef 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/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); } } diff --git a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java index b78b127b..e977e41c 100644 --- a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java @@ -28,15 +28,10 @@ import com.owncloud.android.R; import com.owncloud.android.datamodel.DataStorageManager; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -//<<<<<<< HEAD import com.owncloud.android.operations.RemoteOperationResult; import com.owncloud.android.operations.SynchronizeFolderOperation; import com.owncloud.android.operations.UpdateOCVersionOperation; -/*======= -import com.owncloud.android.files.services.FileDownloader; -import com.owncloud.android.files.services.FileObserverService; -import com.owncloud.android.utils.OwnCloudVersion; ->>>>>>> origin/master*/ +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; import android.accounts.Account; import android.app.Notification; @@ -71,6 +66,8 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { private int mFailedResultsCounter; private RemoteOperationResult mLastFailedResult; private SyncResult mSyncResult; + private int mConflictsFound; + private int mFailsInFavouritesFound; public FileSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); @@ -88,8 +85,12 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { mIsManualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); mFailedResultsCounter = 0; mLastFailedResult = null; + mConflictsFound = 0; + mFailsInFavouritesFound = 0; mSyncResult = syncResult; - + mSyncResult.fullSyncRequested = false; + mSyncResult.delayUntil = 60*60*24; // sync after 24h + this.setAccount(account); this.setContentProvider(provider); this.setStorageManager(new FileDataStorageManager(account, getContentProvider())); @@ -126,13 +127,15 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { /// notify the user about the failure of MANUAL synchronization notifyFailedSynchronization(); + + } else if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { + notifyFailsInFavourites(); } sendStickyBroadcast(false, null, mLastFailedResult); // message to signal the end to the UI } } - - + /** * Called by system SyncManager when a synchronization is required to be cancelled. @@ -186,12 +189,16 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { // synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess sendStickyBroadcast(true, remotePath, null); - if (result.isSuccess()) { + if (result.isSuccess() || result.getCode() == ResultCode.SYNC_CONFLICT) { + + if (result.getCode() == ResultCode.SYNC_CONFLICT) { + mConflictsFound += synchFolderOp.getConflictsFound(); + mFailsInFavouritesFound += synchFolderOp.getFailsInFavouritesFound(); + } // synchronize children folders List children = synchFolderOp.getChildren(); fetchChildren(children); // beware of the 'hidden' recursion here! -//<<<<<<< HEAD } else { if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED) { mSyncResult.stats.numAuthExceptions++; @@ -201,33 +208,6 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { } else if (result.getException() instanceof IOException) { mSyncResult.stats.numIoExceptions++; -/*======= - // insertion or update of files - List updatedFiles = new Vector(resp.getResponses().length - 1); - for (int i = 1; i < resp.getResponses().length; ++i) { - WebdavEntry we = new WebdavEntry(resp.getResponses()[i], getUri().getPath()); - OCFile file = fillOCFile(we); - file.setParentId(parentId); - if (getStorageManager().getFileByPath(file.getRemotePath()) != null && - getStorageManager().getFileByPath(file.getRemotePath()).keepInSync() && - file.getModificationTimestamp() > getStorageManager().getFileByPath(file.getRemotePath()) - .getModificationTimestamp()) { - // first disable observer so we won't get file upload right after download - Log.d(TAG, "Disabling observation of remote file" + file.getRemotePath()); - Intent intent = new Intent(getContext(), FileObserverService.class); - intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_ADD_DOWNLOADING_FILE); - intent.putExtra(FileObserverService.KEY_CMD_ARG, file.getRemotePath()); - getContext().startService(intent); - intent = new Intent(this.getContext(), FileDownloader.class); - intent.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount()); - intent.putExtra(FileDownloader.EXTRA_FILE, file); - file.setKeepInSync(true); - getContext().startService(intent); - } - if (getStorageManager().getFileByPath(file.getRemotePath()) != null) - file.setKeepInSync(getStorageManager().getFileByPath(file.getRemotePath()).keepInSync()); ->>>>>>> origin/master*/ - } mFailedResultsCounter++; mLastFailedResult = result; @@ -306,6 +286,35 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_ticker, notification); } - + + /** + * Notifies the user about conflicts and strange fails when trying to synchronize the contents of favourite files. + * + * By now, we won't consider a failed synchronization. + */ + private void notifyFailsInFavourites() { + if (mFailedResultsCounter > 0) { + Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_in_favourites_ticker), System.currentTimeMillis()); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + // TODO put something smart in the contentIntent below + notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); + notification.setLatestEventInfo(getContext().getApplicationContext(), + getContext().getString(R.string.sync_fail_in_favourites_ticker), + String.format(getContext().getString(R.string.sync_fail_in_favourites_content), mFailedResultsCounter + mConflictsFound, mConflictsFound), + notification.contentIntent); + ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_in_favourites_ticker, notification); + + } else { + Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_conflicts_in_favourites_ticker), System.currentTimeMillis()); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + // TODO put something smart in the contentIntent below + notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); + notification.setLatestEventInfo(getContext().getApplicationContext(), + getContext().getString(R.string.sync_conflicts_in_favourites_ticker), + String.format(getContext().getString(R.string.sync_conflicts_in_favourites_content), mConflictsFound), + notification.contentIntent); + ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_conflicts_in_favourites_ticker, notification); + } + } } diff --git a/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java b/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java index 23ce0c8c..52714b6a 100644 --- a/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java +++ b/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java @@ -476,8 +476,14 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity onFocusChange(findViewById(R.id.host_URL), false); } else if (v.getId() == R.id.viewPassword) { TextView view = (TextView) findViewById(R.id.account_password); - int input_type = InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; + int input_type = view.getInputType(); + if ((input_type & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { + input_type = InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_PASSWORD; + } else { + input_type = InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; + } view.setInputType(input_type); } } diff --git a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java index 057e0417..8f008e74 100644 --- a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java +++ b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java @@ -19,6 +19,7 @@ package com.owncloud.android.ui.activity; import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.ui.dialog.ConflictsResolveDialog; import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision; @@ -38,21 +39,27 @@ import android.util.Log; */ public class ConflictsResolveActivity extends SherlockFragmentActivity implements OnConflictDecisionMadeListener { + public static final String EXTRA_FILE = "FILE"; + public static final String EXTRA_ACCOUNT = "ACCOUNT"; + private String TAG = ConflictsResolveActivity.class.getSimpleName(); - private String mRemotePath; + //private String mRemotePath; - private String mLocalPath; + //private String mLocalPath; + private OCFile mFile; private Account mOCAccount; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mRemotePath = getIntent().getStringExtra("remotepath"); - mLocalPath = getIntent().getStringExtra("localpath"); - mOCAccount = getIntent().getParcelableExtra("account"); - ConflictsResolveDialog d = ConflictsResolveDialog.newInstance(mRemotePath, this); + + //mRemotePath = getIntent().getStringExtra("remotepath"); + //mLocalPath = getIntent().getStringExtra("localpath"); + mFile = getIntent().getParcelableExtra(EXTRA_FILE); + mOCAccount = getIntent().getParcelableExtra(EXTRA_ACCOUNT); + ConflictsResolveDialog d = ConflictsResolveDialog.newInstance(mFile.getRemotePath(), this); d.showDialog(this); } @@ -62,6 +69,7 @@ public class ConflictsResolveActivity extends SherlockFragmentActivity implement switch (decision) { case CANCEL: + finish(); return; case OVERWRITE: i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true); @@ -72,8 +80,9 @@ public class ConflictsResolveActivity extends SherlockFragmentActivity implement return; } i.putExtra(FileUploader.KEY_ACCOUNT, mOCAccount); - i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath); - i.putExtra(FileUploader.KEY_LOCAL_FILE, mLocalPath); + //i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath); + //i.putExtra(FileUploader.KEY_LOCAL_FILE, mLocalPath); + i.putExtra(FileUploader.KEY_FILE, mFile); i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE); startService(i); diff --git a/src/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java b/src/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java index 1319562e..1c89790c 100644 --- a/src/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java +++ b/src/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java @@ -109,6 +109,11 @@ public class ConflictsResolveDialog extends SherlockDialogFragment { } } + @Override + public void onCancel(DialogInterface dialog) { + mListener.ConflictDecisionMade(Decision.CANCEL); + } + public interface OnConflictDecisionMadeListener { public void ConflictDecisionMade(Decision decision); } diff --git a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java index d0c0a94f..47e75d37 100644 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/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; @@ -111,6 +113,7 @@ public class FileDetailFragment extends SherlockFragment implements private View mView; private OCFile mFile; private Account mAccount; + private FileDataStorageManager mStorageManager; private ImageView mPreview; private DownloadFinishReceiver mDownloadFinishReceiver; @@ -119,7 +122,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"; @@ -132,6 +135,7 @@ public class FileDetailFragment extends SherlockFragment implements public FileDetailFragment() { mFile = null; mAccount = null; + mStorageManager = null; mLayout = R.layout.file_details_empty; } @@ -147,6 +151,7 @@ public class FileDetailFragment extends SherlockFragment implements public FileDetailFragment(OCFile fileToDetail, Account ocAccount) { mFile = fileToDetail; mAccount = ocAccount; + mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment mLayout = R.layout.file_details_empty; if(fileToDetail != null && ocAccount != null) { @@ -203,6 +208,18 @@ public class FileDetailFragment extends SherlockFragment implements throw new ClassCastException(activity.toString() + " must implement " + FileDetailFragment.ContainerActivity.class.getSimpleName()); } } + + + /** + * {@inheritDoc} + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (mAccount != null) { + mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());; + } + } @Override @@ -257,7 +274,6 @@ public class FileDetailFragment extends SherlockFragment implements public void onClick(View v) { switch (v.getId()) { case R.id.fdDownloadBtn: { - //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath())) { FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) { @@ -289,42 +305,39 @@ public class FileDetailFragment extends SherlockFragment implements } } else { - 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, null, mStorageManager, 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); - if (mFile.keepInSync()) { - onClick(getView().findViewById(R.id.fdDownloadBtn)); - } else { - mContainerActivity.onFileStateChanged(); // put inside 'else' to not call it twice (here, and in the virtual click on fdDownloadBtn) - } + mStorageManager.saveFile(mFile); + /// register the OCFile instance in the observer service to monitor local updates; + /// if necessary, the file is download 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()); + intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, mFile); + intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount); Log.e(TAG, "starting observer service"); getActivity().startService(intent); + if (mFile.keepInSync()) { + onClick(getView().findViewById(R.id.fdDownloadBtn)); // force an immediate synchronization + } break; } case R.id.fdRenameBtn: { @@ -403,11 +416,10 @@ public class FileDetailFragment extends SherlockFragment implements @Override public void onConfirmation(String callerTag) { if (callerTag.equals(FTAG_CONFIRMATION)) { - FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getContentResolver()); - if (fdsm.getFileById(mFile.getFileId()) != null) { + if (mStorageManager.getFileById(mFile.getFileId()) != null) { mLastRemoteOperation = new RemoveFileOperation( mFile, true, - new FileDataStorageManager(mAccount, getActivity().getContentResolver())); + mStorageManager); WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); mLastRemoteOperation.execute(wc, this, mHandler); @@ -419,12 +431,11 @@ public class FileDetailFragment extends SherlockFragment implements @Override public void onNeutral(String callerTag) { - FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getContentResolver()); File f = null; if (mFile.isDown() && (f = new File(mFile.getStoragePath())).exists()) { f.delete(); mFile.setStoragePath(null); - fdsm.saveFile(mFile); + mStorageManager.saveFile(mFile); updateFileDetails(mFile, mAccount); } } @@ -460,6 +471,12 @@ public class FileDetailFragment extends SherlockFragment implements */ public void updateFileDetails(OCFile file, Account ocAccount) { mFile = file; + if (ocAccount != null && ( + mStorageManager == null || + (mAccount != null && !mAccount.equals(ocAccount)) + )) { + mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver()); + } mAccount = ocAccount; updateFileDetails(false); } @@ -596,8 +613,7 @@ public class FileDetailFragment extends SherlockFragment implements private void setButtonsForDown() { if (!isEmpty()) { Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn); - downloadButton.setText(R.string.filedetails_redownload); - //downloadButton.setEnabled(true); + downloadButton.setText(R.string.filedetails_sync_file); ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(true); ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true); @@ -680,7 +696,7 @@ public class FileDetailFragment extends SherlockFragment implements String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); if (mFile.getRemotePath().equals(downloadedRemotePath)) { if (downloadWasFine) { - mFile.setStoragePath(intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH)); // updates the local object without accessing the database again + mFile = mStorageManager.getFileByPath(downloadedRemotePath); } updateFileDetails(false); // it updates the buttons; must be called although !downloadWasFine } @@ -707,10 +723,16 @@ public class FileDetailFragment extends SherlockFragment implements if (!isEmpty() && accountName.equals(mAccount.name)) { boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false); String uploadRemotePath = intent.getStringExtra(FileUploader.EXTRA_REMOTE_PATH); - if (mFile.getRemotePath().equals(uploadRemotePath)) { + boolean renamedInUpload = mFile.getRemotePath().equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH)); + if (mFile.getRemotePath().equals(uploadRemotePath) || + renamedInUpload) { if (uploadWasFine) { - FileDataStorageManager fdsm = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver()); - mFile = fdsm.getFileByPath(mFile.getRemotePath()); + mFile = mStorageManager.getFileByPath(mFile.getRemotePath()); + } + if (renamedInUpload) { + String newName = (new File(uploadRemotePath)).getName(); + Toast msg = Toast.makeText(getActivity().getApplicationContext(), String.format(getString(R.string.filedetails_renamed_in_upload_msg), newName), Toast.LENGTH_LONG); + msg.show(); } updateFileDetails(false); // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server } @@ -924,6 +946,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,6 +1002,45 @@ 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.putExtra(ConflictsResolveActivity.EXTRA_FILE, mFile); + i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount); + startActivity(i); + + } else { + Toast msg = Toast.makeText(getActivity(), R.string.sync_file_fail_msg, Toast.LENGTH_LONG); + msg.show(); + } + + if (mFile.isDown()) { + setButtonsForDown(); + + } else { + setButtonsForRemote(); + } + + } 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(); + } + } + } + } } diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java new file mode 100644 index 00000000..620e581e --- /dev/null +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -0,0 +1,47 @@ +/* ownCloud Android client application + * Copyright (C) 2012 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.utils; + +import java.io.File; + +import android.net.Uri; +import android.os.Environment; +import com.owncloud.android.datamodel.OCFile; + + +public class FileStorageUtils { + + public static final String getSavePath(String accountName) { + File sdCard = Environment.getExternalStorageDirectory(); + return sdCard.getAbsolutePath() + "/owncloud/" + Uri.encode(accountName, "@"); + // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B + } + + public static final String getDefaultSavePathFor(String accountName, OCFile file) { + return getSavePath(accountName) + file.getRemotePath(); + } + + public static final String getTemporalPath(String accountName) { + File sdCard = Environment.getExternalStorageDirectory(); + return sdCard.getAbsolutePath() + "/owncloud/tmp/" + Uri.encode(accountName, "@"); + // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B + } + + +} \ No newline at end of file