From: tobiasKaminsky Date: Sat, 31 Oct 2015 07:48:10 +0000 (+0100) Subject: Merge remote-tracking branch 'remotes/upstream/master' into beta X-Git-Tag: beta-20151122~66 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/495fd6e4041c6ed933022e49d1aee0a30a9d6f76?hp=-c Merge remote-tracking branch 'remotes/upstream/master' into beta --- 495fd6e4041c6ed933022e49d1aee0a30a9d6f76 diff --combined owncloud-android-library index 652cd28b,f02dffb1..59fb6160 --- a/owncloud-android-library +++ b/owncloud-android-library @@@ -1,1 -1,1 +1,1 @@@ - Subproject commit 652cd28bb15672eaedfe8c1d9a46cf293c909b89 -Subproject commit f02dffb1d3c46305c70d246f696cde7b8c3b0971 ++Subproject commit 59fb61601de4dd8bfcab1afb619e016e1a7b904d diff --combined res/menu/file_actions_menu.xml index 4a295d53,63fadbbc..0a49e020 --- a/res/menu/file_actions_menu.xml +++ b/res/menu/file_actions_menu.xml @@@ -1,5 -1,4 +1,5 @@@ - All files - + On device Settings Logs Close @@@ -81,7 -82,7 +81,7 @@@ Created: Modified: Download - Refresh file + Synchronize File was renamed to %1$s during upload List Layout Share link @@@ -89,8 -90,7 +89,7 @@@ Yes No OK - Cancel download - Cancel upload + Cancel synchronization Cancel Save & Exit Error @@@ -205,11 -205,11 +204,11 @@@ Unfavorite Rename Remove - "Do you really want to remove %1$s?" + "Do you really want to remove %1$s?" "Do you really want to remove %1$s and its contents?" Local only Local only - From server + From server Remote & local "Removal succeeded" "Removal failed" @@@ -262,11 -262,9 +261,11 @@@ 389 KB 2012/05/18 12:23 PM 12:23:45 - - Upload pictures via WiFi only - Upload videos via WiFi only + + Upload pictures via wifi only + Upload when charging only + Upload videos via wifi only + Upload when charging only /InstantUpload File conflict Which files do you want to keep? If you select both versions, the local file will have a number added to its name. @@@ -278,7 -276,7 +277,7 @@@ This image cannot be shown %1$s could not be copied to %2$s local folder - Upload Path + Upload path Sorry, sharing is not enabled on your server. Please contact your administrator. @@@ -343,8 -341,8 +342,9 @@@ Instant Uploads Security - Upload Video Path + Upload video path + Download of %1$s folder could not be completed + Synchronization of %1$s folder could not be completed shared with you @@@ -366,21 -364,5 +366,21 @@@ %1$d files %1$d files, 1 folder %1$d files, %2$d folders + Switch to grid view + Switch to list view + Common + Cache size + Upload file to server and ... + Behaviour + Copy file + Move file + + do nothing + copy file to OC folder + move file to OC folder + delete origin file + Do you really want to remove selected items? + Do you really want to remove a folder and its content? + selected items diff --combined src/com/owncloud/android/datamodel/FileDataStorageManager.java index 46a4d854,540e83d1..7d014b03 --- a/src/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/com/owncloud/android/datamodel/FileDataStorageManager.java @@@ -24,8 -24,10 +24,10 @@@ import java.io.File import java.util.ArrayList; import java.util.Collection; import java.util.Collections; + import java.util.HashSet; import java.util.Iterator; import java.util.List; + import java.util.Set; import java.util.Vector; import android.accounts.Account; @@@ -88,18 -90,10 +90,10 @@@ public class FileDataStorageManager return mAccount; } - public void setContentResolver(ContentResolver cr) { - mContentResolver = cr; - } - public ContentResolver getContentResolver() { return mContentResolver; } - public void setContentProviderClient(ContentProviderClient cp) { - mContentProviderClient = cp; - } - public ContentProviderClient getContentProviderClient() { return mContentProviderClient; } @@@ -148,9 -142,10 +142,9 @@@ } - public Vector getFolderContent(OCFile f/*, boolean onlyOnDevice*/) { + public Vector getFolderContent(OCFile f, boolean onlyOnDevice) { if (f != null && f.isFolder() && f.getFileId() != -1) { - // TODO Enable when "On Device" is recovered ? - return getFolderContent(f.getFileId()/*, onlyOnDevice*/); + return getFolderContent(f.getFileId(), onlyOnDevice); } else { return new Vector(); @@@ -158,11 -153,12 +152,11 @@@ } - public Vector getFolderImages(OCFile folder/*, boolean onlyOnDevice*/) { + public Vector getFolderImages(OCFile folder, boolean onlyOnDevice) { Vector ret = new Vector(); if (folder != null) { // TODO better implementation, filtering in the access to database instead of here - // TODO Enable when "On Device" is recovered ? - Vector tmp = getFolderContent(folder/*, onlyOnDevice*/); + Vector tmp = getFolderContent(folder, onlyOnDevice); OCFile current = null; for (int i=0; i FileDataStorageManager.ROOT_PARENT_ID) { - // Log_OC.d(TAG, "Updating size of " + id); - // if (getContentResolver() != null) { - // getContentResolver().update(ProviderTableMeta.CONTENT_URI_DIR, - // new ContentValues(), - // won't be used, but cannot be null; crashes in KLP - // ProviderTableMeta._ID + "=?", - // new String[] { String.valueOf(id) }); - // } else { - // try { - // getContentProviderClient().update(ProviderTableMeta.CONTENT_URI_DIR, - // new ContentValues(), - // won't be used, but cannot be null; crashes in KLP - // ProviderTableMeta._ID + "=?", - // new String[] { String.valueOf(id) }); - // - // } catch (RemoteException e) { - // Log_OC.e( - // TAG, "Exception in update of folder size through compatibility patch " + e.getMessage()); - // } - // } - // } else { - // Log_OC.e(TAG, "not updating size for folder " + id); - // } - // } - - public boolean removeFile(OCFile file, boolean removeDBData, boolean removeLocalCopy) { boolean success = true; if (file != null) { @@@ -503,6 -459,7 +457,7 @@@ // maybe unnecessary, but should be checked TODO remove if unnecessary file.setStoragePath(null); saveFile(file); + saveConflict(file, null); } } } @@@ -549,7 -506,8 +504,7 @@@ File localFolder = new File(localFolderPath); if (localFolder.exists()) { // stage 1: remove the local files already registered in the files database - // TODO Enable when "On Device" is recovered ? - Vector files = getFolderContent(folder.getFileId()/*, false*/); + Vector files = getFolderContent(folder.getFileId(), false); if (files != null) { for (OCFile file : files) { if (file.isFolder()) { @@@ -784,7 -742,7 +739,7 @@@ } - private Vector getFolderContent(long parentId/*, boolean onlyOnDevice*/) { + private Vector getFolderContent(long parentId, boolean onlyOnDevice) { Vector ret = new Vector(); @@@ -811,9 -769,10 +766,9 @@@ if (c.moveToFirst()) { do { OCFile child = createFileInstance(c); - // TODO Enable when "On Device" is recovered ? - // if (child.isFolder() || !onlyOnDevice || onlyOnDevice && child.isDown()){ + if (child.isFolder() || !onlyOnDevice || onlyOnDevice && child.isDown()){ ret.add(child); - // } + } } while (c.moveToNext()); } @@@ -938,44 -897,12 +893,12 @@@ c.getColumnIndex(ProviderTableMeta.FILE_UPDATE_THUMBNAIL)) == 1 ? true : false); file.setDownloading(c.getInt( c.getColumnIndex(ProviderTableMeta.FILE_IS_DOWNLOADING)) == 1 ? true : false); - - } - return file; - } - - /** - * Returns if the file/folder is shared by link or not - * - * @param path Path of the file/folder - * @return - */ - public boolean isShareByLink(String path) { - Cursor c = getCursorForValue(ProviderTableMeta.FILE_STORAGE_PATH, path); - OCFile file = null; - if (c.moveToFirst()) { - file = createFileInstance(c); - } - c.close(); - return file.isShareByLink(); - } + file.setEtagInConflict(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_ETAG_IN_CONFLICT))); - /** - * Returns the public link of the file/folder - * - * @param path Path of the file/folder - * @return - */ - public String getPublicLink(String path) { - Cursor c = getCursorForValue(ProviderTableMeta.FILE_STORAGE_PATH, path); - OCFile file = null; - if (c.moveToFirst()) { - file = createFileInstance(c); } - c.close(); - return file.getPublicLink(); + return file; } - // Methods for Shares public boolean saveShare(OCShare share) { boolean overriden = false; @@@ -1306,6 -1233,7 +1229,7 @@@ ProviderTableMeta.FILE_IS_DOWNLOADING, file.isDownloading() ? 1 : 0 ); + cv.put(ProviderTableMeta.FILE_ETAG_IN_CONFLICT, file.getEtagInConflict()); boolean existsByPath = fileExists(file.getRemotePath()); if (existsByPath || fileExists(file.getFileId())) { @@@ -1473,7 -1401,8 +1397,7 @@@ + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?"; String [] whereArgs = new String[]{ "", mAccount.name }; - // TODO Enable when "On Device" is recovered ? - Vector files = getFolderContent(folder /*, false*/); + Vector files = getFolderContent(folder, false); for (OCFile file : files) { whereArgs[0] = file.getRemotePath(); @@@ -1485,47 -1414,9 +1409,9 @@@ } } return preparedOperations; - - /* - if (operations.size() > 0) { - try { - if (getContentResolver() != null) { - getContentResolver().applyBatch(MainApp.getAuthority(), operations); - - } else { - getContentProviderClient().applyBatch(operations); - } - - } catch (OperationApplicationException e) { - Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage()); - - } catch (RemoteException e) { - Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage()); - } - } - */ - - /* - if (getContentResolver() != null) { - - getContentResolver().delete(ProviderTableMeta.CONTENT_URI_SHARE, - where, - whereArgs); - } else { - try { - getContentProviderClient().delete( ProviderTableMeta.CONTENT_URI_SHARE, - where, - whereArgs); - - } catch (RemoteException e) { - Log_OC.e(TAG, "Exception deleting shares in a folder " + e.getMessage()); - } - } - */ - //} } - public void triggerMediaScan(String path) { + public static void triggerMediaScan(String path) { Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(Uri.fromFile(new File(path))); MainApp.getAppContext().sendBroadcast(intent); @@@ -1573,4 -1464,138 +1459,138 @@@ } + public void saveConflict(OCFile file, String etagInConflict) { + if (!file.isDown()) { + etagInConflict = null; + } + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_ETAG_IN_CONFLICT, etagInConflict); + int updated = 0; + if (getContentResolver() != null) { + updated = getContentResolver().update( + ProviderTableMeta.CONTENT_URI_FILE, + cv, + ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(file.getFileId())} + ); + } else { + try { + updated = getContentProviderClient().update( + ProviderTableMeta.CONTENT_URI_FILE, + cv, + ProviderTableMeta._ID + "=?", + new String[]{String.valueOf(file.getFileId())} + ); + } catch (RemoteException e) { + Log_OC.e(TAG, "Failed saving conflict in database " + e.getMessage()); + } + } + + Log_OC.d(TAG, "Number of files updated with CONFLICT: " + updated); + + if (updated > 0) { + if (etagInConflict != null) { + /// set conflict in all ancestor folders + + long parentId = file.getParentId(); + Set ancestorIds = new HashSet(); + while (parentId != FileDataStorageManager.ROOT_PARENT_ID) { + ancestorIds.add(Long.toString(parentId)); + parentId = getFileById(parentId).getParentId(); + } + + if (ancestorIds.size() > 0) { + StringBuffer whereBuffer = new StringBuffer(); + whereBuffer.append(ProviderTableMeta._ID).append(" IN ("); + for (int i = 0; i < ancestorIds.size() - 1; i++) { + whereBuffer.append("?,"); + } + whereBuffer.append("?"); + whereBuffer.append(")"); + + if (getContentResolver() != null) { + updated = getContentResolver().update( + ProviderTableMeta.CONTENT_URI_FILE, + cv, + whereBuffer.toString(), + ancestorIds.toArray(new String[]{}) + ); + } else { + try { + updated = getContentProviderClient().update( + ProviderTableMeta.CONTENT_URI_FILE, + cv, + whereBuffer.toString(), + ancestorIds.toArray(new String[]{}) + ); + } catch (RemoteException e) { + Log_OC.e(TAG, "Failed saving conflict in database " + e.getMessage()); + } + } + } // else file is ROOT folder, no parent to set in conflict + + } else { + /// update conflict in ancestor folders + // (not directly unset; maybe there are more conflicts below them) + String parentPath = file.getRemotePath(); + if (parentPath.endsWith(OCFile.PATH_SEPARATOR)) { + parentPath = parentPath.substring(0, parentPath.length() - 1); + } + parentPath = parentPath.substring(0, parentPath.lastIndexOf(OCFile.PATH_SEPARATOR) + 1); + + Log_OC.d(TAG, "checking parents to remove conflict; STARTING with " + parentPath); + while (parentPath.length() > 0) { + + String where = + ProviderTableMeta.FILE_ETAG_IN_CONFLICT + " IS NOT NULL AND " + + ProviderTableMeta.FILE_CONTENT_TYPE + " != 'DIR' AND " + + ProviderTableMeta.FILE_ACCOUNT_OWNER + " = ? AND " + + ProviderTableMeta.FILE_PATH + " LIKE ?"; + Cursor descendentsInConflict = getContentResolver().query( + ProviderTableMeta.CONTENT_URI_FILE, + new String[]{ProviderTableMeta._ID}, + where, + new String[]{mAccount.name, parentPath + "%"}, + null + ); + if (descendentsInConflict == null || descendentsInConflict.getCount() == 0) { + Log_OC.d(TAG, "NO MORE conflicts in " + parentPath); + if (getContentResolver() != null) { + updated = getContentResolver().update( + ProviderTableMeta.CONTENT_URI_FILE, + cv, + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + + ProviderTableMeta.FILE_PATH + "=?", + new String[]{mAccount.name, parentPath} + ); + } else { + try { + updated = getContentProviderClient().update( + ProviderTableMeta.CONTENT_URI_FILE, + cv, + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + + ProviderTableMeta.FILE_PATH + "=?" + , new String[]{mAccount.name, parentPath} + ); + } catch (RemoteException e) { + Log_OC.e(TAG, "Failed saving conflict in database " + e.getMessage()); + } + } + + } else { + Log_OC.d(TAG, "STILL " + descendentsInConflict.getCount() + " in " + parentPath); + } + + if (descendentsInConflict != null) { + descendentsInConflict.close(); + } + + parentPath = parentPath.substring(0, parentPath.length() - 1); // trim last / + parentPath = parentPath.substring(0, parentPath.lastIndexOf(OCFile.PATH_SEPARATOR) + 1); + Log_OC.d(TAG, "checking parents to remove conflict; NEXT " + parentPath); + } + } + } + + } } diff --combined src/com/owncloud/android/datamodel/OCFile.java index 1c2ec265,fcde054a..bc1d7e21 --- a/src/com/owncloud/android/datamodel/OCFile.java +++ b/src/com/owncloud/android/datamodel/OCFile.java @@@ -31,11 -31,13 +31,11 @@@ import java.io.File import third_parties.daveKoeller.AlphanumComparator; public class OCFile implements Parcelable, Comparable { - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public OCFile createFromParcel(Parcel source) { return new OCFile(source); } - @Override public OCFile[] newArray(int size) { return new OCFile[size]; } @@@ -72,8 -74,8 +72,10 @@@ private boolean mIsDownloading; + private boolean mShowGridView; + + private String mEtagInConflict; // Save file etag in the server, when there is a conflict. No conflict = null + /** * Create new {@link OCFile} with given path. @@@ -115,8 -117,9 +117,9 @@@ mPublicLink = source.readString(); mPermissions = source.readString(); mRemoteId = source.readString(); - mNeedsUpdateThumbnail = source.readInt() == 0; - mIsDownloading = source.readInt() == 0; + mNeedsUpdateThumbnail = source.readInt() == 1; + mIsDownloading = source.readInt() == 1; + mEtagInConflict = source.readString(); } @@@ -142,6 -145,7 +145,7 @@@ dest.writeString(mRemoteId); dest.writeInt(mNeedsUpdateThumbnail ? 1 : 0); dest.writeInt(mIsDownloading ? 1 : 0); + dest.writeString(mEtagInConflict); } /** @@@ -316,24 -320,6 +320,6 @@@ } /** - * Adds a file to this directory. If this file is not a directory, an - * exception gets thrown. - * - * @param file to add - * @throws IllegalStateException if you try to add a something and this is - * not a directory - */ - public void addFile(OCFile file) throws IllegalStateException { - if (isFolder()) { - file.mParentId = mId; - mNeedsUpdating = true; - return; - } - throw new IllegalStateException( - "This is not a directory where you can add stuff to!"); - } - - /** * Used internally. Reset all file properties */ private void resetData() { @@@ -357,6 -343,7 +343,7 @@@ mRemoteId = null; mNeedsUpdateThumbnail = false; mIsDownloading = false; + mEtagInConflict = null; } /** @@@ -498,10 -485,9 +485,9 @@@ } public void setEtag(String etag) { - this.mEtag = etag; + this.mEtag = (etag != null ? etag : ""); } - public boolean isShareByLink() { return mShareByLink; } @@@ -598,8 -584,11 +584,11 @@@ this.mIsDownloading = isDownloading; } - public boolean isSynchronizing() { - // TODO real implementation - return false; + public String getEtagInConflict() { + return mEtagInConflict; + } + + public void setEtagInConflict(String etagInConflict) { + mEtagInConflict = etagInConflict; } } diff --combined src/com/owncloud/android/files/FileMenuFilter.java index 1d5ca454,738ca7ef..065ab898 --- a/src/com/owncloud/android/files/FileMenuFilter.java +++ b/src/com/owncloud/android/files/FileMenuFilter.java @@@ -34,7 -34,6 +34,6 @@@ import com.owncloud.android.files.servi import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; - import com.owncloud.android.services.OperationsService; import com.owncloud.android.services.OperationsService.OperationsServiceBinder; import com.owncloud.android.ui.activity.ComponentsGetter; @@@ -106,21 -105,25 +105,25 @@@ public class FileMenuFilter * @param toHide List to save the options that must be shown in the menu. */ private void filter(List toShow, List toHide) { - boolean downloading = false; - boolean uploading = false; + boolean synchronizing = false; if (mComponentsGetter != null && mFile != null && mAccount != null) { - FileDownloaderBinder downloaderBinder = mComponentsGetter.getFileDownloaderBinder(); - downloading = (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)); OperationsServiceBinder opsBinder = mComponentsGetter.getOperationsServiceBinder(); - downloading |= (opsBinder != null && opsBinder.isSynchronizing(mAccount, mFile.getRemotePath())); FileUploaderBinder uploaderBinder = mComponentsGetter.getFileUploaderBinder(); - uploading = (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)); + FileDownloaderBinder downloaderBinder = mComponentsGetter.getFileDownloaderBinder(); + synchronizing = ( + // comparing local and remote + (opsBinder != null && opsBinder.isSynchronizing(mAccount, mFile.getRemotePath())) || + // downloading + (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) || + // uploading + (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)) + ); } /// decision is taken for each possible action on a file in the menu // DOWNLOAD - if (mFile == null || mFile.isDown() || downloading || uploading) { + if (mFile == null || mFile.isDown() || mFile.isFolder() || synchronizing) { toHide.add(R.id.action_download_file); } else { @@@ -128,7 -131,7 +131,7 @@@ } // RENAME - if (mFile == null || downloading || uploading) { + if (mFile == null || synchronizing) { toHide.add(R.id.action_rename_file); } else { @@@ -136,7 -139,7 +139,7 @@@ } // MOVE & COPY - if (mFile == null || downloading || uploading) { + if (mFile == null || synchronizing) { toHide.add(R.id.action_move); toHide.add(R.id.action_copy); } else { @@@ -145,7 -148,7 +148,7 @@@ } // REMOVE - if (mFile == null || downloading || uploading) { + if (mFile == null || synchronizing) { toHide.add(R.id.action_remove_file); } else { @@@ -153,31 -156,25 +156,25 @@@ } // OPEN WITH (different to preview!) - if (mFile == null || mFile.isFolder() || !mFile.isDown() || downloading || uploading) { + if (mFile == null || mFile.isFolder() || !mFile.isDown() || synchronizing) { toHide.add(R.id.action_open_file_with); } else { toShow.add(R.id.action_open_file_with); } + // CANCEL SYNCHRONIZATION + if (mFile == null || !synchronizing) { + toHide.add(R.id.action_cancel_sync); - // CANCEL DOWNLOAD - if (mFile == null || !downloading) { - toHide.add(R.id.action_cancel_download); - } else { - toShow.add(R.id.action_cancel_download); - } - - // CANCEL UPLOAD - if (mFile == null || !uploading || mFile.isFolder()) { - toHide.add(R.id.action_cancel_upload); } else { - toShow.add(R.id.action_cancel_upload); + toShow.add(R.id.action_cancel_sync); } - // SYNC FILE CONTENTS - if (mFile == null || mFile.isFolder() || !mFile.isDown() || downloading || uploading) { + // SYNC CONTENTS (BOTH FILE AND FOLDER) + if (mFile == null || (!mFile.isFolder() && !mFile.isDown()) || synchronizing) { toHide.add(R.id.action_sync_file); + } else { toShow.add(R.id.action_sync_file); } @@@ -210,25 -207,26 +207,25 @@@ // SEND boolean sendAllowed = (mContext != null && mContext.getString(R.string.send_files_to_other_apps).equalsIgnoreCase("on")); - if (mFile == null || !sendAllowed || mFile.isFolder() || uploading || downloading) { + if (mFile == null || !sendAllowed || mFile.isFolder() || synchronizing) { toHide.add(R.id.action_send_file); } else { toShow.add(R.id.action_send_file); } // FAVORITES - if (mFile == null || downloading || uploading || mFile.isFolder() || mFile.isFavorite()) { + if (mFile == null || synchronizing || mFile.isFolder() || mFile.isFavorite()) { toHide.add(R.id.action_favorite_file); } else { toShow.add(R.id.action_favorite_file); } // UNFAVORITES - if (mFile == null || downloading || uploading || mFile.isFolder() || !mFile.isFavorite()) { + if (mFile == null || synchronizing || mFile.isFolder() || !mFile.isFavorite()) { toHide.add(R.id.action_unfavorite_file); } else { toShow.add(R.id.action_unfavorite_file); } - } } diff --combined src/com/owncloud/android/files/FileOperationsHelper.java index 51d447e1,cf0f50f0..e799e176 --- a/src/com/owncloud/android/files/FileOperationsHelper.java +++ b/src/com/owncloud/android/files/FileOperationsHelper.java @@@ -27,17 -27,14 +27,17 @@@ import android.content.Context import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.graphics.Bitmap; import android.net.Uri; import android.support.v4.app.DialogFragment; import android.webkit.MimeTypeMap; import android.widget.Toast; +import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; import com.owncloud.android.lib.common.network.WebdavUtils; @@@ -46,21 -43,12 +46,21 @@@ import com.owncloud.android.lib.resourc import com.owncloud.android.services.OperationsService; import com.owncloud.android.services.observer.FileObserverService; import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.adapter.DiskLruImageCacheFileProvider; import com.owncloud.android.ui.dialog.ShareLinkToDialog; import org.apache.http.protocol.HTTP; import java.util.List; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +import java.util.ArrayList; + /** * */ @@@ -248,32 -236,13 +248,37 @@@ public class FileOperationsHelper } } + public void sendCachedImage(OCFile file) { + if (file != null) { + Intent sendIntent = new Intent(android.content.Intent.ACTION_SEND); + // set MimeType + sendIntent.setType(file.getMimetype()); +// sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" + DiskLruImageCacheFileProvider.AUTHORITY + "/#" + file.getRemoteId() + "#" + file.getFileName())); + sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" + DiskLruImageCacheFileProvider.AUTHORITY + file.getRemotePath())); + sendIntent.putExtra(Intent.ACTION_SEND, true); // Send Action + + // Show dialog, without the own app + String[] packagesToExclude = new String[] { mFileActivity.getPackageName() }; + DialogFragment chooserDialog = ShareLinkToDialog.newInstance(sendIntent, packagesToExclude, file); + chooserDialog.show(mFileActivity.getSupportFragmentManager(), FTAG_CHOOSER_DIALOG); + } else { + Log_OC.wtf(TAG, "Trying to send a NULL OCFile"); + } + } + + public void syncFiles(ArrayList files) { + for (OCFile file: files) { + syncFile(file); + } + } + + /** + * Request the synchronization of a file or folder with the OC server, including its contents. + * + * @param file The file or folder to synchronize + */ public void syncFile(OCFile file) { - if (!file.isFolder()) { + if (!file.isFolder()){ Intent intent = new Intent(mFileActivity, OperationsService.class); intent.setAction(OperationsService.ACTION_SYNC_FILE); intent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount()); @@@ -281,22 -250,17 +286,23 @@@ intent.putExtra(OperationsService.EXTRA_SYNC_FILE_CONTENTS, true); mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(intent); mFileActivity.showLoadingDialog(); - + } else { Intent intent = new Intent(mFileActivity, OperationsService.class); intent.setAction(OperationsService.ACTION_SYNC_FOLDER); intent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount()); intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); mFileActivity.startService(intent); + } } + public void toggleFavorites(ArrayList files, boolean isFavorite){ + for (OCFile file: files) { + toggleFavorite(file, isFavorite); + } + } + public void toggleFavorite(OCFile file, boolean isFavorite) { file.setFavorite(isFavorite); mFileActivity.getStorageManager().saveFile(file); @@@ -368,19 -332,11 +374,11 @@@ // for both files and folders FileDownloaderBinder downloaderBinder = mFileActivity.getFileDownloaderBinder(); - FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder(); if (downloaderBinder != null && downloaderBinder.isDownloading(account, file)) { downloaderBinder.cancel(account, file); - - // TODO - review why is this here, and solve in a better way - // Remove etag for parent, if file is a favorite - if (file.isFavorite()) { - OCFile parent = mFileActivity.getStorageManager().getFileById(file.getParentId()); - parent.setEtag(""); - mFileActivity.getStorageManager().saveFile(parent); - } - - } else if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) { + } + FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder(); + if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) { uploaderBinder.cancel(account, file); } } @@@ -400,8 -356,7 +398,8 @@@ service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount()); mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service); - mFileActivity.showLoadingDialog(); + // TODO Tobi loading dialog? + // mFileActivity.showLoadingDialog(); } /** diff --combined src/com/owncloud/android/files/services/FileUploader.java index 6aca4937,7c953b84..df4dccda --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@@ -21,18 -21,14 +21,14 @@@ package com.owncloud.android.files.services; import java.io.File; - import java.io.IOException; import java.util.AbstractList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Vector; - import java.util.concurrent.ConcurrentHashMap; - import java.util.concurrent.ConcurrentMap; import android.accounts.Account; import android.accounts.AccountManager; - import android.accounts.AccountsException; import android.accounts.OnAccountsUpdateListener; import android.app.NotificationManager; import android.app.PendingIntent; @@@ -46,6 -42,7 +42,7 @@@ import android.os.Looper import android.os.Message; import android.os.Process; import android.support.v4.app.NotificationCompat; + import android.util.Pair; import android.webkit.MimeTypeMap; import com.owncloud.android.R; @@@ -86,6 -83,7 +83,7 @@@ public class FileUploader extends Servi 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_OLD_FILE_PATH = "OLD_FILE_PATH"; + public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO"; public static final String ACCOUNT_NAME = "ACCOUNT_NAME"; public static final String KEY_FILE = "FILE"; @@@ -103,7 -101,6 +101,7 @@@ public static final int LOCAL_BEHAVIOUR_COPY = 0; public static final int LOCAL_BEHAVIOUR_MOVE = 1; public static final int LOCAL_BEHAVIOUR_FORGET = 2; + public static final int LOCAL_BEHAVIOUR_REMOVE = 3; public static final int UPLOAD_SINGLE_FILE = 0; public static final int UPLOAD_MULTIPLE_FILES = 1; @@@ -114,11 -111,10 +112,10 @@@ private ServiceHandler mServiceHandler; private IBinder mBinder; private OwnCloudClient mUploadClient = null; - private Account mLastAccount = null; + private Account mCurrentAccount = null; private FileDataStorageManager mStorageManager; - private ConcurrentMap mPendingUploads = - new ConcurrentHashMap(); + private IndexedForest mPendingUploads = new IndexedForest(); private UploadFileOperation mCurrentUpload = null; private NotificationManager mNotificationManager; @@@ -134,26 -130,14 +131,14 @@@ } /** - * Builds a key for mPendingUploads from the account and file to upload - * - * @param account Account where the file to upload is stored - * @param file File to upload - */ - private String buildRemoteName(Account account, OCFile file) { - return account.name + file.getRemotePath(); - } - - private String buildRemoteName(Account account, String remotePath) { - return account.name + remotePath; - } - - /** * Checks if an ownCloud server version should support chunked uploads. * * @param version OwnCloud version instance corresponding to an ownCloud * server. * @return 'True' if the ownCloud server with version supports chunked * uploads. + * + * TODO - move to OCClient */ private static boolean chunkedUploadIsSupported(OwnCloudVersion version) { return (version != null && version.compareTo(OwnCloudVersion.owncloud_v4_5) >= 0); @@@ -283,7 -267,7 +268,7 @@@ files = new OCFile[localPaths.length]; for (int i = 0; i < localPaths.length; i++) { files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], - ((mimeTypes != null) ? mimeTypes[i] : null), storageManager); + ((mimeTypes != null) ? mimeTypes[i] : null)); if (files[i] == null) { // TODO @andomaex add failure Notification return Service.START_NOT_STICKY; @@@ -299,18 -283,23 +284,23 @@@ UploadFileOperation newUpload = null; try { for (int i = 0; i < files.length; i++) { - uploadKey = buildRemoteName(account, files[i].getRemotePath()); - newUpload = new UploadFileOperation(account, files[i], chunked, isInstant, + newUpload = new UploadFileOperation( + account, + files[i], + chunked, + isInstant, forceOverwrite, localAction, - getApplicationContext()); + getApplicationContext() + ); if (isInstant) { newUpload.setRemoteFolderToBeCreated(); } - // Grants that the file only upload once time - mPendingUploads.putIfAbsent(uploadKey, newUpload); - newUpload.addDatatransferProgressListener(this); - newUpload.addDatatransferProgressListener((FileUploaderBinder)mBinder); + newUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder); + Pair putResult = mPendingUploads.putIfAbsent( + account, files[i].getRemotePath(), newUpload + ); + uploadKey = putResult.first; requestedUploads.add(uploadKey); } @@@ -334,7 -323,6 +324,6 @@@ msg.obj = requestedUploads; mServiceHandler.sendMessage(msg); } - Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); return Service.START_NOT_STICKY; } @@@ -387,23 -375,27 +376,27 @@@ /** * Cancels a pending or current upload of a remote file. * - * @param account Owncloud account where the remote file will be stored. - * @param file A file in the queue of pending uploads + * @param account ownCloud account where the remote file will be stored. + * @param file A file in the queue of pending uploads */ public void cancel(Account account, OCFile file) { - UploadFileOperation upload; - synchronized (mPendingUploads) { - upload = mPendingUploads.remove(buildRemoteName(account, file)); - } + Pair removeResult = mPendingUploads.remove(account, file.getRemotePath()); + UploadFileOperation upload = removeResult.first; if (upload != null) { upload.cancel(); + } else { + if (mCurrentUpload != null && mCurrentAccount != null && + mCurrentUpload.getRemotePath().startsWith(file.getRemotePath()) && + account.name.equals(mCurrentAccount.name)) { + mCurrentUpload.cancel(); + } } } /** - * Cancels a pending or current upload for an account + * Cancels all the uploads for an account * - * @param account Owncloud accountName where the remote file will be stored. + * @param account ownCloud account. */ public void cancel(Account account) { Log_OC.d(TAG, "Account= " + account.name); @@@ -415,13 -407,14 +408,14 @@@ } } // Cancel pending uploads - cancelUploadForAccount(account.name); + cancelUploadsForAccount(account); } public void clearListeners() { mBoundListeners.clear(); } + /** * Returns True when the file described by 'file' is being uploaded to * the ownCloud account 'account' or waiting for it @@@ -433,22 -426,8 +427,8 @@@ * @param file A file that could be in the queue of pending uploads */ public boolean isUploading(Account account, OCFile file) { - if (account == null || file == null) - return false; - String targetKey = buildRemoteName(account, file); - synchronized (mPendingUploads) { - if (file.isFolder()) { - // this can be slow if there are many uploads :( - Iterator it = mPendingUploads.keySet().iterator(); - boolean found = false; - while (it.hasNext() && !found) { - found = it.next().startsWith(targetKey); - } - return found; - } else { - return (mPendingUploads.containsKey(targetKey)); - } - } + if (account == null || file == null) return false; + return (mPendingUploads.contains(account, file.getRemotePath())); } @@@ -497,15 -476,19 +477,19 @@@ } /** - * Review uploads and cancel it if its account doesn't exist + * Builds a key for the map of listeners. + * + * TODO remove and replace key with file.getFileId() after changing current policy (upload file, then + * add to local database) to better policy (add to local database, then upload) + * + * @param account ownCloud account where the file to upload belongs. + * @param file File to upload + * @return Key */ - public void checkAccountOfCurrentUpload() { - if (mCurrentUpload != null && - !AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) { - mCurrentUpload.cancel(); - } - // The rest of uploads are cancelled when they try to start + private String buildRemoteName(Account account, OCFile file) { + return account.name + file.getRemotePath(); } + } /** @@@ -545,17 -528,13 +529,13 @@@ /** * Core upload method: sends the file(s) to upload * - * @param uploadKey Key to access the upload to perform, contained in - * mPendingUploads + * @param uploadKey Key to access the upload to perform, contained in mPendingUploads */ public void uploadFile(String uploadKey) { - synchronized (mPendingUploads) { - mCurrentUpload = mPendingUploads.get(uploadKey); - } + mCurrentUpload = mPendingUploads.get(uploadKey); if (mCurrentUpload != null) { - // Detect if the account exists if (AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) { Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().name + " exists"); @@@ -565,16 -544,20 +545,20 @@@ RemoteOperationResult uploadResult = null, grantResult; try { - /// prepare client object to send requests to the ownCloud server - if (mUploadClient == null || - !mLastAccount.equals(mCurrentUpload.getAccount())) { - mLastAccount = mCurrentUpload.getAccount(); - mStorageManager = - new FileDataStorageManager(mLastAccount, getContentResolver()); - OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, this); - mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton(). - getClientFor(ocAccount, this); - } + /// prepare client object to send the request to the ownCloud server + if (mCurrentAccount == null || !mCurrentAccount.equals(mCurrentUpload.getAccount())) { + mCurrentAccount = mCurrentUpload.getAccount(); + mStorageManager = new FileDataStorageManager( + mCurrentAccount, + getContentResolver() + ); + } // else, reuse storage manager from previous operation + + // always get client from client manager, to get fresh credentials in case of update + OwnCloudAccount ocAccount = new OwnCloudAccount(mCurrentAccount, this); + mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton(). + getClientFor(ocAccount, this); + /// check the existence of the parent folder for the file to upload String remoteParentPath = new File(mCurrentUpload.getRemotePath()).getParent(); @@@ -589,43 -572,44 +573,44 @@@ uploadResult = mCurrentUpload.execute(mUploadClient); if (uploadResult.isSuccess()) { saveUploadedFile(); + + } else if (uploadResult.getCode() == ResultCode.SYNC_CONFLICT) { + mStorageManager.saveConflict(mCurrentUpload.getFile(), + mCurrentUpload.getFile().getEtagInConflict()); } } else { uploadResult = grantResult; } - } catch (AccountsException e) { - Log_OC.e(TAG, "Error while trying to get autorization for " + - mLastAccount.name, e); - uploadResult = new RemoteOperationResult(e); - - } catch (IOException e) { - Log_OC.e(TAG, "Error while trying to get autorization for " + - mLastAccount.name, e); + } catch (Exception e) { + Log_OC.e(TAG, "Error uploading", e); uploadResult = new RemoteOperationResult(e); } finally { - synchronized (mPendingUploads) { - mPendingUploads.remove(uploadKey); - Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map."); - } - if (uploadResult != null && uploadResult.isException()) { - // enforce the creation of a new client object for next uploads; - // this grant that a new socket will be created in the future if - // the current exception is due to an abrupt lose of network connection - mUploadClient = null; + Pair removeResult; + if (mCurrentUpload.wasRenamed()) { + removeResult = mPendingUploads.removePayload( + mCurrentAccount, + mCurrentUpload.getOldFile().getRemotePath() + ); + } else { + removeResult = mPendingUploads.removePayload( + mCurrentAccount, + mCurrentUpload.getRemotePath() + ); } - } - /// notify result - notifyUploadResult(uploadResult, mCurrentUpload); - sendFinalBroadcast(mCurrentUpload, uploadResult); + /// notify result + notifyUploadResult(mCurrentUpload, uploadResult); + + sendBroadcastUploadFinished(mCurrentUpload, uploadResult, removeResult.second); + } } else { // Cancel the transfer Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().toString() + " doesn't exist"); - cancelUploadForAccount(mCurrentUpload.getAccount().name); + cancelUploadsForAccount(mCurrentUpload.getAccount()); } } @@@ -692,7 -676,7 +677,7 @@@ * synchronized with the server, specially the modification time and Etag * (where available) * - * TODO refactor this ugly thing + * TODO move into UploadFileOperation */ private void saveUploadedFile() { OCFile file = mCurrentUpload.getFile(); @@@ -710,6 -694,8 +695,8 @@@ if (result.isSuccess()) { updateOCFile(file, (RemoteFile) result.getData().get(0)); file.setLastSyncDateForProperties(syncDate); + } else { + Log_OC.e(TAG, "Error reading properties of file after successful upload; this is gonna hurt..."); } // / maybe this would be better as part of UploadFileOperation... or @@@ -719,6 -705,7 +706,7 @@@ if (oldFile.fileExists()) { oldFile.setStoragePath(null); mStorageManager.saveFile(oldFile); + mStorageManager.saveConflict(oldFile, null); } // else: it was just an automatic renaming due to a name // coincidence; nothing else is needed, the storagePath is right @@@ -726,7 -713,10 +714,10 @@@ } file.setNeedsUpdateThumbnail(true); mStorageManager.saveFile(file); + mStorageManager.saveConflict(file, null); + mStorageManager.triggerMediaScan(file.getStoragePath()); + } private void updateOCFile(OCFile file, RemoteFile remoteFile) { @@@ -735,12 -725,11 +726,11 @@@ file.setMimetype(remoteFile.getMimeType()); file.setModificationTimestamp(remoteFile.getModifiedTimestamp()); file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp()); - // file.setEtag(remoteFile.getEtag()); // TODO Etag, where available + file.setEtag(remoteFile.getEtag()); file.setRemoteId(remoteFile.getRemoteId()); } - private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, - FileDataStorageManager storageManager) { + private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) { // MIME type if (mimeType == null || mimeType.length() <= 0) { @@@ -831,11 -820,11 +821,11 @@@ /** * Updates the status notification with the result of an upload operation. * - * @param uploadResult Result of the upload operation. - * @param upload Finished upload operation + * @param uploadResult Result of the upload operation. + * @param upload Finished upload operation */ - private void notifyUploadResult( - RemoteOperationResult uploadResult, UploadFileOperation upload) { + private void notifyUploadResult(UploadFileOperation upload, + RemoteOperationResult uploadResult) { Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode()); // / cancelled operation or success -> silent removal of progress notification mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker); @@@ -942,10 -931,15 +932,15 @@@ * Sends a broadcast in order to the interested activities can update their * view * - * @param upload Finished upload operation - * @param uploadResult Result of the upload operation + * @param upload Finished upload operation + * @param uploadResult Result of the upload operation + * @param unlinkedFromRemotePath Path in the uploads tree where the upload was unlinked from */ - private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) { + private void sendBroadcastUploadFinished( + UploadFileOperation upload, + RemoteOperationResult uploadResult, + String unlinkedFromRemotePath) { + Intent end = new Intent(getUploadFinishMessage()); end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote // path, after @@@ -958,6 -952,10 +953,10 @@@ end.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath()); end.putExtra(ACCOUNT_NAME, upload.getAccount().name); end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess()); + if (unlinkedFromRemotePath != null) { + end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath); + } + sendStickyBroadcast(end); } @@@ -967,6 -965,8 +966,8 @@@ * @param localPath Full path to a file in the local file system. * @param mimeType MIME type of the file. * @return true if is needed to add the pdf file extension to the file + * + * TODO - move to OCFile or Utils class */ private boolean isPdfFileFromContentProviderWithoutExtension(String localPath, String mimeType) { @@@ -977,20 -977,11 +978,11 @@@ /** * Remove uploads of an account - * @param accountName Name of an OC account + * + * @param account Downloads account to remove */ - private void cancelUploadForAccount(String accountName){ - // this can be slow if there are many uploads :( - Iterator it = mPendingUploads.keySet().iterator(); - Log_OC.d(TAG, "Number of pending updloads= " + mPendingUploads.size()); - while (it.hasNext()) { - String key = it.next(); - Log_OC.d(TAG, "mPendingUploads CANCELLED " + key); - if (key.startsWith(accountName)) { - synchronized (mPendingUploads) { - mPendingUploads.remove(key); - } - } - } + private void cancelUploadsForAccount(Account account){ + // Cancel pending uploads + mPendingUploads.remove(account); } } diff --combined src/com/owncloud/android/operations/RefreshFolderOperation.java index dade4d97,61b13fc8..ddd843de --- a/src/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/src/com/owncloud/android/operations/RefreshFolderOperation.java @@@ -20,26 -20,17 +20,17 @@@ package com.owncloud.android.operations; - import java.io.File; - import java.io.FileInputStream; - import java.io.FileOutputStream; - import java.io.IOException; - import java.io.InputStream; - import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Vector; - import org.apache.http.HttpStatus; import android.accounts.Account; import android.content.Context; import android.content.Intent; import android.util.Log; - //import android.support.v4.content.LocalBroadcastManager; - import com.owncloud.android.MainApp; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; @@@ -50,7 -41,6 +41,6 @@@ import com.owncloud.android.lib.common. import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.shares.GetRemoteSharesForFileOperation; - import com.owncloud.android.lib.resources.files.FileUtils; import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation; import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation; import com.owncloud.android.lib.resources.files.RemoteFile; @@@ -120,7 -110,10 +110,10 @@@ public class RefreshFolderOperation ext /** 'True' means that Etag will be ignored */ private boolean mIgnoreETag; - + private List mFilesToSyncContents; + // this will be used for every file when 'folder synchronization' replaces 'folder download' + + /** * Creates a new instance of {@link RefreshFolderOperation}. * @@@ -154,6 -147,7 +147,7 @@@ mForgottenLocalFiles = new HashMap(); mRemoteFolderChanged = false; mIgnoreETag = ignoreETag; + mFilesToSyncContents = new Vector(); } @@@ -191,7 -185,7 +185,7 @@@ mConflictsFound = 0; mForgottenLocalFiles.clear(); - if (FileUtils.PATH_SEPARATOR.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount) { + if (OCFile.ROOT_PATH.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount) { updateOCVersion(client); } @@@ -201,8 -195,14 +195,14 @@@ if (mRemoteFolderChanged) { result = fetchAndSyncRemoteFolder(client); } else { + fetchFavoritesToSyncFromLocalData(); - mChildren = mStorageManager.getFolderContent(mLocalFolder/*, false*/); + mChildren = mStorageManager.getFolderContent(mLocalFolder, false); } + + if (result.isSuccess()) { + // request for the synchronization of KEPT-IN-SYNC file contents + startContentSynchronizations(mFilesToSyncContents, client); + } } if (!mSyncFullAccount) { @@@ -238,9 -238,8 +238,8 @@@ private RemoteOperationResult checkForChanges(OwnCloudClient client) { mRemoteFolderChanged = true; RemoteOperationResult result = null; - String remotePath = null; + String remotePath = mLocalFolder.getRemotePath(); - remotePath = mLocalFolder.getRemotePath(); Log_OC.d(TAG, "Checking changes in " + mAccount.name + remotePath); // remote request @@@ -263,7 -262,7 +262,7 @@@ result = new RemoteOperationResult(ResultCode.OK); - Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " + + Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " + (mRemoteFolderChanged ? "changed" : "not changed")); } else { @@@ -336,72 -335,74 +335,73 @@@ mLocalFolder = mStorageManager.getFileByPath(mLocalFolder.getRemotePath()); // parse data from remote folder - OCFile remoteFolder = fillOCFile((RemoteFile)folderAndFiles.get(0)); + OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) folderAndFiles.get(0)); remoteFolder.setParentId(mLocalFolder.getParentId()); remoteFolder.setFileId(mLocalFolder.getFileId()); - Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() + Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() + " changed - starting update of local data "); List updatedFiles = new Vector(folderAndFiles.size() - 1); - List filesToSyncContents = new Vector(); + mFilesToSyncContents.clear(); // get current data about local contents of the folder to synchronize - // TODO Enable when "On Device" is recovered ? - List localFiles = mStorageManager.getFolderContent(mLocalFolder/*, false*/); + List localFiles = mStorageManager.getFolderContent(mLocalFolder, false); Map localFilesMap = new HashMap(localFiles.size()); for (OCFile file : localFiles) { localFilesMap.put(file.getRemotePath(), file); } // loop to update every child - OCFile remoteFile = null, localFile = null; + OCFile remoteFile = null, localFile = null, updatedFile = null; + RemoteFile r; for (int i=1; i 0){ - out.write(buf, 0, len); - } - file.setStoragePath(expectedPath); - - } catch (Exception e) { - Log_OC.e(TAG, "Exception while copying foreign file " + expectedPath, e); - mForgottenLocalFiles.put(file.getRemotePath(), storagePath); - file.setStoragePath(null); - - } finally { - try { - if (in != null) in.close(); - } catch (Exception e) { - Log_OC.d(TAG, "Weird exception while closing input stream for " - + storagePath + " (ignoring)", e); - } - try { - if (out != null) out.close(); - } catch (Exception e) { - Log_OC.d(TAG, "Weird exception while closing output stream for " - + expectedPath + " (ignoring)", e); - } - } - } - } - } - - private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) { RemoteOperationResult result = null; @@@ -571,24 -478,6 +477,6 @@@ /** - * Scans the default location for saving local copies of files searching for - * a 'lost' file with the same full name as the {@link OCFile} received as - * parameter. - * - * @param file File to associate a possible 'lost' local file. - */ - private void searchForLocalFileInDefaultPath(OCFile file) { - if (file.getStoragePath() == null && !file.isFolder()) { - File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); - if (f.exists()) { - file.setStoragePath(f.getAbsolutePath()); - file.setLastSyncDateForData(f.lastModified()); - } - } - } - - - /** * Sends a message to any application component interested in the progress * of the synchronization. * @@@ -612,8 -501,20 +500,20 @@@ } - public boolean getRemoteFolderChanged() { - return mRemoteFolderChanged; + private void fetchFavoritesToSyncFromLocalData() { - List children = mStorageManager.getFolderContent(mLocalFolder); ++ List children = mStorageManager.getFolderContent(mLocalFolder, false); + for (OCFile child : children) { + if (!child.isFolder() && child.isFavorite()) { + SynchronizeFileOperation operation = new SynchronizeFileOperation( + child, + child, // cheating with the remote file to get an update to server; to refactor + mAccount, + true, + mContext + ); + mFilesToSyncContents.add(operation); + } + } } } diff --combined src/com/owncloud/android/operations/SynchronizeFolderOperation.java index d2d6c373,b8da4a1f..e4f96620 --- a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java @@@ -60,7 -60,8 +60,8 @@@ import java.util.concurrent.atomic.Atom * Fetches the list and properties of the files contained in the given folder, including their * properties, and updates the local database with them. * - * Does NOT enter in the child folders to synchronize their contents also. + * Does NOT enter in the child folders to synchronize their contents also, BUT requests for a new operation instance + * doing so. */ public class SynchronizeFolderOperation extends SyncOperation { @@@ -96,10 -97,7 +97,7 @@@ private List mFilesForDirectDownload; // to avoid extra PROPFINDs when there was no change in the folder - private List mFilesToSyncContentsWithoutUpload; - // this will go out when 'folder synchronization' replaces 'folder download'; step by step - - private List mFavouriteFilesToSyncContents; + private List mFilesToSyncContents; // this will be used for every file when 'folder synchronization' replaces 'folder download' private final AtomicBoolean mCancellationRequested; @@@ -120,8 -118,7 +118,7 @@@ mContext = context; mRemoteFolderChanged = false; mFilesForDirectDownload = new Vector(); - mFilesToSyncContentsWithoutUpload = new Vector(); - mFavouriteFilesToSyncContents = new Vector(); + mFilesToSyncContents = new Vector(); mCancellationRequested = new AtomicBoolean(false); } @@@ -281,7 -278,7 +278,7 @@@ FileDataStorageManager storageManager = getStorageManager(); // parse data from remote folder - OCFile remoteFolder = fillOCFile((RemoteFile)folderAndFiles.get(0)); + OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) folderAndFiles.get(0)); remoteFolder.setParentId(mLocalFolder.getParentId()); remoteFolder.setFileId(mLocalFolder.getFileId()); @@@ -290,100 -287,92 +287,91 @@@ List updatedFiles = new Vector(folderAndFiles.size() - 1); mFilesForDirectDownload.clear(); - mFilesToSyncContentsWithoutUpload.clear(); - mFavouriteFilesToSyncContents.clear(); + mFilesToSyncContents.clear(); if (mCancellationRequested.get()) { throw new OperationCancelledException(); } // get current data about local contents of the folder to synchronize - // TODO Enable when "On Device" is recovered ? - List localFiles = storageManager.getFolderContent(mLocalFolder/*, false*/); + List localFiles = storageManager.getFolderContent(mLocalFolder, false); Map localFilesMap = new HashMap(localFiles.size()); for (OCFile file : localFiles) { localFilesMap.put(file.getRemotePath(), file); } // loop to synchronize every child - OCFile remoteFile = null, localFile = null; + OCFile remoteFile = null, localFile = null, updatedFile = null; + RemoteFile r; for (int i=1; i children = getStorageManager().getFolderContent(mLocalFolder/*, false*/); + List children = getStorageManager().getFolderContent(mLocalFolder, false); for (OCFile child : children) { /// classify file to sync/download contents later if (child.isFolder()) { @@@ -406,10 -396,23 +394,23 @@@ } } else { - /// prepare limited synchronization for regular files + /// synchronization for regular files if (!child.isDown()) { mFilesForDirectDownload.add(child); + + } else { + /// this should result in direct upload of files that were locally modified + SynchronizeFileOperation operation = new SynchronizeFileOperation( + child, + (child.getEtagInConflict() != null ? child : null), + mAccount, + true, + mContext + ); + mFilesToSyncContents.add(operation); + } + } } } @@@ -417,8 -420,7 +418,7 @@@ private void syncContents(OwnCloudClient client) throws OperationCancelledException { startDirectDownloads(); - startContentSynchronizations(mFilesToSyncContentsWithoutUpload, client); - startContentSynchronizations(mFavouriteFilesToSyncContents, client); + startContentSynchronizations(mFilesToSyncContents, client); } @@@ -477,26 -479,6 +477,6 @@@ /** - * Creates and populates a new {@link com.owncloud.android.datamodel.OCFile} - * object with the data read from the server. - * - * @param remote remote file read from the server (remote file or folder). - * @return New OCFile instance representing the remote resource described by we. - */ - private OCFile fillOCFile(RemoteFile remote) { - OCFile file = new OCFile(remote.getRemotePath()); - file.setCreationTimestamp(remote.getCreationTimestamp()); - file.setFileLength(remote.getLength()); - file.setMimetype(remote.getMimeType()); - file.setModificationTimestamp(remote.getModifiedTimestamp()); - file.setEtag(remote.getEtag()); - file.setPermissions(remote.getPermissions()); - file.setRemoteId(remote.getRemoteId()); - return file; - } - - - /** * Scans the default location for saving local copies of files searching for * a 'lost' file with the same full name as the {@link com.owncloud.android.datamodel.OCFile} * received as parameter. diff --combined src/com/owncloud/android/operations/UploadFileOperation.java index 6be2e0ae,ee9f7c88..e7b5ba3d --- a/src/com/owncloud/android/operations/UploadFileOperation.java +++ b/src/com/owncloud/android/operations/UploadFileOperation.java @@@ -31,7 -31,7 +31,7 @@@ import java.util.Iterator import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; - import org.apache.commons.httpclient.methods.PutMethod; + import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.RequestEntity; import android.accounts.Account; @@@ -39,7 -39,6 +39,7 @@@ import android.content.Context import android.net.Uri; import com.owncloud.android.MainApp; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.lib.common.OwnCloudClient; @@@ -76,7 -75,6 +76,6 @@@ public class UploadFileOperation extend private boolean mWasRenamed = false; private String mOriginalFileName = null; private String mOriginalStoragePath = null; - PutMethod mPutMethod = null; private Set mDataTransferListeners = new HashSet(); private AtomicBoolean mCancellationRequested = new AtomicBoolean(false); private Context mContext; @@@ -313,65 -311,61 +312,63 @@@ (new File(mFile.getStoragePath())).length() > ChunkedUploadRemoteFileOperation.CHUNK_SIZE ) { mUploadOperation = new ChunkedUploadRemoteFileOperation(mFile.getStoragePath(), - mFile.getRemotePath(), mFile.getMimetype()); + mFile.getRemotePath(), mFile.getMimetype(), mFile.getEtagInConflict()); } else { mUploadOperation = new UploadRemoteFileOperation(mFile.getStoragePath(), - mFile.getRemotePath(), mFile.getMimetype()); + mFile.getRemotePath(), mFile.getMimetype(), mFile.getEtagInConflict()); } Iterator listener = mDataTransferListeners.iterator(); while (listener.hasNext()) { mUploadOperation.addDatatransferProgressListener(listener.next()); } - if (!mCancellationRequested.get()) { - result = mUploadOperation.execute(client); - - /// move local temporal file or original file to its corresponding - // location in the ownCloud local folder - if (result.isSuccess()) { - if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_FORGET) { - mFile.setStoragePath(null); - } else if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_REMOVE){ - mFile.setStoragePath(null); - originalFile.delete(); - } else { - mFile.setStoragePath(expectedPath); - File fileToMove = null; - if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY - // ; see where temporalFile was - // set - fileToMove = temporalFile; - } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE - fileToMove = originalFile; - } - if (!expectedFile.equals(fileToMove)) { - File expectedFolder = expectedFile.getParentFile(); - expectedFolder.mkdirs(); - if (!expectedFolder.isDirectory() || !fileToMove.renameTo(expectedFile)) { - mFile.setStoragePath(null); // forget the local file - // by now, treat this as a success; the file was - // uploaded; the user won't like that the local file - // is not linked, but this should be a very rare - // fail; - // the best option could be show a warning message - // (but not a fail) - // result = new - // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED); - // return result; - } + if (mCancellationRequested.get()) { + throw new OperationCancelledException(); + } + + result = mUploadOperation.execute(client); + + /// move local temporal file or original file to its corresponding + // location in the ownCloud local folder + if (result.isSuccess()) { + if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_FORGET) { + mFile.setStoragePath(null); + + } else { + mFile.setStoragePath(expectedPath); + File fileToMove = null; + if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY + // ; see where temporalFile was + // set + fileToMove = temporalFile; + } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE + fileToMove = originalFile; + } + if (!expectedFile.equals(fileToMove)) { + File expectedFolder = expectedFile.getParentFile(); + expectedFolder.mkdirs(); + if (!expectedFolder.isDirectory() || !fileToMove.renameTo(expectedFile)) { + mFile.setStoragePath(null); // forget the local file + // by now, treat this as a success; the file was + // uploaded; the user won't like that the local file + // is not linked, but this should be a very rare + // fail; + // the best option could be show a warning message + // (but not a fail) + // result = new + // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED); + // return result; } } + FileDataStorageManager.triggerMediaScan(originalFile.getAbsolutePath()); + FileDataStorageManager.triggerMediaScan(expectedFile.getAbsolutePath()); } + + } else if (result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED ) { + result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); } } catch (Exception e) { - // TODO something cleaner with cancellations - if (mCancellationRequested.get()) { - result = new RemoteOperationResult(new OperationCancelledException()); - } else { - result = new RemoteOperationResult(e); - } + result = new RemoteOperationResult(e); } finally { if (temporalFile != null && !originalFile.equals(temporalFile)) { @@@ -411,7 -405,7 +408,7 @@@ newFile.setModificationTimestamp(mFile.getModificationTimestamp()); newFile.setModificationTimestampAtLastSyncForData( mFile.getModificationTimestampAtLastSyncForData()); - // newFile.setEtag(mFile.getEtag()) + newFile.setEtag(mFile.getEtag()); newFile.setFavorite(mFile.isFavorite()); newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties()); newFile.setLastSyncDateForData(mFile.getLastSyncDateForData()); diff --combined src/com/owncloud/android/ui/activity/FileDisplayActivity.java index ec214a5e,644cf014..5debcd43 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@@ -26,7 -26,6 +26,7 @@@ import android.accounts.Account import android.accounts.AccountManager; import android.accounts.AuthenticatorException; import android.annotation.TargetApi; +import android.os.Parcelable; import android.support.v7.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.ComponentName; @@@ -108,8 -107,6 +108,8 @@@ import com.owncloud.android.utils.FileS import com.owncloud.android.utils.UriUtils; import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; /** * Displays, what files the user has available in his ownCloud. @@@ -152,14 -149,13 +152,14 @@@ public class FileDisplayActivity extend private boolean mSyncInProgress = false; private static String DIALOG_UNTRUSTED_CERT = "DIALOG_UNTRUSTED_CERT"; - private static String DIALOG_CREATE_FOLDER = "DIALOG_CREATE_FOLDER"; + public static String DIALOG_CREATE_FOLDER = "DIALOG_CREATE_FOLDER"; private static String DIALOG_UPLOAD_SOURCE = "DIALOG_UPLOAD_SOURCE"; private static String DIALOG_CERT_NOT_SAVED = "DIALOG_CERT_NOT_SAVED"; private OCFile mWaitingToSend; + private Menu mOptionsMenu; + - @Override protected void onCreate(Bundle savedInstanceState) { Log_OC.v(TAG, "onCreate() start"); @@@ -269,7 -265,12 +269,7 @@@ setFile(file); if (mAccountWasSet) { - RelativeLayout navigationDrawerLayout = (RelativeLayout) findViewById(R.id.left_drawer); - if (navigationDrawerLayout != null && getAccount() != null) { - TextView username = (TextView) navigationDrawerLayout.findViewById(R.id.drawer_username); - int lastAtPos = getAccount().name.lastIndexOf("@"); - username.setText(getAccount().name.substring(0, lastAtPos)); - } + setUsernameInDrawer((RelativeLayout) findViewById(R.id.left_drawer), getAccount()); } if (!stateWasRecovered) { @@@ -298,7 -299,10 +298,7 @@@ /// First fragment OCFileListFragment listOfFiles = getListOfFilesFragment(); if (listOfFiles != null) { - listOfFiles.listDirectory(getCurrentDir()); - // TODO Enable when "On Device" is recovered - // listOfFiles.listDirectory(getCurrentDir(), MainApp.getOnlyOnDevice()); - + listOfFiles.listDirectory(getCurrentDir(), MainApp.getOnlyOnDevice()); } else { Log_OC.e(TAG, "Still have a chance to lose the initializacion of list fragment >("); } @@@ -317,12 -321,6 +317,12 @@@ startTextPreview(file); } + if (DisplayUtils.isGridView(getFile(), getStorageManager())){ + switchToGridView(); + } else { + switchToListView(); + } + } else { Log_OC.wtf(TAG, "initFragments() called with invalid NULLs!"); if (getAccount() == null) { @@@ -433,7 -431,9 +433,7 @@@ protected void refreshListOfFilesFragment() { OCFileListFragment fileListFragment = getListOfFilesFragment(); if (fileListFragment != null) { - fileListFragment.listDirectory(); - // TODO Enable when "On Device" is recovered ? - // fileListFragment.listDirectory(MainApp.getOnlyOnDevice()); + fileListFragment.listDirectory(MainApp.getOnlyOnDevice()); } } @@@ -485,9 -485,10 +485,9 @@@ @Override public boolean onPrepareOptionsMenu(Menu menu) { boolean drawerOpen = mDrawerLayout.isDrawerOpen(GravityCompat.START); - menu.findItem(R.id.action_upload).setVisible(!drawerOpen); - menu.findItem(R.id.action_create_dir).setVisible(!drawerOpen); menu.findItem(R.id.action_sort).setVisible(!drawerOpen); menu.findItem(R.id.action_sync_account).setVisible(!drawerOpen); + menu.findItem(R.id.action_switch_view).setVisible(!drawerOpen); return super.onPrepareOptionsMenu(menu); } @@@ -496,13 -497,6 +496,13 @@@ public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main_menu, menu); + menu.findItem(R.id.action_create_dir).setVisible(false); + mOptionsMenu = menu; + + MenuItem menuItem = mOptionsMenu.findItem(R.id.action_switch_view); + + changeGridIcon(); + return true; } @@@ -511,10 -505,23 +511,10 @@@ public boolean onOptionsItemSelected(MenuItem item) { boolean retval = true; switch (item.getItemId()) { - case R.id.action_create_dir: { - CreateFolderDialogFragment dialog = - CreateFolderDialogFragment.newInstance(getCurrentDir()); - dialog.show(getSupportFragmentManager(), DIALOG_CREATE_FOLDER); - break; - } - case R.id.action_sync_account: { startSynchronization(); break; } - case R.id.action_upload: { - UploadSourceDialogFragment dialog = - UploadSourceDialogFragment.newInstance(getAccount()); - dialog.show(getSupportFragmentManager(), DIALOG_UPLOAD_SOURCE); - break; - } case android.R.id.home: { FileFragment second = getSecondFragment(); OCFile currentDir = getCurrentDir(); @@@ -557,57 -564,12 +557,57 @@@ builder.create().show(); break; } + case R.id.action_switch_view:{ + if (isGridView()){ + item.setTitle(getApplicationContext().getString(R.string.action_switch_grid_view)); + item.setIcon(ContextCompat.getDrawable(getApplicationContext(), + R.drawable.ic_view_module)); + DisplayUtils.setViewMode(getFile(), false); + switchToListView(); + } else { + item.setTitle(getApplicationContext().getString(R.string.action_switch_list_view)); + item.setIcon(ContextCompat.getDrawable(getApplicationContext(), + R.drawable.ic_view_list)); + DisplayUtils.setViewMode(getFile(), true); + switchToGridView(); + } + + return true; + } default: retval = super.onOptionsItemSelected(item); } return retval; } + public void createFolder() { + CreateFolderDialogFragment dialog = + CreateFolderDialogFragment.newInstance(getCurrentDir()); + dialog.show(getSupportFragmentManager(), DIALOG_CREATE_FOLDER); + } + + public void uploadLocalFilesSelected() { + Intent action = new Intent(this, UploadFilesActivity.class); + action.putExtra( + UploadFilesActivity.EXTRA_ACCOUNT, + getAccount() + ); + startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES); + } + + public void uploadFromOtherAppsSelected() { + Intent action = new Intent(Intent.ACTION_GET_CONTENT); + action = action.setType("*/*").addCategory(Intent.CATEGORY_OPENABLE); + //Intent.EXTRA_ALLOW_MULTIPLE is only supported on api level 18+, Jelly Bean + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + action.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + } + startActivityForResult( + Intent.createChooser(action, getString(R.string.upload_chooser_title)), + ACTION_SELECT_CONTENT_FROM_APPS + ); + } + private void startSynchronization() { Log_OC.d(TAG, "Got to start sync"); if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) { @@@ -802,12 -764,8 +802,12 @@@ */ private void requestMoveOperation(Intent data, int resultCode) { OCFile folderToMoveAt = (OCFile) data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER); - OCFile targetFile = (OCFile) data.getParcelableExtra(FolderPickerActivity.EXTRA_FILE); - getFileOperationsHelper().moveFile(folderToMoveAt, targetFile); + + ArrayList files = data.getParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES); + + for (Parcelable file : files) { + getFileOperationsHelper().moveFile(folderToMoveAt, (OCFile) file); + } } /** @@@ -818,36 -776,13 +818,36 @@@ */ private void requestCopyOperation(Intent data, int resultCode) { OCFile folderToMoveAt = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER); - OCFile targetFile = data.getParcelableExtra(FolderPickerActivity.EXTRA_FILE); - getFileOperationsHelper().copyFile(folderToMoveAt, targetFile); + + ArrayList files = data.getParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES); + + for (Parcelable file : files) { + getFileOperationsHelper().copyFile(folderToMoveAt, (OCFile) file); + } } @Override public void onBackPressed() { - if (!isDrawerOpen()){ + boolean isFabOpen = isFabOpen(); + boolean isDrawerOpen = isDrawerOpen(); + + /* + * BackPressed priority/hierarchy: + * 1. close drawer if opened + * 2. close FAB if open (only if drawer isn't open) + * 3. navigate up (only if drawer and FAB aren't open) + */ + if(isDrawerOpen && isFabOpen) { + // close drawer first + super.onBackPressed(); + } else if(isDrawerOpen && !isFabOpen) { + // close drawer + super.onBackPressed(); + } else if (!isDrawerOpen && isFabOpen) { + // close fab + getListOfFilesFragment().getFabMain().collapse(); + } else { + // all closed OCFileListFragment listOfFiles = getListOfFilesFragment(); if (mDualPane || getSecondFragment() == null) { OCFile currentDir = getCurrentDir(); @@@ -863,20 -798,8 +863,20 @@@ setFile(listOfFiles.getCurrentFile()); } cleanSecondFragment(); + changeGridIcon(); + } + } + + private void changeGridIcon(){ + MenuItem menuItem = mOptionsMenu.findItem(R.id.action_switch_view); + if (DisplayUtils.isGridView(getFile(), getStorageManager())){ + menuItem.setTitle(getApplicationContext().getString(R.string.action_switch_list_view)); + menuItem.setIcon(ContextCompat.getDrawable(getApplicationContext(), + R.drawable.ic_view_list)); } else { - super.onBackPressed(); + menuItem.setTitle(getApplicationContext().getString(R.string.action_switch_grid_view)); + menuItem.setIcon(ContextCompat.getDrawable(getApplicationContext(), + R.drawable.ic_view_module)); } } @@@ -955,14 -878,6 +955,14 @@@ Log_OC.v(TAG, "onPause() end"); } + public boolean isFabOpen() { + if(getListOfFilesFragment() != null && getListOfFilesFragment().getFabMain() != null && getListOfFilesFragment().getFabMain().isExpanded()) { + return true; + } else { + return false; + } + } + private class SyncBroadcastReceiver extends BroadcastReceiver { @@@ -1018,8 -933,10 +1018,8 @@@ currentDir.getRemotePath().equals(synchFolderRemotePath)) { OCFileListFragment fileListFragment = getListOfFilesFragment(); if (fileListFragment != null) { - fileListFragment.listDirectory(); - // TODO Enable when "On Device" is recovered ? - // fileListFragment.listDirectory(currentDir, - // MainApp.getOnlyOnDevice()); + fileListFragment.listDirectory(currentDir, + MainApp.getOnlyOnDevice()); } } setFile(currentFile); @@@ -1131,7 -1048,11 +1131,11 @@@ (uploadedRemotePath.startsWith(currentDir.getRemotePath())); if (sameAccount && isDescendant) { - refreshListOfFilesFragment(); + String linkedToRemotePath = + intent.getStringExtra(FileDownloader.EXTRA_LINKED_TO_PATH); + if (linkedToRemotePath == null || isAscendant(linkedToRemotePath)) { + refreshListOfFilesFragment(); + } } boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, @@@ -1184,6 -1105,16 +1188,16 @@@ } + // TODO refactor this receiver, and maybe DownloadFinishReceiver; this method is duplicated :S + private boolean isAscendant(String linkedToRemotePath) { + OCFile currentDir = getCurrentDir(); + return ( + currentDir != null && + currentDir.getRemotePath().startsWith(linkedToRemotePath) + ); + } + + } @@@ -1195,11 -1126,10 +1209,10 @@@ */ private class DownloadFinishReceiver extends BroadcastReceiver { - //int refreshCounter = 0; @Override public void onReceive(Context context, Intent intent) { try { - boolean sameAccount = isSameAccount(context, intent); + boolean sameAccount = isSameAccount(intent); String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); boolean isDescendant = isDescendant(downloadedRemotePath); @@@ -1208,7 -1138,6 +1221,6 @@@ String linkedToRemotePath = intent.getStringExtra(FileDownloader.EXTRA_LINKED_TO_PATH); if (linkedToRemotePath == null || isAscendant(linkedToRemotePath)) { - //Log_OC.v(TAG, "refresh #" + ++refreshCounter); refreshListOfFilesFragment(); } refreshSecondFragment( @@@ -1250,7 -1179,7 +1262,7 @@@ ); } - private boolean isSameAccount(Context context, Intent intent) { + private boolean isSameAccount(Intent intent) { String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME); return (accountName != null && getAccount() != null && accountName.equals(getAccount().name)); @@@ -1262,7 -1191,9 +1274,7 @@@ OCFileListFragment listOfFiles = getListOfFilesFragment(); if (listOfFiles != null) { // should never be null, indeed OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH); - listOfFiles.listDirectory(root); - // TODO Enable when "On Device" is recovered ? - // listOfFiles.listDirectory(root, MainApp.getOnlyOnDevice()); + listOfFiles.listDirectory(root, MainApp.getOnlyOnDevice()); setFile(listOfFiles.getCurrentFile()); startSyncFolderOperation(root, false); } @@@ -1281,15 -1212,6 +1293,15 @@@ cleanSecondFragment(); // Sync Folder startSyncFolderOperation(directory, false); + + MenuItem menuItem = mOptionsMenu.findItem(R.id.action_switch_view); + + changeGridIcon(); + if (DisplayUtils.isGridView(directory, getStorageManager())){ + switchToGridView(); + } else { + switchToListView(); + } } /** @@@ -1356,7 -1278,9 +1368,7 @@@ // getFileDownloadBinder() - THIS IS A MESS OCFileListFragment listOfFiles = getListOfFilesFragment(); if (listOfFiles != null) { - listOfFiles.listDirectory(); - // TODO Enable when "On Device" is recovered ? - // listOfFiles.listDirectory(MainApp.getOnlyOnDevice()); + listOfFiles.listDirectory(MainApp.getOnlyOnDevice()); } FileFragment secondFragment = getSecondFragment(); if (secondFragment != null && secondFragment instanceof FileDetailFragment) { @@@ -1885,19 -1809,8 +1897,19 @@@ private void sortByName(boolean ascending) { getListOfFilesFragment().sortByName(ascending); } + private boolean isGridView(){ return getListOfFilesFragment().isGridView(); } + private void switchToGridView() { + getListOfFilesFragment().switchToGridView(); + } + private void switchToListView() { + getListOfFilesFragment().switchToListView(); + } public void allFilesOption() { browseToRoot(); } + + public void refreshDirectory(){ + getListOfFilesFragment().refreshDirectory(); + } } diff --combined src/com/owncloud/android/ui/adapter/FileListListAdapter.java index 3aecd7e8,d0abff29..956c7d1f --- a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java @@@ -25,16 -25,12 +25,16 @@@ package com.owncloud.android.ui.adapter import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.Vector; import android.accounts.Account; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Bitmap; +import android.graphics.Color; import android.os.Build; import android.preference.PreferenceManager; import android.text.format.DateUtils; @@@ -83,8 -79,6 +83,8 @@@ public class FileListListAdapter extend private enum ViewType {LIST_ITEM, GRID_IMAGE, GRID_ITEM }; private SharedPreferences mAppPreferences; + + private HashMap mSelection = new HashMap(); public FileListListAdapter( boolean justFolders, @@@ -160,7 -154,7 +160,7 @@@ ViewType viewType; if (!mGridMode){ viewType = ViewType.LIST_ITEM; - } else if (file.isImage()){ + } else if (file.isImage() || file.isVideo()){ viewType = ViewType.GRID_IMAGE; } else { viewType = ViewType.GRID_ITEM; @@@ -200,37 -194,35 +200,37 @@@ switch (viewType){ case LIST_ITEM: TextView fileSizeV = (TextView) view.findViewById(R.id.file_size); + TextView fileSizeSeparatorV = (TextView) view.findViewById(R.id.file_separator); TextView lastModV = (TextView) view.findViewById(R.id.last_mod); - ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox); + lastModV.setVisibility(View.VISIBLE); lastModV.setText(showRelativeTimestamp(file)); - checkBoxV.setVisibility(View.GONE); + fileSizeSeparatorV.setVisibility(View.VISIBLE); fileSizeV.setVisibility(View.VISIBLE); fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength())); - if (!file.isFolder()) { - AbsListView parentList = (AbsListView)parent; - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - if (parentList.getChoiceMode() == AbsListView.CHOICE_MODE_NONE) { - checkBoxV.setVisibility(View.GONE); - } else { - if (parentList.isItemChecked(position)) { - checkBoxV.setImageResource( - android.R.drawable.checkbox_on_background); - } else { - checkBoxV.setImageResource( - android.R.drawable.checkbox_off_background); - } - checkBoxV.setVisibility(View.VISIBLE); - } - } - - } else { //Folder +// if (!file.isFolder()) { +// AbsListView parentList = (AbsListView)parent; +// if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { +// if (parentList.getChoiceMode() == AbsListView.CHOICE_MODE_NONE) { +// checkBoxV.setVisibility(View.GONE); +// } else { +// if (parentList.isItemChecked(position)) { +// checkBoxV.setImageResource( +// R.drawable.ic_checkbox_marked); +// } else { +// checkBoxV.setImageResource( +// R.drawable.ic_checkbox_blank_outline); +// } +// checkBoxV.setVisibility(View.VISIBLE); +// } +// } + + if (file.isFolder()) { + fileSizeSeparatorV.setVisibility(View.INVISIBLE); fileSizeV.setVisibility(View.INVISIBLE); } @@@ -257,24 -249,47 +257,47 @@@ mTransferServiceGetter.getFileDownloaderBinder(); FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder(); - boolean downloading = (downloaderBinder != null && - downloaderBinder.isDownloading(mAccount, file)); OperationsServiceBinder opsBinder = mTransferServiceGetter.getOperationsServiceBinder(); - downloading |= (opsBinder != null && - opsBinder.isSynchronizing(mAccount, file.getRemotePath())); - if (downloading) { - localStateView.setImageResource(R.drawable.downloading_file_indicator); + + localStateView.setVisibility(View.INVISIBLE); // default first + + if ( //synchronizing + opsBinder != null && + opsBinder.isSynchronizing(mAccount, file.getRemotePath()) + ) { + localStateView.setImageResource(R.drawable.synchronizing_file_indicator); + localStateView.setVisibility(View.VISIBLE); + + } else if ( // downloading + downloaderBinder != null && + downloaderBinder.isDownloading(mAccount, file) + ) { + localStateView.setImageResource( + file.isFolder() ? + R.drawable.synchronizing_file_indicator : + R.drawable.downloading_file_indicator + ); localStateView.setVisibility(View.VISIBLE); - } else if (uploaderBinder != null && - uploaderBinder.isUploading(mAccount, file)) { - localStateView.setImageResource(R.drawable.uploading_file_indicator); + + } else if ( //uploading + uploaderBinder != null && + uploaderBinder.isUploading(mAccount, file) + ) { + localStateView.setImageResource( + file.isFolder() ? + R.drawable.synchronizing_file_indicator : + R.drawable.uploading_file_indicator + ); localStateView.setVisibility(View.VISIBLE); + + } else if (file.getEtagInConflict() != null) { // conflict + localStateView.setImageResource(R.drawable.conflict_file_indicator); + localStateView.setVisibility(View.VISIBLE); + } else if (file.isDown()) { localStateView.setImageResource(R.drawable.local_file_indicator); localStateView.setVisibility(View.VISIBLE); - } else { - localStateView.setVisibility(View.INVISIBLE); } // share with me icon @@@ -290,25 -305,6 +313,25 @@@ break; } + + ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox); + checkBoxV.setVisibility(View.GONE); + + AbsListView parentList = (AbsListView)parent; + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + if (parentList.getChoiceMode() == AbsListView.CHOICE_MODE_NONE) { + checkBoxV.setVisibility(View.GONE); + } else if (parentList.getCheckedItemCount() > 0){ + if (parentList.isItemChecked(position)) { + checkBoxV.setImageResource( + android.R.drawable.checkbox_on_background); + } else { + checkBoxV.setImageResource( + android.R.drawable.checkbox_off_background); + } + checkBoxV.setVisibility(View.VISIBLE); + } + } // For all Views @@@ -325,7 -321,8 +348,7 @@@ if (file.isImage() && file.getRemoteId() != null){ // Thumbnail in Cache? Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - String.valueOf(file.getRemoteId()) - ); + "t" + String.valueOf(file.getRemoteId())); if (thumbnail != null && !file.needsUpdateThumbnail()){ fileIcon.setImageBitmap(thumbnail); } else { @@@ -345,7 -342,7 +368,7 @@@ task ); fileIcon.setImageDrawable(asyncDrawable); - task.execute(file); + task.execute(file, true); } } @@@ -368,55 -365,9 +391,15 @@@ } } + if (mSelection.get(position) != null) { + view.setBackgroundColor(Color.rgb(248, 248, 248)); + } else { + view.setBackgroundColor(Color.WHITE); + } + return view; } - /** - * Local Folder size in human readable format - * - * @param path - * String - * @return Size in human readable format - */ - private String getFolderSizeHuman(String path) { - - File dir = new File(path); - - if (dir.exists()) { - long bytes = FileStorageUtils.getFolderSize(dir); - return DisplayUtils.bytesToHumanReadable(bytes); - } - - return "0 B"; - } - - /** - * Local Folder size - * @param dir File - * @return Size in bytes - */ - private long getFolderSize(File dir) { - if (dir.exists()) { - long result = 0; - File[] fileList = dir.listFiles(); - for(int i = 0; i < fileList.length; i++) { - if(fileList[i].isDirectory()) { - result += getFolderSize(fileList[i]); - } else { - result += fileList[i].length(); - } - } - return result; - } - return 0; - } - @Override public int getViewTypeCount() { return 1; @@@ -440,14 -391,15 +423,14 @@@ * mStorageManager if is different (and not NULL) */ public void swapDirectory(OCFile directory, FileDataStorageManager updatedStorageManager - /*, boolean onlyOnDevice*/) { + , boolean onlyOnDevice) { mFile = directory; if (updatedStorageManager != null && updatedStorageManager != mStorageManager) { mStorageManager = updatedStorageManager; mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); } if (mStorageManager != null) { - // TODO Enable when "On Device" is recovered ? - mFiles = mStorageManager.getFolderContent(mFile/*, onlyOnDevice*/); + mFiles = mStorageManager.getFolderContent(mFile, onlyOnDevice); mFilesOrig.clear(); mFilesOrig.addAll(mFiles); @@@ -458,7 -410,7 +441,7 @@@ mFiles = null; } - mFiles = FileStorageUtils.sortFolder(mFiles); + mFiles = FileStorageUtils.sortOcFolder(mFiles); notifyDataSetChanged(); } @@@ -505,7 -457,7 +488,7 @@@ FileStorageUtils.mSortAscending = ascending; - mFiles = FileStorageUtils.sortFolder(mFiles); + mFiles = FileStorageUtils.sortOcFolder(mFiles); notifyDataSetChanged(); } @@@ -518,45 -470,4 +501,45 @@@ public void setGridMode(boolean gridMode) { mGridMode = gridMode; } + + public boolean isGridMode() { + return mGridMode; + } + + public void setNewSelection(int position, boolean checked) { + mSelection.put(position, checked); + notifyDataSetChanged(); + } + + public void removeSelection(int position) { + mSelection.remove(position); + notifyDataSetChanged(); + } + + public void removeSelection(){ + mSelection.clear(); + notifyDataSetChanged(); + } + + public ArrayList getCheckedItemPositions() { + ArrayList ids = new ArrayList(); + + for (Map.Entry entry : mSelection.entrySet()){ + if (entry.getValue()){ + ids.add(entry.getKey()); + } + } + return ids; + } + + public ArrayList getCheckedItems() { + ArrayList files = new ArrayList(); + + for (Map.Entry entry : mSelection.entrySet()){ + if (entry.getValue()){ + files.add((OCFile) getItem(entry.getKey())); + } + } + return files; + } } diff --combined src/com/owncloud/android/ui/dialog/RemoveFileDialogFragment.java index 0b0883c2,f8310a1d..d42cf465 --- a/src/com/owncloud/android/ui/dialog/RemoveFileDialogFragment.java +++ b/src/com/owncloud/android/ui/dialog/RemoveFileDialogFragment.java @@@ -25,7 -25,6 +25,6 @@@ package com.owncloud.android.ui.dialog * * Triggers the removal according to the user response. */ - import java.util.Vector; import android.app.Dialog; import android.os.Bundle; @@@ -53,9 -52,9 +52,9 @@@ implements ConfirmationDialogFragmentLi RemoveFileDialogFragment frag = new RemoveFileDialogFragment(); Bundle args = new Bundle(); - int messageStringId = R.string.confirmation_remove_alert; + int messageStringId = R.string.confirmation_remove_file_alert; - int posBtn = R.string.confirmation_remove_remote; + int posBtn = R.string.confirmation_remove_file_remote; int negBtn = -1; if (file.isFolder()) { messageStringId = R.string.confirmation_remove_folder_alert; @@@ -106,33 -105,6 +105,6 @@@ public void onCancel(String callerTag) { ComponentsGetter cg = (ComponentsGetter)getActivity(); cg.getFileOperationsHelper().removeFile(mTargetFile, true); - - FileDataStorageManager storageManager = cg.getStorageManager(); - - boolean containsFavorite = false; - if (mTargetFile.isFolder()) { - Vector files = storageManager.getFolderContent(mTargetFile, false); - for(OCFile file: files) { - containsFavorite = file.isFavorite() || containsFavorite; - - if (containsFavorite) - break; - } - } - - // Remove etag for parent, if file is a favorite - // or is a folder and contains favorite - if (mTargetFile.isFavorite() || containsFavorite) { - OCFile folder = null; - if (mTargetFile.isFolder()) { - folder = mTargetFile; - } else { - folder = storageManager.getFileById(mTargetFile.getParentId()); - } - - folder.setEtag(""); - storageManager.saveFile(folder); - } } @Override diff --combined src/com/owncloud/android/ui/fragment/OCFileListFragment.java index c639525e,23590b5e..804d5074 --- a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java @@@ -24,24 -24,17 +24,24 @@@ package com.owncloud.android.ui.fragmen import android.app.Activity; import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.v4.widget.SwipeRefreshLayout; -import android.view.ContextMenu; +import android.view.ActionMode; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.PopupMenu; +import android.widget.TextView; +import android.widget.Toast; +import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.datamodel.FileDataStorageManager; @@@ -53,30 -46,24 +53,30 @@@ import com.owncloud.android.ui.activity import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.activity.FolderPickerActivity; import com.owncloud.android.ui.activity.OnEnforceableRefreshListener; +import com.owncloud.android.ui.activity.UploadFilesActivity; import com.owncloud.android.ui.adapter.FileListListAdapter; import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; +import com.owncloud.android.ui.dialog.CreateFolderDialogFragment; import com.owncloud.android.ui.dialog.FileActionsDialogFragment; import com.owncloud.android.ui.dialog.RemoveFileDialogFragment; +import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; import com.owncloud.android.ui.dialog.RenameFileDialogFragment; +import com.owncloud.android.ui.dialog.UploadSourceDialogFragment; import com.owncloud.android.ui.preview.PreviewImageFragment; import com.owncloud.android.ui.preview.PreviewMediaFragment; +import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.FileStorageUtils; import com.owncloud.android.ui.preview.PreviewTextFragment; import java.io.File; +import java.util.ArrayList; /** * A Fragment that lists all files and folders in a given path. * * TODO refactor to get rid of direct dependency on FileDisplayActivity */ -public class OCFileListFragment extends ExtendedListFragment implements FileActionsDialogFragment.FileActionsDialogFragmentListener { +public class OCFileListFragment extends ExtendedListFragment { private static final String TAG = OCFileListFragment.class.getSimpleName(); @@@ -85,12 -72,8 +85,12 @@@ public final static String ARG_JUST_FOLDERS = MY_PACKAGE + ".JUST_FOLDERS"; public final static String ARG_ALLOW_CONTEXTUAL_ACTIONS = MY_PACKAGE + ".ALLOW_CONTEXTUAL"; + public final static String ARG_HIDE_FAB = MY_PACKAGE + ".HIDE_FAB"; private static final String KEY_FILE = MY_PACKAGE + ".extra.FILE"; + private static final String KEY_FAB_EVER_CLICKED = "FAB_EVER_CLICKED"; + + private static String DIALOG_CREATE_FOLDER = "DIALOG_CREATE_FOLDER"; private FileFragment.ContainerActivity mContainerActivity; @@@ -99,8 -82,8 +99,8 @@@ private boolean mJustFolders; private OCFile mTargetFile; - - + + private boolean miniFabClicked = false; /** * {@inheritDoc} @@@ -161,211 -144,15 +161,211 @@@ setListAdapter(mAdapter); registerLongClickListener(); + + boolean hideFab = (args != null) && args.getBoolean(ARG_HIDE_FAB, false); + if (hideFab) { + setFabEnabled(false); + } else { + setFabEnabled(true); + registerFabListeners(); + + // detect if a mini FAB has ever been clicked + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); + if(prefs.getLong(KEY_FAB_EVER_CLICKED, 0) > 0) { + miniFabClicked = true; + } + + // add labels to the min FABs when none of them has ever been clicked on + if(!miniFabClicked) { + setFabLabels(); + } else { + removeFabLabels(); + } + } } + /** + * adds labels to all mini FABs. + */ + private void setFabLabels() { + getFabUpload().setTitle(getResources().getString(R.string.actionbar_upload)); + getFabMkdir().setTitle(getResources().getString(R.string.actionbar_mkdir)); + getFabUploadFromApp().setTitle(getResources().getString(R.string.actionbar_upload_from_apps)); + } + + /** + * registers all listeners on all mini FABs. + */ + private void registerFabListeners() { + registerFabUploadListeners(); + registerFabMkDirListeners(); + registerFabUploadFromAppListeners(); + } + + /** + * registers {@link android.view.View.OnClickListener} and {@link android.view.View.OnLongClickListener} + * on the Upload mini FAB for the linked action and {@link Toast} showing the underlying action. + */ + private void registerFabUploadListeners() { + getFabUpload().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent action = new Intent(getActivity(), UploadFilesActivity.class); + action.putExtra( + UploadFilesActivity.EXTRA_ACCOUNT, + ((FileActivity) getActivity()).getAccount() + ); + getActivity().startActivityForResult(action, UploadSourceDialogFragment.ACTION_SELECT_MULTIPLE_FILES); + getFabMain().collapse(); + recordMiniFabClick(); + } + }); + + getFabUpload().setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + Toast.makeText(getActivity(), R.string.actionbar_upload, Toast.LENGTH_SHORT).show(); + return true; + } + }); + } + + /** + * registers {@link android.view.View.OnClickListener} and {@link android.view.View.OnLongClickListener} + * on the 'Create Dir' mini FAB for the linked action and {@link Toast} showing the underlying action. + */ + private void registerFabMkDirListeners() { + getFabMkdir().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CreateFolderDialogFragment dialog = + CreateFolderDialogFragment.newInstance(mFile); + dialog.show(getActivity().getSupportFragmentManager(), FileDisplayActivity.DIALOG_CREATE_FOLDER); + getFabMain().collapse(); + recordMiniFabClick(); + } + }); + + getFabMkdir().setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + Toast.makeText(getActivity(), R.string.actionbar_mkdir, Toast.LENGTH_SHORT).show(); + return true; + } + }); + } + + /** + * registers {@link android.view.View.OnClickListener} and {@link android.view.View.OnLongClickListener} + * on the Upload from App mini FAB for the linked action and {@link Toast} showing the underlying action. + */ + private void registerFabUploadFromAppListeners() { + getFabUploadFromApp().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent action = new Intent(Intent.ACTION_GET_CONTENT); + action = action.setType("*/*").addCategory(Intent.CATEGORY_OPENABLE); + + //Intent.EXTRA_ALLOW_MULTIPLE is only supported on api level 18+, Jelly Bean + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + action.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + } + + getActivity().startActivityForResult( + Intent.createChooser(action, getString(R.string.upload_chooser_title)), + UploadSourceDialogFragment.ACTION_SELECT_CONTENT_FROM_APPS + ); + getFabMain().collapse(); + recordMiniFabClick(); + } + }); + + getFabUploadFromApp().setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + Toast.makeText(getActivity(), + R.string.actionbar_upload_from_apps, + Toast.LENGTH_SHORT).show(); + return true; + } + }); + } + + /** + * records a click on a mini FAB and thus: + *
    + *
  1. persists the click fact
  2. + *
  3. removes the mini FAB labels
  4. + *
+ */ + private void recordMiniFabClick() { + // only record if it hasn't been done already at some other time + if(!miniFabClicked) { + final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity()); + sp.edit().putLong(KEY_FAB_EVER_CLICKED, 1).commit(); + miniFabClicked = true; + } + } + + /** + * removes the labels on all known min FABs. + */ + private void removeFabLabels() { + getFabUpload().setTitle(null); + getFabMkdir().setTitle(null); + getFabUploadFromApp().setTitle(null); + ((TextView) getFabUpload().getTag(com.getbase.floatingactionbutton.R.id.fab_label)).setVisibility(View.GONE); + ((TextView) getFabMkdir().getTag(com.getbase.floatingactionbutton.R.id.fab_label)).setVisibility(View.GONE); + ((TextView) getFabUploadFromApp().getTag(com.getbase.floatingactionbutton.R.id.fab_label)).setVisibility(View.GONE); + } + private void registerLongClickListener() { - getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { - public boolean onItemLongClick(AdapterView arg0, View v, - int index, long arg3) { - showFileAction(index); + getListView().setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() { + private Menu menu; + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { + final int checkedCount = getListView().getCheckedItemCount(); + // TODO Tobi extract to values + mode.setTitle(checkedCount + " selected"); + + if (checked) { + mAdapter.setNewSelection(position, checked); + } else { + mAdapter.removeSelection(position); + } + + // TODO maybe change: only recreate menu if count changes + menu.clear(); + if (checkedCount == 1) { + createContextMenu(menu); + } else { + // download, move, copy, delete + getActivity().getMenuInflater().inflate(R.menu.multiple_file_actions_menu, menu); + } + + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + this.menu = menu; return true; } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return onFileActionChosen(item.getItemId()); + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + mAdapter.removeSelection(); + } }); } @@@ -423,11 -210,11 +423,11 @@@ /** * Call this, when the user presses the up button. - * - * Tries to move up the current folder one level. If the parent folder was removed from the - * database, it continues browsing up until finding an existing folders. - *

- * return Count of folder levels browsed up. + *

+ * Tries to move up the current folder one level. If the parent folder was removed from the + * database, it continues browsing up until finding an existing folders. + *

+ * @return Count of folder levels browsed up. */ public int onBrowseUp() { OCFile parentDir = null; @@@ -455,7 -242,8 +455,7 @@@ } // exit is granted because storageManager.getFileByPath("/") never returns null mFile = parentDir; - // TODO Enable when "On Device" is recovered ? - listDirectory(mFile /*, MainApp.getOnlyOnDevice()*/); + listDirectory(mFile, MainApp.getOnlyOnDevice()); onRefresh(false); @@@ -473,7 -261,8 +473,7 @@@ if (file != null) { if (file.isFolder()) { // update state and view of this fragment - // TODO Enable when "On Device" is recovered ? - listDirectory(file/*, MainApp.getOnlyOnDevice()*/); + listDirectory(file, MainApp.getOnlyOnDevice()); // then, notify parent activity to let it update its state and view mContainerActivity.onBrowsedDownTo(file); // save index and top position @@@ -492,11 -281,14 +492,11 @@@ } else { mContainerActivity.getFileOperationsHelper().openFile(file); } - } else { // automatic download, preview on finish ((FileDisplayActivity) mContainerActivity).startDownloadForPreview(file); } - } - } else { Log_OC.d(TAG, "Null object in ListAdapter!!"); } @@@ -506,18 -298,17 +506,18 @@@ /** * {@inheritDoc} */ - @Override - public void onCreateContextMenu( - ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + // TODO Tobi needed? + public void createContextMenu(Menu menu) { Bundle args = getArguments(); boolean allowContextualActions = (args == null) ? true : args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, true); if (allowContextualActions) { MenuInflater inflater = getActivity().getMenuInflater(); inflater.inflate(R.menu.file_actions_menu, menu); - AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; - OCFile targetFile = (OCFile) mAdapter.getItem(info.position); + OCFile targetFile = null; + if (mAdapter.getCheckedItems().size() == 1){ + targetFile = mAdapter.getCheckedItems().get(0); + } if (mContainerActivity.getStorageManager() != null) { FileMenuFilter mf = new FileMenuFilter( @@@ -540,128 -331,88 +540,127 @@@ item.setEnabled(false); } } + +// String.format(mContext.getString(R.string.subject_token), +// getClient().getCredentials().getUsername(), file.getFileName())); } } - /** - * {@inheritDoc} - */ - @Override - public boolean onFileActionChosen(int menuId, int filePosition) { - mTargetFile = (OCFile) mAdapter.getItem(filePosition); - switch (menuId) { - case R.id.action_share_file: { - mContainerActivity.getFileOperationsHelper().shareFileWithLink(mTargetFile); - return true; - } - case R.id.action_open_file_with: { - mContainerActivity.getFileOperationsHelper().openFile(mTargetFile); - return true; - } - case R.id.action_unshare_file: { - mContainerActivity.getFileOperationsHelper().unshareFileWithLink(mTargetFile); - return true; - } - case R.id.action_rename_file: { - RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(mTargetFile); - dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE); - return true; - } - case R.id.action_remove_file: { - RemoveFileDialogFragment dialog = RemoveFileDialogFragment.newInstance(mTargetFile); - dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION); - return true; - } - case R.id.action_download_file: - case R.id.action_sync_file: { - mContainerActivity.getFileOperationsHelper().syncFile(mTargetFile); - return true; - } - case R.id.action_cancel_sync: { - ((FileDisplayActivity)mContainerActivity).cancelTransference(mTargetFile); - return true; - } - case R.id.action_see_details: { - mContainerActivity.showDetails(mTargetFile); - return true; - } - case R.id.action_send_file: { - // Obtain the file - if (!mTargetFile.isDown()) { // Download the file - Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded"); - ((FileDisplayActivity) mContainerActivity).startDownloadForSending(mTargetFile); + public boolean onFileActionChosen(int menuId) { + if (mAdapter.getCheckedItems().size() == 1){ + OCFile mTargetFile = mAdapter.getCheckedItems().get(0); - } else { - mContainerActivity.getFileOperationsHelper().sendDownloadedFile(mTargetFile); + switch (menuId) { + case R.id.action_share_file: { + mContainerActivity.getFileOperationsHelper().shareFileWithLink(mTargetFile); + return true; } - return true; - } - case R.id.action_move: { - Intent action = new Intent(getActivity(), FolderPickerActivity.class); + case R.id.action_open_file_with: { + mContainerActivity.getFileOperationsHelper().openFile(mTargetFile); + return true; + } + case R.id.action_unshare_file: { + mContainerActivity.getFileOperationsHelper().unshareFileWithLink(mTargetFile); + return true; + } + case R.id.action_rename_file: { + RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(mTargetFile); + dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE); + return true; + } + case R.id.action_remove_file: { + RemoveFileDialogFragment dialog = RemoveFileDialogFragment.newInstance(mTargetFile); + dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION); + return true; + } + case R.id.action_download_file: + case R.id.action_sync_file: { + mContainerActivity.getFileOperationsHelper().syncFile(mTargetFile); + return true; + } - case R.id.action_cancel_download: - case R.id.action_cancel_upload: { ++ case R.id.action_cancel_sync: { + ((FileDisplayActivity) mContainerActivity).cancelTransference(mTargetFile); + return true; + } + case R.id.action_see_details: { + mContainerActivity.showDetails(mTargetFile); + return true; + } + case R.id.action_send_file: { + // Obtain the file + if (!mTargetFile.isDown()) { // Download the file + Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded"); + ((FileDisplayActivity) mContainerActivity).startDownloadForSending(mTargetFile); - // Pass mTargetFile that contains info of selected file/folder - action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile); - getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_MOVE_FILES); - return true; - } - case R.id.action_favorite_file: { - mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, true); - return true; - } - case R.id.action_unfavorite_file: { - mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, false); - return true; + } else { + mContainerActivity.getFileOperationsHelper().sendDownloadedFile(mTargetFile); + } + return true; + } + case R.id.action_move: { + Intent action = new Intent(getActivity(), FolderPickerActivity.class); + ArrayList files = new ArrayList(); + files.add(mTargetFile); + action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, files); + getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_MOVE_FILES); + return true; + } + case R.id.action_favorite_file: { + mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, true); + return true; + } + case R.id.action_unfavorite_file: { + mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, false); + return true; + } + case R.id.action_copy: + Intent action = new Intent(getActivity(), FolderPickerActivity.class); + + // Pass mTargetFile that contains info of selected file/folder + action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile); + getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_COPY_FILES); + return true; + default: + return false; } - case R.id.action_copy: - Intent action = new Intent(getActivity(), FolderPickerActivity.class); + } else { + ArrayList mTargetFiles = mAdapter.getCheckedItems(); - // Pass mTargetFile that contains info of selected file/folder - action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile); - getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_COPY_FILES); - return true; - default: - return false; + switch (menuId) { + case R.id.action_remove_file: { + RemoveFilesDialogFragment dialog = RemoveFilesDialogFragment.newInstance(mTargetFiles); + dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION); + return true; + } + case R.id.action_download_file: + case R.id.action_sync_file: { + mContainerActivity.getFileOperationsHelper().syncFiles(mTargetFiles); + return true; + } + case R.id.action_move: { + Intent action = new Intent(getActivity(), FolderPickerActivity.class); + action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, mTargetFiles); + getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_MOVE_FILES); + return true; + } + case R.id.action_favorite_file: { + mContainerActivity.getFileOperationsHelper().toggleFavorites(mTargetFiles, true); + return true; + } + case R.id.action_unfavorite_file: { + mContainerActivity.getFileOperationsHelper().toggleFavorites(mTargetFiles, false); + return true; + } + case R.id.action_copy: + Intent action = new Intent(getActivity(), FolderPickerActivity.class); + action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, mTargetFiles); + getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_COPY_FILES); + return true; + default: + return false; + } } + } /** @@@ -670,7 -421,7 +669,7 @@@ @Override public boolean onContextItemSelected (MenuItem item) { AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); - boolean matched = onFileActionChosen(item.getItemId(), ((AdapterContextMenuInfo) item.getMenuInfo()).position); + boolean matched = onFileActionChosen(item.getItemId()); if(!matched) { return super.onContextItemSelected(item); } else { @@@ -690,14 -441,17 +689,14 @@@ } /** - * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter + * Calls {@link OCFileListFragment#listDirectory(OCFile, boolean)} with a null parameter */ - public void listDirectory(/*boolean onlyOnDevice*/){ - listDirectory(null); - // TODO Enable when "On Device" is recovered ? - // listDirectory(null, onlyOnDevice); + public void listDirectory(boolean onlyOnDevice){ + listDirectory(null, onlyOnDevice); } public void refreshDirectory(){ - // TODO Enable when "On Device" is recovered ? - listDirectory(getCurrentFile()/*, MainApp.getOnlyOnDevice()*/); + listDirectory(getCurrentFile(), MainApp.getOnlyOnDevice()); } /** @@@ -707,7 -461,7 +706,7 @@@ * * @param directory File to be listed */ - public void listDirectory(OCFile directory/*, boolean onlyOnDevice*/) { + public void listDirectory(OCFile directory, boolean onlyOnDevice) { FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); if (storageManager != null) { @@@ -728,7 -482,8 +727,7 @@@ directory = storageManager.getFileById(directory.getParentId()); } - // TODO Enable when "On Device" is recovered ? - mAdapter.swapDirectory(directory, storageManager/*, onlyOnDevice*/); + mAdapter.swapDirectory(directory, storageManager, onlyOnDevice); if (mFile == null || !mFile.equals(directory)) { mCurrentListView.setSelection(0); } @@@ -765,7 -520,7 +764,7 @@@ OwnCloudVersion version = AccountUtils.getServerVersion( ((FileActivity)mContainerActivity).getAccount()); if (version != null && version.supportsRemoteThumbnails() && - imagesCount > 0 && imagesCount == filesCount) { + DisplayUtils.isGridView(mFile, mContainerActivity.getStorageManager())) { switchToGridView(); registerLongClickListener(); } else { diff --combined src/com/owncloud/android/utils/DisplayUtils.java index f63d7ec4,a30595c3..25cc55a4 --- a/src/com/owncloud/android/utils/DisplayUtils.java +++ b/src/com/owncloud/android/utils/DisplayUtils.java @@@ -22,21 -22,9 +22,21 @@@ package com.owncloud.android.utils; +import java.io.File; +import java.net.IDN; +import java.text.DateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.Vector; + import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; +import android.content.SharedPreferences; import android.graphics.Point; import android.graphics.PorterDuff; import android.os.Build; @@@ -47,10 -35,8 +47,10 @@@ import android.widget.SeekBar import com.owncloud.android.MainApp; import com.owncloud.android.R; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; +import java.math.BigDecimal; import java.net.IDN; import java.text.DateFormat; import java.util.Calendar; @@@ -66,7 -52,6 +66,7 @@@ public class DisplayUtils private static final String OWNCLOUD_APP_NAME = "ownCloud"; private static final String[] sizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; + private static final int[] sizeScales = { 0, 0, 0, 1, 1, 2, 2, 2, 2 }; private static Map mimeType2HumanReadable; @@@ -87,24 -72,19 +87,24 @@@ /** * Converts the file size in bytes to human readable output. - * + *
    + *
  • appends a size suffix, e.g. B, KB, MB etc.
  • + *
  • rounds the size based on the suffix to 0,1 or 2 decimals
  • + *
+ * * @param bytes Input file size * @return Like something readable like "12 MB" */ public static String bytesToHumanReadable(long bytes) { double result = bytes; - int attachedsuff = 0; - while (result > 1024 && attachedsuff < sizeSuffixes.length) { + int attachedSuff = 0; + while (result > 1024 && attachedSuff < sizeSuffixes.length) { result /= 1024.; - attachedsuff++; + attachedSuff++; } - result = ((int) (result * 100)) / 100.; - return result + " " + sizeSuffixes[attachedsuff]; + + return new BigDecimal(result).setScale( + sizeScales[attachedSuff], BigDecimal.ROUND_HALF_UP) + " " + sizeSuffixes[attachedSuff]; } /** @@@ -213,19 -193,7 +213,7 @@@ else if ((System.currentTimeMillis() - time) < 60 * 1000) { return c.getString(R.string.file_list_seconds_ago); } else { - // Workaround 2.x bug (see https://github.com/owncloud/android/issues/716) - if ( Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB && - (System.currentTimeMillis() - time) > 24 * 60 * 60 * 1000 ) { - Date date = new Date(time); - date.setHours(0); - date.setMinutes(0); - date.setSeconds(0); - dateString = DateUtils.getRelativeDateTimeString( - c, date.getTime(), minResolution, transitionResolution, flags - ); - } else { - dateString = DateUtils.getRelativeDateTimeString(c, time, minResolution, transitionResolution, flags); - } + dateString = DateUtils.getRelativeDateTimeString(c, time, minResolution, transitionResolution, flags); } String[] parts = dateString.toString().split(","); @@@ -236,8 -204,8 +224,8 @@@ return parts[1]; } } - //dateString contains unexpected format. use localized, absolute date. - return DisplayUtils.unixTimeToHumanReadable(time); + //dateString contains unexpected format. fallback: use relative date time string from android api as is. + return dateString.toString(); } /** @@@ -274,67 -242,6 +262,67 @@@ } /** + * Determines if user set folder to grid or list view. If folder is not set itself, + * it finds a parent that is set (at least root is set). + * @param file + * @param storageManager + * @return + */ + public static boolean isGridView(OCFile file, FileDataStorageManager storageManager){ + if (file != null) { + OCFile fileToTest = file; + OCFile parentDir = null; + String parentPath = null; + + SharedPreferences setting = MainApp.getAppContext().getSharedPreferences( + "viewMode", Context.MODE_PRIVATE); + + if (setting.contains(fileToTest.getRemoteId())) { + return setting.getBoolean(fileToTest.getRemoteId(), false); + } else { + do { + if (fileToTest.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) { + parentPath = new File(fileToTest.getRemotePath()).getParent(); + parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : + parentPath + OCFile.PATH_SEPARATOR; + parentDir = storageManager.getFileByPath(parentPath); + } else { + parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH); + } + + while (parentDir == null) { + parentPath = new File(parentPath).getParent(); + parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : + parentPath + OCFile.PATH_SEPARATOR; + parentDir = storageManager.getFileByPath(parentPath); + } + fileToTest = parentDir; + } while (endWhile(parentDir, setting)); + return setting.getBoolean(fileToTest.getRemoteId(), false); + } + } else { + return false; + } + } + + private static boolean endWhile(OCFile parentDir, SharedPreferences setting) { + if (parentDir.getRemotePath().compareToIgnoreCase(OCFile.ROOT_PATH) == 0) { + return false; + } else { + return !setting.contains(parentDir.getRemoteId()); + } + } + + public static void setViewMode(OCFile file, boolean setGrid){ + SharedPreferences setting = MainApp.getAppContext().getSharedPreferences( + "viewMode", Context.MODE_PRIVATE); + + SharedPreferences.Editor editor = setting.edit(); + editor.putBoolean(file.getRemoteId(), setGrid); + editor.commit(); + } + + /** * sets the coloring of the given progress bar to color_accent. * * @param progressBar the progress bar to be colored diff --combined src/com/owncloud/android/utils/FileStorageUtils.java index 757f896c,1740ca58..47a46d2c --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@@ -21,11 -21,8 +21,11 @@@ package com.owncloud.android.utils; import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.List; import java.util.Vector; import third_parties.daveKoeller.AlphanumComparator; @@@ -35,6 -32,7 +35,7 @@@ import com.owncloud.android.R import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.lib.resources.files.RemoteFile; + import android.accounts.Account; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; @@@ -56,8 -54,6 +57,6 @@@ public class FileStorageUtils public static Boolean mSortAscending = true; - //private static final String LOG_TAG = "FileStorageUtils"; - public static final String getSavePath(String accountName) { File sdCard = Environment.getExternalStorageDirectory(); return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/" + Uri.encode(accountName, "@"); @@@ -123,7 -119,7 +122,7 @@@ * Creates and populates a new {@link OCFile} object with the data read from the server. * * @param remote remote file read from the server (remote file or folder). - * @return New OCFile instance representing the remote resource described by we. + * @return New OCFile instance representing the remote resource described by remote. */ public static OCFile fillOCFile(RemoteFile remote) { OCFile file = new OCFile(remote.getRemotePath()); @@@ -158,13 -154,13 +157,13 @@@ /** * Sorts all filenames, regarding last user decision */ - public static Vector sortFolder(Vector files){ + public static Vector sortOcFolder(Vector files){ switch (mSortOrder){ case 0: - files = FileStorageUtils.sortByName(files); + files = FileStorageUtils.sortOCFilesByName(files); break; case 1: - files = FileStorageUtils.sortByDate(files); + files = FileStorageUtils.sortOCFilesByDate(files); break; case 2: // mFiles = FileStorageUtils.sortBySize(mSortAscending); @@@ -173,31 -169,12 +172,31 @@@ return files; } + + /** + * Sorts all filenames, regarding last user decision + */ + public static File[] sortLocalFolder(File[] files){ + switch (mSortOrder){ + case 0: + files = FileStorageUtils.sortLocalFilesByName(files); + break; + case 1: + files = FileStorageUtils.sortLocalFilesByDate(files); + break; + case 2: + // mFiles = FileStorageUtils.sortBySize(mSortAscending); + break; + } + + return files; + } /** * Sorts list by Date * @param files */ - public static Vector sortByDate(Vector files){ + public static Vector sortOCFilesByDate(Vector files){ final Integer val; if (mSortAscending){ val = 1; @@@ -227,43 -204,6 +226,43 @@@ return files; } + /** + * Sorts list by Date + * @param filesArray + */ + public static File[] sortLocalFilesByDate(File[] filesArray){ + final Integer val; + if (mSortAscending){ + val = 1; + } else { + val = -1; + } + + List files = new ArrayList(Arrays.asList(filesArray)); + + Collections.sort(files, new Comparator() { + public int compare(File o1, File o2) { + if (o1.isDirectory() && o2.isDirectory()) { + Long obj1 = o1.lastModified(); + return val * obj1.compareTo(o2.lastModified()); + } + else if (o1.isDirectory()) { + return -1; + } else if (o2.isDirectory()) { + return 1; + } else if (o1.lastModified() == 0 || o2.lastModified() == 0){ + return 0; + } else { + Long obj1 = o1.lastModified(); + return val * obj1.compareTo(o2.lastModified()); + } + } + }); + + File[] returnArray = new File[1]; + return files.toArray(returnArray); + } + // /** // * Sorts list by Size // * @param sortAscending true: ascending, false: descending @@@ -302,7 -242,7 +301,7 @@@ * Sorts list by Name * @param files files to sort */ - public static Vector sortByName(Vector files){ + public static Vector sortOCFilesByName(Vector files){ final Integer val; if (mSortAscending){ val = 1; @@@ -325,38 -265,6 +324,38 @@@ return files; } + + /** + * Sorts list by Name + * @param filesArray files to sort + */ + public static File[] sortLocalFilesByName(File[] filesArray){ + final Integer val; + if (mSortAscending){ + val = 1; + } else { + val = -1; + } + + List files = new ArrayList(Arrays.asList(filesArray)); + + Collections.sort(files, new Comparator() { + public int compare(File o1, File o2) { + if (o1.isDirectory() && o2.isDirectory()) { + return val * o1.getPath().toLowerCase().compareTo(o2.getPath().toLowerCase()); + } else if (o1.isDirectory()) { + return -1; + } else if (o2.isDirectory()) { + return 1; + } + return val * new AlphanumComparator().compare(o1.getPath().toLowerCase(), + o2.getPath().toLowerCase()); + } + }); + + File[] returnArray = new File[1]; + return files.toArray(returnArray); + } /** * Local Folder size @@@ -393,5 -301,33 +392,33 @@@ String result = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase()); return (result != null) ? result : ""; } - + + /** + * Scans the default location for saving local copies of files searching for + * a 'lost' file with the same full name as the {@link OCFile} received as + * parameter. + * + * This method helps to keep linked local copies of the files when the app is uninstalled, and then + * reinstalled in the device. OR after the cache of the app was deleted in system settings. + * + * The method is assuming that all the local changes in the file where synchronized in the past. This is dangerous, + * but assuming the contrary could lead to massive unnecessary synchronizations of downloaded file after deleting + * the app cache. + * + * This should be changed in the near future to avoid any chance of data loss, but we need to add some options + * to limit hard automatic synchronizations to wifi, unless the user wants otherwise. + * + * @param file File to associate a possible 'lost' local file. + * @param account Account holding file. + */ + public static void searchForLocalFileInDefaultPath(OCFile file, Account account) { + if (file.getStoragePath() == null && !file.isFolder()) { + File f = new File(FileStorageUtils.getDefaultSavePathFor(account.name, file)); + if (f.exists()) { + file.setStoragePath(f.getAbsolutePath()); + file.setLastSyncDateForData(f.lastModified()); + } + } + } + }