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=-c Merge ... master after sync_review was merged into it --- 4c50eb4d2dd1fba177d743315ae52223430db8cb diff --combined res/values/strings.xml index bf9ab002,0428f411..378bb00e --- a/res/values/strings.xml +++ b/res/values/strings.xml @@@ -74,14 -74,14 +74,16 @@@ Created: Modified: Download - Refresh + Refresh + Redownload Open + File was renamed to %1$s during upload Yes No OK - Cancel + Cancel download + Cancel upload + Cancel Save & Exit Leave ownCloud Error @@@ -102,7 -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 -110,10 +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 @@@ -171,20 -175,20 +177,23 @@@ Rename Remove - "Do you really want to remove %1$s ?" - Local only - Remove from server - Remote and local + "Do you really want to remove %1$s ?" + "Do you really want to remove %1$s and its contents ?" + Local only + Local contents only + Remove from server + Remote and local "Successful removal" "Removal could not be completed" + Enter a new name "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 --combined src/com/owncloud/android/datamodel/FileDataStorageManager.java index 4ed425e1,7d9a678c..26404045 --- a/src/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/com/owncloud/android/datamodel/FileDataStorageManager.java @@@ -27,7 -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,13 +118,18 @@@ public class FileDataStorageManager imp 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 -142,6 +147,6 @@@ + 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,36 -194,19 +199,32 @@@ 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 + "=?", new String[] { String.valueOf(file.getFileId()) }) .build()); + } else if (fileExists(file.getFileId())) { + 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()); + + operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). + withValues(cv). + withSelection( ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(file.getFileId()) }) + .build()); + } else { operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI).withValues(cv).build()); } @@@ -426,10 -383,12 +401,12 @@@ 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 -397,10 +415,10 @@@ .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); } @@@ -465,111 -426,6 +444,111 @@@ if (file.isDown() && removeLocalCopy) { 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(); + } + } + } + + @Override + public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent) { + // TODO consider possible failures + if (dir != null && dir.isDirectory() && dir.getFileId() != -1) { + Vector children = getDirectoryContent(dir); + if (children != null) { + OCFile child = null; + for (int i=0; i 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 { + ContentValues cv = new ContentValues(); // don't take the constructor out of the loop and clear the object + OCFile child = createFileInstance(c); + cv.put(ProviderTableMeta.FILE_PATH, newPath + child.getRemotePath().substring(lengthOfOldPath)); + if (child.getStoragePath() != null && child.getStoragePath().startsWith(defaultSavePath)) { + cv.put(ProviderTableMeta.FILE_STORAGE_PATH, defaultSavePath + newPath + child.getStoragePath().substring(lengthOfOldStoragePath)); + } + operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). + withValues(cv). + withSelection( ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(child.getFileId()) }) + .build()); + } while (c.moveToNext()); + } + c.close(); + + /// 3. apply updates in batch + try { + if (getContentResolver() != null) { + getContentResolver().applyBatch(ProviderMeta.AUTHORITY_FILES, operations); + + } else { + getContentProvider().applyBatch(operations); + } + + } catch (OperationApplicationException e) { + Log.e(TAG, "Fail to update descendants of " + dir.getFileId() + " in database", e); + + } catch (RemoteException e) { + Log.e(TAG, "Fail to update desendants of " + dir.getFileId() + " in database", e); + } + + } } } diff --combined src/com/owncloud/android/datamodel/OCFile.java index 21b8c375,766a8325..ec4e749b --- a/src/com/owncloud/android/datamodel/OCFile.java +++ b/src/com/owncloud/android/datamodel/OCFile.java @@@ -22,7 -22,6 +22,7 @@@ import java.io.File import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; public class OCFile implements Parcelable, Comparable { @@@ -39,8 -38,6 +39,8 @@@ }; public static final String PATH_SEPARATOR = "/"; + + private static final String TAG = OCFile.class.getSimpleName(); private long mId; private long mParentId; @@@ -51,9 -48,12 +51,12 @@@ 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 -86,8 +89,8 @@@ mMimeType = source.readString(); mNeedsUpdating = source.readInt() == 0; mKeepInSync = source.readInt() == 1; - mLastSyncDate = source.readLong(); + mLastSyncDateForProperties = source.readLong(); + mLastSyncDateForData = source.readLong(); } @Override @@@ -101,7 -102,8 +105,8 @@@ dest.writeString(mMimeType); dest.writeInt(mNeedsUpdating ? 1 : 0); dest.writeInt(mKeepInSync ? 1 : 0); - dest.writeLong(mLastSyncDate); + dest.writeLong(mLastSyncDateForProperties); + dest.writeLong(mLastSyncDateForData); } /** @@@ -215,25 -217,7 +220,25 @@@ */ public String getFileName() { File f = new File(getRemotePath()); - return f.getName().length() == 0 ? "/" : f.getName(); + return f.getName().length() == 0 ? PATH_SEPARATOR : f.getName(); + } + + /** + * Sets the name of the file + * + * Does nothing if the new name is null, empty or includes "/" ; or if the file is the root directory + */ + public void setFileName(String name) { + Log.d(TAG, "OCFile name changin from " + mRemotePath); + if (name != null && name.length() > 0 && !name.contains(PATH_SEPARATOR) && !mRemotePath.equals(PATH_SEPARATOR)) { + String parent = (new File(getRemotePath())).getParent(); + parent = (parent.endsWith(PATH_SEPARATOR)) ? parent : parent + PATH_SEPARATOR; + mRemotePath = parent + name; + if (isDirectory()) { + mRemotePath += PATH_SEPARATOR; + } + Log.d(TAG, "OCFile name changed to " + mRemotePath); + } } /** @@@ -275,7 -259,8 +280,8 @@@ mLength = 0; mCreationTimestamp = 0; mModifiedTimestamp = 0; - mLastSyncDate = 0; + mLastSyncDateForProperties = 0; + mLastSyncDateForData = 0; mKeepInSync = false; mNeedsUpdating = false; } @@@ -343,12 -328,20 +349,20 @@@ 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) { @@@ -391,8 -384,20 +405,20 @@@ @Override public String toString() { String asString = "[id=%s, name=%s, mime=%s, downloaded=%s, local=%s, remote=%s, parentId=%s, keepInSinc=%s]"; - asString = String.format(asString, new Long(mId), getFileName(), mMimeType, isDown(), mLocalPath, mRemotePath, new Long(mParentId), new Boolean(mKeepInSync)); + asString = String.format(asString, Long.valueOf(mId), getFileName(), mMimeType, isDown(), mLocalPath, mRemotePath, Long.valueOf(mParentId), Boolean.valueOf(mKeepInSync)); 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 --combined src/com/owncloud/android/files/services/FileDownloader.java index 0f2d6742,f58e563c..5e80f5d3 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@@ -25,8 -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 +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 +59,7 @@@ public class FileDownloader extends Ser 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 +73,7 @@@ 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 +92,6 @@@ 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 +136,7 @@@ 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()); @@@ -202,27 -190,14 +190,27 @@@ /** - * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download + * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download. + * + * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download. * * @param account Owncloud account where the remote file is stored. * @param file A file that could be in the queue of downloads. */ public boolean isDownloading(Account account, OCFile file) { + String targetKey = buildRemoteName(account, file); synchronized (mPendingDownloads) { - return (mPendingDownloads.containsKey(buildRemoteName(account, file))); + if (file.isDirectory()) { + // this can be slow if there are many downloads :( + Iterator it = mPendingDownloads.keySet().iterator(); + boolean found = false; + while (it.hasNext() && !found) { + found = it.next().startsWith(targetKey); + } + return found; + } else { + return (mPendingDownloads.containsKey(targetKey)); + } } } } @@@ -277,6 -252,7 +265,7 @@@ /// 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 -261,7 +274,7 @@@ 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 -274,28 +287,28 @@@ /// 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 -369,32 +382,32 @@@ /** - * 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 --combined src/com/owncloud/android/files/services/FileUploader.java index 7956a99b,1847a960..958036a7 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@@ -25,6 -25,10 +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 +38,12 @@@ import com.owncloud.android.operations. 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; @@@ -64,17 -71,22 +71,21 @@@ import eu.alefzero.webdav.WebdavClient public class FileUploader extends Service implements OnDatatransferProgressListener { public static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH"; - public static final String EXTRA_PARENT_DIR_ID = "PARENT_DIR_ID"; 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"; // TODO remove this as a possible input argument ; use KEY_FILE everywhere - public static final String KEY_REMOTE_FILE = "REMOTE_FILE"; // TODO remove this as a possible input argument ; use KEY_FILE everywhere + 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 -161,7 +160,7 @@@ */ @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 -172,76 +171,76 @@@ } 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(); @@@ -280,25 -313,12 +312,25 @@@ /** * Returns True when the file described by 'file' is being uploaded to the ownCloud account 'account' or waiting for it * + * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download. + * * @param account Owncloud account where the remote file will be stored. * @param file A file that could be in the queue of pending uploads */ public boolean isUploading(Account account, OCFile file) { + String targetKey = buildRemoteName(account, file); synchronized (mPendingUploads) { - return (mPendingUploads.containsKey(buildRemoteName(account, file))); + if (file.isDirectory()) { + // this can be slow if there are many downloads :( + Iterator it = mPendingUploads.keySet().iterator(); + boolean found = false; + while (it.hasNext() && !found) { + found = it.next().startsWith(targetKey); + } + return found; + } else { + return (mPendingUploads.containsKey(targetKey)); + } } } } @@@ -372,7 -392,7 +404,7 @@@ try { uploadResult = mCurrentUpload.execute(mUploadClient); if (uploadResult.isSuccess()) { - saveUploadedFile(mCurrentUpload.getFile(), mStorageManager); + saveUploadedFile(); } } finally { @@@ -391,15 -411,89 +423,89 @@@ } /** - * 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 -511,17 +523,17 @@@ } - 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 @@@ -446,7 -541,7 +553,7 @@@ // parent dir String parentPath = new File(remotePath).getParent(); - parentPath = parentPath.endsWith("/")?parentPath:parentPath+"/" ; + parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR ; OCFile parentDir = storageManager.getFileByPath(parentPath); if (parentDir == null) { throw new IllegalStateException("Can not upload a file to a non existing remote location: " + parentPath); @@@ -581,10 -676,14 +688,13 @@@ 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); - end.putExtra(EXTRA_PARENT_DIR_ID, upload.getFile().getParentId()); + sendStickyBroadcast(end); } diff --combined src/com/owncloud/android/operations/RenameFileOperation.java index a2d3b300,483a312e..f4ec7b0a --- a/src/com/owncloud/android/operations/RenameFileOperation.java +++ b/src/com/owncloud/android/operations/RenameFileOperation.java @@@ -24,13 -24,12 +24,13 @@@ import java.io.IOException import org.apache.jackrabbit.webdav.client.methods.DavMethodBase; //import org.apache.jackrabbit.webdav.client.methods.MoveMethod; +import android.accounts.Account; 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; @@@ -49,9 -48,7 +49,9 @@@ public class RenameFileOperation extend private OCFile mFile; + private Account mAccount; private String mNewName; + private String mNewRemotePath; private DataStorageManager mStorageManager; @@@ -59,15 -56,12 +59,15 @@@ * Constructor * * @param file OCFile instance describing the remote file or folder to rename + * @param account OwnCloud account containing the remote file * @param newName New name to set as the name of file. * @param storageManager Reference to the local database corresponding to the account where the file is contained. */ - public RenameFileOperation(OCFile file, String newName, DataStorageManager storageManager) { + public RenameFileOperation(OCFile file, Account account, String newName, DataStorageManager storageManager) { mFile = file; + mAccount = account; mNewName = newName; + mNewRemotePath = null; mStorageManager = storageManager; } @@@ -86,63 -80,60 +86,63 @@@ RemoteOperationResult result = null; LocalMoveMethod move = null; - //MoveMethod move = null; // TODO find out why not use this - String newRemotePath = null; + mNewRemotePath = null; try { if (mNewName.equals(mFile.getFileName())) { return new RemoteOperationResult(ResultCode.OK); } - newRemotePath = (new File(mFile.getRemotePath())).getParent() + mNewName; + String parent = (new File(mFile.getRemotePath())).getParent(); + parent = (parent.endsWith(OCFile.PATH_SEPARATOR)) ? parent : parent + OCFile.PATH_SEPARATOR; + mNewRemotePath = parent + mNewName; + if (mFile.isDirectory()) { + mNewRemotePath += OCFile.PATH_SEPARATOR; + } // check if the new name is valid in the local file system if (!isValidNewName()) { return new RemoteOperationResult(ResultCode.INVALID_LOCAL_FILE_NAME); } - // check if a remote file with the new name already exists - if (client.existsFile(newRemotePath)) { + // check if a file with the new name already exists + if (client.existsFile(mNewRemotePath) || // remote check could fail by network failure, or by indeterminate behavior of HEAD for folders ... + mStorageManager.getFileByPath(mNewRemotePath) != null) { // ... so local check is convenient return new RemoteOperationResult(ResultCode.INVALID_OVERWRITE); } - /*move = new MoveMethod( client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()), - client.getBaseUri() + WebdavUtils.encodePath(newRemotePath), - false);*/ move = new LocalMoveMethod( client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()), - client.getBaseUri() + WebdavUtils.encodePath(newRemotePath)); + client.getBaseUri() + WebdavUtils.encodePath(mNewRemotePath)); int status = client.executeMethod(move, RENAME_READ_TIMEOUT, RENAME_CONNECTION_TIMEOUT); if (move.succeeded()) { - // create new OCFile instance for the renamed file - OCFile newFile = obtainUpdatedFile(); - OCFile oldFile = mFile; - mFile = newFile; - - // try to rename the local copy of the file - if (oldFile.isDown()) { - File f = new File(oldFile.getStoragePath()); - String newStoragePath = f.getParent() + mNewName; - if (f.renameTo(new File(newStoragePath))) { - mFile.setStoragePath(newStoragePath); - } - // else - NOTHING: the link to the local file is kept although the local name can't be updated - // TODO - study conditions when this could be a problem + if (mFile.isDirectory()) { + saveLocalDirectory(); + + } else { + saveLocalFile(); + } - - mStorageManager.removeFile(oldFile, false); - mStorageManager.saveFile(mFile); + + /* + *} else if (mFile.isDirectory() && (status == 207 || status >= 500)) { + * // TODO + * // if server fails in the rename of a folder, some children files could have been moved to a folder with the new name while some others + * // stayed in the old folder; + * // + * // easiest and heaviest solution is synchronizing the parent folder (or the full account); + * // + * // a better solution is synchronizing the folders with the old and new names; + *} + */ } move.getResponseBodyAsString(); // exhaust response, although not interesting result = new RemoteOperationResult(move.succeeded(), status); - Log.i(TAG, "Rename " + mFile.getRemotePath() + " to " + newRemotePath + ": " + result.getLogMessage()); + Log.i(TAG, "Rename " + mFile.getRemotePath() + " to " + mNewRemotePath + ": " + result.getLogMessage()); } catch (Exception e) { result = new RemoteOperationResult(e); - Log.e(TAG, "Rename " + mFile.getRemotePath() + " to " + ((newRemotePath==null) ? mNewName : newRemotePath) + ": " + result.getLogMessage(), e); + Log.e(TAG, "Rename " + mFile.getRemotePath() + " to " + ((mNewRemotePath==null) ? mNewName : mNewRemotePath) + ": " + result.getLogMessage(), e); } finally { if (move != null) @@@ -152,35 -143,6 +152,35 @@@ } + 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 + } + } + + private void saveLocalFile() { + mFile.setFileName(mNewName); + + // try to rename the local copy of the file + if (mFile.isDown()) { + File f = new File(mFile.getStoragePath()); + String parentStoragePath = f.getParent(); + if (!parentStoragePath.endsWith(File.separator)) + parentStoragePath += File.separator; + if (f.renameTo(new File(parentStoragePath + mNewName))) { + mFile.setStoragePath(parentStoragePath + mNewName); + } + // else - NOTHING: the link to the local file is kept although the local name can't be updated + // TODO - study conditions when this could be a problem + } + + mStorageManager.saveFile(mFile); + } + /** * Checks if the new name to set is valid in the file system * @@@ -201,7 -163,7 +201,7 @@@ 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 -180,27 +218,7 @@@ } - /** - * Creates a new OCFile for the new remote name of the renamed file. - * - * @return OCFile object with the same information than mFile, but the renamed remoteFile and the storagePath (empty) - */ - private OCFile obtainUpdatedFile() { - OCFile file = new OCFile(mStorageManager.getFileById(mFile.getParentId()).getRemotePath() + mNewName); - file.setCreationTimestamp(mFile.getCreationTimestamp()); - file.setFileId(mFile.getFileId()); - file.setFileLength(mFile.getFileLength()); - file.setKeepInSync(mFile.keepInSync()); - file.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties()); - file.setLastSyncDateForData(mFile.getLastSyncDateForData()); - file.setMimetype(mFile.getMimetype()); - file.setModificationTimestamp(mFile.getModificationTimestamp()); - file.setParentId(mFile.getParentId()); - return file; - } - -- -- // 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 --combined src/com/owncloud/android/ui/fragment/FileDetailFragment.java index d0c0a94f,9338b93f..47e75d37 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@@ -82,6 -82,8 +82,8 @@@ import com.owncloud.android.operations. 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 +113,7 @@@ public class FileDetailFragment extend private View mView; private OCFile mFile; private Account mAccount; + private FileDataStorageManager mStorageManager; private ImageView mPreview; private DownloadFinishReceiver mDownloadFinishReceiver; @@@ -119,7 -122,7 +122,7 @@@ 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 +135,7 @@@ public FileDetailFragment() { mFile = null; mAccount = null; + mStorageManager = null; mLayout = R.layout.file_details_empty; } @@@ -147,6 -151,7 +151,7 @@@ 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) { @@@ -186,7 -191,7 +191,7 @@@ mPreview = (ImageView)mView.findViewById(R.id.fdPreview); } - updateFileDetails(); + updateFileDetails(false); return view; } @@@ -203,6 -208,18 +208,18 @@@ 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,7 +274,6 @@@ 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,46 -306,44 +305,43 @@@ } } 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: { - EditNameDialog dialog = EditNameDialog.newInstance(mFile.getFileName()); - dialog.setOnDismissListener(this); + EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mFile.getFileName(), this); dialog.show(getFragmentManager(), "nameeditdialog"); break; } @@@ -359,13 -374,8 +372,13 @@@ try { Intent i = new Intent(Intent.ACTION_VIEW); mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); - if (mimeType != null && !mimeType.equals(mFile.getMimetype())) { - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType); + if (mimeType == null || !mimeType.equals(mFile.getMimetype())) { + if (mimeType != null) { + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType); + } else { + // desperate try + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*"); + } i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); startActivity(i); toastIt = false; @@@ -403,11 -413,10 +416,10 @@@ @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 -428,11 +431,11 @@@ @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,22 -468,21 +471,28 @@@ */ 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(); + updateFileDetails(false); } /** * Updates the view with all relevant details about that file. + * + * TODO Remove parameter when the transferring state of files is kept in database. + * + * @param transferring Flag signaling if the file should be considered as downloading or uploading, + * although {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and + * {@link FileUploaderBinder#isUploading(Account, OCFile)} return false. + * */ - public void updateFileDetails() { + public void updateFileDetails(boolean transferring) { if (mFile != null && mAccount != null && mLayout == R.layout.file_details_fragment) { @@@ -497,7 -504,7 +514,7 @@@ //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath()) || FileUploader.isUploading(mAccount, mFile.getRemotePath())) { FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); - if ((downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))) { + if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))) { setButtonsForTransferring(); } else if (mFile.isDown()) { @@@ -510,11 -517,9 +527,11 @@@ setButtonsForDown(); } else { + // TODO load default preview image; when the local file is removed, the preview remains there setButtonsForRemote(); } } + getView().invalidate(); } @@@ -596,8 -601,8 +613,7 @@@ 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); - //downloadButton.setEnabled(true); ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(true); ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true); @@@ -680,9 -685,9 +696,9 @@@ 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(); // it updates the buttons; must be called although !downloadWasFine + updateFileDetails(false); // it updates the buttons; must be called although !downloadWasFine } } } @@@ -702,17 -707,24 +718,23 @@@ private class UploadFinishReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - Log.d(TAG, "Received broacast! : " + intent.getStringExtra(FileUploader.EXTRA_REMOTE_PATH)); String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME); 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(); // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server + updateFileDetails(false); // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server } } } @@@ -828,7 -840,6 +850,7 @@@ String newFilename = dialog.getNewFilename(); Log.d(TAG, "name edit dialog dismissed with new name " + newFilename); mLastRemoteOperation = new RenameFileOperation( mFile, + mAccount, newFilename, new FileDataStorageManager(mAccount, getActivity().getContentResolver())); WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); @@@ -924,6 -935,9 +946,9 @@@ } else if (operation instanceof RenameFileOperation) { onRenameFileOperationFinish((RenameFileOperation)operation, result); + + } else if (operation instanceof SynchronizeFileOperation) { + onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result); } } } @@@ -977,6 -991,45 +1002,45 @@@ } } } + + 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(); + } + } + } + } }