From: tobiasKaminsky Date: Thu, 21 May 2015 19:12:44 +0000 (+0200) Subject: Merge remote-tracking branch 'upstream/develop' into navigationDrawer_update X-Git-Tag: oc-android-1.7.2~1^2~23^2~42 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/6d4207a06bfa0a1bb19b8d5942b799dadfbbe608?ds=inline;hp=--cc Merge remote-tracking branch 'upstream/develop' into navigationDrawer_update --- 6d4207a06bfa0a1bb19b8d5942b799dadfbbe608 diff --cc res/menu/main_menu.xml index 2ed7bb86,d35eba01..ab41e4ba --- a/res/menu/main_menu.xml +++ b/res/menu/main_menu.xml @@@ -30,13 -31,36 +31,15 @@@ android:icon="@drawable/ic_action_create_dir" android:orderInCategory="2" android:showAsAction="always" - android:title="@string/actionbar_mkdir"/> + android:title="@string/actionbar_mkdir" + android:contentDescription="@string/actionbar_mkdir"/> - - - + android:title="@string/actionbar_sort" - android:contentDescription="@string/actionbar_sort"/> ++ android:contentDescription="@string/actionbar_sort"/>/> diff --cc res/values/colors.xml index b4693f99,6aeb6acf..b42538fd --- a/res/values/colors.xml +++ b/res/values/colors.xml @@@ -22,6 -22,7 +22,8 @@@ #DDDDDD #00ddff #989898 + #000000 + #303030 + #fff0f0f0 diff --cc res/values/strings.xml index b1a2b91c,0e2e9458..b9aeea49 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@@ -332,10 -328,12 +336,16 @@@ Security Upload Video Path + Download of %1$s folder could not be completed - %1$s shared \"%2$s\" with you + shared + with you ++ + %1$s %2$s >>%3$s<< %4$s - Username + Refresh connection + Server address + Not enough memory + ++ Username diff --cc src/com/owncloud/android/MainApp.java index 0dcf1da0,657469ef..0d725e5a --- a/src/com/owncloud/android/MainApp.java +++ b/src/com/owncloud/android/MainApp.java @@@ -119,12 -170,26 +174,34 @@@ public class MainApp extends Applicatio public static String getLogName() { return getAppContext().getResources().getString(R.string.log_name); } + + public static void showOnlyFilesOnDevice(boolean state){ + mOnlyOnDevice = state; + } + + public static boolean getOnlyOnDevice(){ + return mOnlyOnDevice; + } + + // user agent + public static String getUserAgent() { + String appString = getAppContext().getResources().getString(R.string.user_agent); + String packageName = getAppContext().getPackageName(); + String version = ""; + + PackageInfo pInfo = null; + try { + pInfo = getAppContext().getPackageManager().getPackageInfo(packageName, 0); + if (pInfo != null) { + version = pInfo.versionName; + } + } catch (PackageManager.NameNotFoundException e) { + Log_OC.e(TAG, "Trying to get packageName", e.getCause()); + } + + // Mozilla/5.0 (Android) ownCloud-android/1.7.0 + String userAgent = String.format(appString, version); + + return userAgent; + } } diff --cc src/com/owncloud/android/datamodel/FileDataStorageManager.java index fef48024,c32dbbce..42f2c780 --- a/src/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/com/owncloud/android/datamodel/FileDataStorageManager.java @@@ -38,15 -48,8 +40,16 @@@ import android.content.OperationApplica import android.database.Cursor; import android.net.Uri; import android.os.RemoteException; + import android.provider.MediaStore; +import com.owncloud.android.MainApp; +import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.lib.resources.files.FileUtils; +import com.owncloud.android.lib.resources.shares.OCShare; +import com.owncloud.android.lib.resources.shares.ShareType; +import com.owncloud.android.utils.FileStorageUtils; + public class FileDataStorageManager { public static final int ROOT_PARENT_ID = 0; @@@ -539,10 -543,11 +543,11 @@@ private boolean removeLocalFolder(OCFile folder) { boolean success = true; - File localFolder = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, folder)); + String localFolderPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, folder); + File localFolder = new File(localFolderPath); if (localFolder.exists()) { // stage 1: remove the local files already registered in the files database - Vector files = getFolderContent(folder.getFileId()); + Vector files = getFolderContent(folder.getFileId(), false); if (files != null) { for (OCFile file : files) { if (file.isFolder()) { diff --cc src/com/owncloud/android/operations/RefreshFolderOperation.java index 00000000,50a35fdb..ecc642e9 mode 000000,100644..100644 --- a/src/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/src/com/owncloud/android/operations/RefreshFolderOperation.java @@@ -1,0 -1,613 +1,613 @@@ + /** + * ownCloud Android client application + * + * @author David A. Velasco + * Copyright (C) 2015 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + package com.owncloud.android.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; + + import com.owncloud.android.lib.common.OwnCloudClient; + import com.owncloud.android.lib.resources.shares.OCShare; + import com.owncloud.android.lib.common.operations.RemoteOperation; + import com.owncloud.android.lib.common.operations.RemoteOperationResult; + 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; + + import com.owncloud.android.syncadapter.FileSyncAdapter; + import com.owncloud.android.utils.FileStorageUtils; + + + + /** + * Remote operation performing the synchronization of the list of files contained + * in a folder identified with its remote path. + * + * 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. + */ + public class RefreshFolderOperation extends RemoteOperation { + + private static final String TAG = RefreshFolderOperation.class.getSimpleName(); + + public static final String EVENT_SINGLE_FOLDER_CONTENTS_SYNCED = + RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_CONTENTS_SYNCED"; + public static final String EVENT_SINGLE_FOLDER_SHARES_SYNCED = + RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_SHARES_SYNCED"; + + /** Time stamp for the synchronization process in progress */ + private long mCurrentSyncTime; + + /** Remote folder to synchronize */ + private OCFile mLocalFolder; + + /** Access to the local database */ + private FileDataStorageManager mStorageManager; + + /** Account where the file to synchronize belongs */ + private Account mAccount; + + /** Android context; necessary to send requests to the download service */ + private Context mContext; + + /** Files and folders contained in the synchronized folder after a successful operation */ + private List mChildren; + + /** Counter of conflicts found between local and remote files */ + private int mConflictsFound; + + /** Counter of failed operations in synchronization of kept-in-sync files */ + private int mFailsInFavouritesFound; + + /** + * Map of remote and local paths to files that where locally stored in a location + * out of the ownCloud folder and couldn't be copied automatically into it + **/ + private Map mForgottenLocalFiles; + + /** 'True' means that this operation is part of a full account synchronization */ + private boolean mSyncFullAccount; + + /** 'True' means that Share resources bound to the files into should be refreshed also */ + private boolean mIsShareSupported; + + /** 'True' means that the remote folder changed and should be fetched */ + private boolean mRemoteFolderChanged; + + /** 'True' means that Etag will be ignored */ + private boolean mIgnoreETag; + + + /** + * Creates a new instance of {@link RefreshFolderOperation}. + * + * @param folder Folder to synchronize. + * @param currentSyncTime Time stamp for the synchronization process in progress. + * @param syncFullAccount 'True' means that this operation is part of a full account + * synchronization. + * @param isShareSupported 'True' means that the server supports the sharing API. + * @param ignoreETag 'True' means that the content of the remote folder should + * be fetched and updated even though the 'eTag' did not + * change. + * @param dataStorageManager Interface with the local database. + * @param account ownCloud account where the folder is located. + * @param context Application context. + */ + public RefreshFolderOperation(OCFile folder, + long currentSyncTime, + boolean syncFullAccount, + boolean isShareSupported, + boolean ignoreETag, + FileDataStorageManager dataStorageManager, + Account account, + Context context) { + mLocalFolder = folder; + mCurrentSyncTime = currentSyncTime; + mSyncFullAccount = syncFullAccount; + mIsShareSupported = isShareSupported; + mStorageManager = dataStorageManager; + mAccount = account; + mContext = context; + mForgottenLocalFiles = new HashMap(); + mRemoteFolderChanged = false; + mIgnoreETag = ignoreETag; + } + + + public int getConflictsFound() { + return mConflictsFound; + } + + public int getFailsInFavouritesFound() { + return mFailsInFavouritesFound; + } + + public Map getForgottenLocalFiles() { + return mForgottenLocalFiles; + } + + /** + * Returns the list of files and folders contained in the synchronized folder, + * if called after synchronization is complete. + * + * @return List of files and folders contained in the synchronized folder. + */ + public List getChildren() { + return mChildren; + } + + /** + * Performs the synchronization. + * + * {@inheritDoc} + */ + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperationResult result = null; + mFailsInFavouritesFound = 0; + mConflictsFound = 0; + mForgottenLocalFiles.clear(); + + if (FileUtils.PATH_SEPARATOR.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount) { + updateOCVersion(client); + } + + result = checkForChanges(client); + + if (result.isSuccess()) { + if (mRemoteFolderChanged) { + result = fetchAndSyncRemoteFolder(client); + } else { - mChildren = mStorageManager.getFolderContent(mLocalFolder); ++ mChildren = mStorageManager.getFolderContent(mLocalFolder, false); + } + } + + if (!mSyncFullAccount) { + sendLocalBroadcast( + EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result + ); + } + + if (result.isSuccess() && mIsShareSupported && !mSyncFullAccount) { + refreshSharesForFolder(client); // share result is ignored + } + + if (!mSyncFullAccount) { + sendLocalBroadcast( + EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result + ); + } + + return result; + + } + + + private void updateOCVersion(OwnCloudClient client) { + UpdateOCVersionOperation update = new UpdateOCVersionOperation(mAccount, mContext); + RemoteOperationResult result = update.execute(client); + if (result.isSuccess()) { + mIsShareSupported = update.getOCVersion().isSharedSupported(); + } + } + + + private RemoteOperationResult checkForChanges(OwnCloudClient client) { + mRemoteFolderChanged = true; + RemoteOperationResult result = null; + String remotePath = null; + + remotePath = mLocalFolder.getRemotePath(); + Log_OC.d(TAG, "Checking changes in " + mAccount.name + remotePath); + + // remote request + ReadRemoteFileOperation operation = new ReadRemoteFileOperation(remotePath); + result = operation.execute(client); + if (result.isSuccess()){ + OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0)); + + if (!mIgnoreETag) { + // check if remote and local folder are different + mRemoteFolderChanged = + !(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag())); + } + + result = new RemoteOperationResult(ResultCode.OK); + + Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " + + (mRemoteFolderChanged ? "changed" : "not changed")); + + } else { + // check failed + if (result.getCode() == ResultCode.FILE_NOT_FOUND) { + removeLocalFolder(); + } + if (result.isException()) { + Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " + + result.getLogMessage(), result.getException()); + } else { + Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " + + result.getLogMessage()); + } + } + + return result; + } + + + private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) { + String remotePath = mLocalFolder.getRemotePath(); + ReadRemoteFolderOperation operation = new ReadRemoteFolderOperation(remotePath); + RemoteOperationResult result = operation.execute(client); + Log_OC.d(TAG, "Synchronizing " + mAccount.name + remotePath); + + if (result.isSuccess()) { + synchronizeData(result.getData(), client); + if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { + result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); + // should be a different result code, but will do the job + } + } else { + if (result.getCode() == ResultCode.FILE_NOT_FOUND) + removeLocalFolder(); + } + + return result; + } + + + private void removeLocalFolder() { + if (mStorageManager.fileExists(mLocalFolder.getFileId())) { + String currentSavePath = FileStorageUtils.getSavePath(mAccount.name); + mStorageManager.removeFolder( + mLocalFolder, + true, + ( mLocalFolder.isDown() && + mLocalFolder.getStoragePath().startsWith(currentSavePath) + ) + ); + } + } + + + /** + * Synchronizes the data retrieved from the server about the contents of the target folder + * with the current data in the local database. + * + * Grants that mChildren is updated with fresh data after execution. + * + * @param folderAndFiles Remote folder and children files in Folder + * + * @param client Client instance to the remote server where the data were + * retrieved. + * @return 'True' when any change was made in the local data, 'false' otherwise + */ + private void synchronizeData(ArrayList folderAndFiles, OwnCloudClient client) { + // get 'fresh data' from the database + mLocalFolder = mStorageManager.getFileByPath(mLocalFolder.getRemotePath()); + + // parse data from remote folder + OCFile remoteFolder = fillOCFile((RemoteFile)folderAndFiles.get(0)); + remoteFolder.setParentId(mLocalFolder.getParentId()); + remoteFolder.setFileId(mLocalFolder.getFileId()); + + 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(); + + // get current data about local contents of the folder to synchronize - List localFiles = mStorageManager.getFolderContent(mLocalFolder); ++ List localFiles = mStorageManager.getFolderContent(mLocalFolder, true); + 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; + for (int i=1; i filesToSyncContents, OwnCloudClient client + ) { + RemoteOperationResult contentsResult = null; + for (SynchronizeFileOperation op: filesToSyncContents) { + contentsResult = op.execute(mStorageManager, mContext); // async + if (!contentsResult.isSuccess()) { + if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) { + mConflictsFound++; + } else { + mFailsInFavouritesFound++; + if (contentsResult.getException() != null) { + Log_OC.e(TAG, "Error while synchronizing favourites : " + + contentsResult.getLogMessage(), contentsResult.getException()); + } else { + Log_OC.e(TAG, "Error while synchronizing favourites : " + + contentsResult.getLogMessage()); + } + } + } // won't let these fails break the synchronization process + } + } + + + public boolean isMultiStatus(int status) { + return (status == HttpStatus.SC_MULTI_STATUS); + } + + /** + * 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. + */ + 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; + } + + + /** + * Checks the storage path of the OCFile received as parameter. + * If it's out of the local ownCloud folder, tries to copy the file inside it. + * + * If the copy fails, the link to the local file is nullified. The account of forgotten + * files is kept in {@link #mForgottenLocalFiles} + *) + * @param file File to check and fix. + */ + private void checkAndFixForeignStoragePath(OCFile file) { + String storagePath = file.getStoragePath(); + String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file); + if (storagePath != null && !storagePath.equals(expectedPath)) { + /// fix storagePaths out of the local ownCloud folder + File originalFile = new File(storagePath); + if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) { + mForgottenLocalFiles.put(file.getRemotePath(), storagePath); + file.setStoragePath(null); + + } else { + InputStream in = null; + OutputStream out = null; + try { + File expectedFile = new File(expectedPath); + File expectedParent = expectedFile.getParentFile(); + expectedParent.mkdirs(); + if (!expectedParent.isDirectory()) { + throw new IOException( + "Unexpected error: parent directory could not be created" + ); + } + expectedFile.createNewFile(); + if (!expectedFile.isFile()) { + throw new IOException("Unexpected error: target file could not be created"); + } + in = new FileInputStream(originalFile); + out = new FileOutputStream(expectedFile); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 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; + + // remote request + GetRemoteSharesForFileOperation operation = + new GetRemoteSharesForFileOperation(mLocalFolder.getRemotePath(), false, true); + result = operation.execute(client); + + if (result.isSuccess()) { + // update local database + ArrayList shares = new ArrayList(); + for(Object obj: result.getData()) { + shares.add((OCShare) obj); + } + mStorageManager.saveSharesInFolder(shares, mLocalFolder); + } + + return 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. + * + * @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. + * + * @param event + * @param dirRemotePath Remote path of a folder that was just synchronized + * (with or without success) + * @param result + */ + private void sendLocalBroadcast( + String event, String dirRemotePath, RemoteOperationResult result + ) { + Log_OC.d(TAG, "Send broadcast " + event); + Intent intent = new Intent(event); + intent.putExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME, mAccount.name); + if (dirRemotePath != null) { + intent.putExtra(FileSyncAdapter.EXTRA_FOLDER_PATH, dirRemotePath); + } + intent.putExtra(FileSyncAdapter.EXTRA_RESULT, result); + mContext.sendStickyBroadcast(intent); + //LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent); + } + + + public boolean getRemoteFolderChanged() { + return mRemoteFolderChanged; + } + + } diff --cc src/com/owncloud/android/operations/SynchronizeFolderOperation.java index 90921e65,286b5ea9..9e1d13b9 --- a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java @@@ -35,12 -24,13 +38,14 @@@ 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; + import com.owncloud.android.files.services.FileDownloader; import com.owncloud.android.lib.common.OwnCloudClient; + import com.owncloud.android.lib.common.operations.OperationCancelledException; +import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.lib.common.utils.Log_OC; @@@ -48,11 -37,19 +53,22 @@@ import com.owncloud.android.lib.resourc import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation; import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation; import com.owncloud.android.lib.resources.files.RemoteFile; +import com.owncloud.android.lib.resources.shares.GetRemoteSharesForFileOperation; +import com.owncloud.android.lib.resources.shares.OCShare; +import com.owncloud.android.syncadapter.FileSyncAdapter; + import com.owncloud.android.operations.common.SyncOperation; + import com.owncloud.android.services.OperationsService; import com.owncloud.android.utils.FileStorageUtils; + import java.io.File; + import java.util.ArrayList; + import java.util.HashMap; + import java.util.List; + import java.util.Map; + import java.util.Vector; + import java.util.concurrent.atomic.AtomicBoolean; + + //import android.support.v4.content.LocalBroadcastManager; /** @@@ -330,15 -283,21 +302,21 @@@ public class SynchronizeFolderOperatio OCFile remoteFolder = 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(); + mFilesForDirectDownload.clear(); + mFilesToSyncContentsWithoutUpload.clear(); + mFavouriteFilesToSyncContents.clear(); + + if (mCancellationRequested.get()) { + throw new OperationCancelledException(); + } // get current data about local contents of the folder to synchronize - List localFiles = mStorageManager.getFolderContent(mLocalFolder, false); - List localFiles = storageManager.getFolderContent(mLocalFolder); ++ List localFiles = storageManager.getFolderContent(mLocalFolder, true); Map localFilesMap = new HashMap(localFiles.size()); for (OCFile file : localFiles) { localFilesMap.put(file.getRemotePath(), file); @@@ -406,12 -386,53 +405,54 @@@ } // save updated contents in local database - mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values()); + storageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values()); + + } + + + private void prepareOpsFromLocalKnowledge() throws OperationCancelledException { - List children = getStorageManager().getFolderContent(mLocalFolder); ++ // TODO TOBI ist das richtig? ++ List children = getStorageManager().getFolderContent(mLocalFolder, true); + for (OCFile child : children) { + /// classify file to sync/download contents later + if (child.isFolder()) { + /// to download children files recursively + synchronized(mCancellationRequested) { + if (mCancellationRequested.get()) { + throw new OperationCancelledException(); + } + startSyncFolderOperation(child.getRemotePath()); + } - // request for the synchronization of file contents AFTER saving current remote properties - startContentSynchronizations(filesToSyncContents, client); + } else { + /// prepare limited synchronization for regular files + if (!child.isDown()) { + mFilesForDirectDownload.add(child); + } + } + } + } - mChildren = updatedFiles; + + private void syncContents(OwnCloudClient client) throws OperationCancelledException { + startDirectDownloads(); + startContentSynchronizations(mFilesToSyncContentsWithoutUpload, client); + startContentSynchronizations(mFavouriteFilesToSyncContents, client); + } + + + private void startDirectDownloads() throws OperationCancelledException { + for (OCFile file : mFilesForDirectDownload) { + synchronized(mCancellationRequested) { + if (mCancellationRequested.get()) { + throw new OperationCancelledException(); + } + Intent i = new Intent(mContext, FileDownloader.class); + i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount); + i.putExtra(FileDownloader.EXTRA_FILE, file); + mContext.startService(i); + } + } } /** diff --cc src/com/owncloud/android/ui/activity/FileDisplayActivity.java index 50fbe90f,3061ff44..48336d79 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@@ -24,11 -27,8 +27,9 @@@ import java.io.File import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; import android.annotation.TargetApi; import android.app.AlertDialog; - import android.app.Dialog; - import android.app.ProgressDialog; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@@ -46,13 -44,9 +46,10 @@@ import android.database.Cursor import android.net.Uri; import android.os.Build; import android.os.Bundle; - import android.os.Environment; import android.os.IBinder; import android.preference.PreferenceManager; - import android.provider.DocumentsContract; - import android.provider.MediaStore; import android.provider.OpenableColumns; +import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; @@@ -104,8 -90,7 +101,9 @@@ import com.owncloud.android.operations. import com.owncloud.android.operations.UnshareLinkOperation; import com.owncloud.android.services.observer.FileObserverService; import com.owncloud.android.syncadapter.FileSyncAdapter; +import com.owncloud.android.ui.adapter.FileListListAdapter; +import com.owncloud.android.ui.adapter.NavigationDrawerListAdapter; + import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; import com.owncloud.android.ui.dialog.CreateFolderDialogFragment; import com.owncloud.android.ui.dialog.SslUntrustedCertDialog; import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener; @@@ -166,30 -146,22 +159,26 @@@ OnSslUntrustedCertListener, OnEnforceab private boolean mSyncInProgress = false; - private String DIALOG_UNTRUSTED_CERT; - + private static String DIALOG_UNTRUSTED_CERT = "DIALOG_UNTRUSTED_CERT"; + private 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 DrawerLayout mDrawerLayout; + private ActionBarDrawerToggle mDrawerToggle; + private boolean showAccounts = false; + @Override protected void onCreate(Bundle savedInstanceState) { - Log_OC.d(TAG, "onCreate() start"); + Log_OC.v(TAG, "onCreate() start"); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - + super.onCreate(savedInstanceState); // this calls onAccountChanged() when ownCloud Account is valid - // PIN CODE request ; best location is to decide, let's try this first - if (getIntent().getAction() != null && getIntent().getAction().equals(Intent.ACTION_MAIN) && savedInstanceState == null) { - requestPinCode(); - } else if (getIntent().getAction() == null && savedInstanceState == null) { - requestPinCode(); - } - - /// grant that FileObserverService is watching favourite files + /// grant that FileObserverService is watching favorite files if (savedInstanceState == null) { Intent initObserversIntent = FileObserverService.makeInitIntent(this); startService(initObserversIntent); @@@ -302,37 -192,21 +291,39 @@@ // Action bar setup mDirectories = new CustomArrayAdapter(this, R.layout.sherlock_spinner_dropdown_item); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation + getSupportActionBar().setDisplayShowCustomEnabled(true); // CRUCIAL - for displaying your custom actionbar + getSupportActionBar().setDisplayShowTitleEnabled(true); setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/); // always AFTER setContentView(...) ; to work around bug in its implementation + mDrawerToggle.syncState(); + setBackgroundText(); - Log_OC.d(TAG, "onCreate() end"); + Log_OC.v(TAG, "onCreate() end"); } @Override protected void onStart() { + Log_OC.v(TAG, "onStart() start"); super.onStart(); getSupportActionBar().setIcon(DisplayUtils.getSeasonalIconId()); + Log_OC.v(TAG, "onStart() end"); } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + // Sync the toggle state after onRestoreInstanceState has occurred. + mDrawerToggle.syncState(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mDrawerToggle.onConfigurationChanged(newConfig); + } @Override protected void onDestroy() { @@@ -595,22 -470,38 +588,23 @@@ boolean retval = true; switch (item.getItemId()) { case R.id.action_create_dir: { - CreateFolderDialogFragment dialog = - CreateFolderDialogFragment.newInstance(getCurrentDir()); - dialog.show(getSupportFragmentManager(), "createdirdialog"); + 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: { - showDialog(DIALOG_CHOOSE_UPLOAD_SOURCE); + UploadSourceDialogFragment dialog = UploadSourceDialogFragment.newInstance(getAccount()); + dialog.show(getSupportFragmentManager(), DIALOG_UPLOAD_SOURCE); + break; } - case R.id.action_settings: { - Intent settingsIntent = new Intent(this, Preferences.class); - startActivity(settingsIntent); - break; - } - case R.id.action_logger: { - Intent loggerIntent = new Intent(getApplicationContext(),LogHistoryActivity.class); - startActivity(loggerIntent); - break; - } case android.R.id.home: { - FileFragment second = getSecondFragment(); - OCFile currentDir = getCurrentDir(); - if((currentDir != null && currentDir.getParentId() != 0) || - (second != null && second.getFile() != null)) { - onBackPressed(); - + if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) { + mDrawerLayout.closeDrawer(GravityCompat.START); + } else { + mDrawerLayout.openDrawer(GravityCompat.START); } + // TODO add hamburger to left of android.R.id.home break; } case R.id.action_sort: { @@@ -1470,12 -1234,14 +1337,16 @@@ // only list of files - set for browsing through folders OCFile currentDir = getCurrentDir(); boolean noRoot = (currentDir != null && currentDir.getParentId() != 0); - actionBar.setDisplayHomeAsUpEnabled(noRoot); - actionBar.setDisplayShowTitleEnabled(!noRoot); +// actionBar.setDisplayHomeAsUpEnabled(noRoot); +// actionBar.setDisplayShowTitleEnabled(!noRoot); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowTitleEnabled(true); if (!noRoot) { actionBar.setTitle(getString(R.string.default_display_name_for_root_folder)); + View actionBarTitleView = getWindow().getDecorView().findViewById(actionBarTitleId); + if (actionBarTitleView != null) { // it's null in Android 2.x + actionBarTitleView.setContentDescription(getString(R.string.default_display_name_for_root_folder)); + } } actionBar.setNavigationMode(!noRoot ? ActionBar.NAVIGATION_MODE_STANDARD : ActionBar.NAVIGATION_MODE_LIST); actionBar.setListNavigationCallbacks(mDirectories, this); // assuming mDirectories is updated @@@ -1875,7 -1634,8 +1739,8 @@@ */ public void showUntrustedCertDialog(RemoteOperationResult result) { // Show a dialog with the certificate info - SslUntrustedCertDialog dialog = SslUntrustedCertDialog.newInstanceForFullSslError((CertificateCombinedException)result.getException()); + SslUntrustedCertDialog dialog = SslUntrustedCertDialog.newInstanceForFullSslError( - (CertificateCombinedException)result.getException()); ++ (CertificateCombinedException) result.getException()); FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); dialog.show(ft, DIALOG_UNTRUSTED_CERT); @@@ -2003,14 -1763,5 +1868,15 @@@ private void sortByName(boolean ascending){ getListOfFilesFragment().sortByName(ascending); } + + public void restart(){ + Intent i = new Intent(this, FileDisplayActivity.class); + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(i); + } + + public void closeDrawer() { + mDrawerLayout.closeDrawers(); + } + } diff --cc src/com/owncloud/android/ui/activity/Uploader.java index f59c875a,9e56a30b..7336af96 --- a/src/com/owncloud/android/ui/activity/Uploader.java +++ b/src/com/owncloud/android/ui/activity/Uploader.java @@@ -60,11 -70,18 +70,25 @@@ import com.actionbarsherlock.view.MenuI import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountAuthenticator; +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.utils.Log_OC; ++import com.owncloud.android.MainApp; ++import com.owncloud.android.R; ++import com.owncloud.android.authentication.AccountAuthenticator; + import com.owncloud.android.datamodel.OCFile; + import com.owncloud.android.files.services.FileUploader; + import com.owncloud.android.lib.common.operations.RemoteOperation; + import com.owncloud.android.lib.common.operations.RemoteOperationResult; + import com.owncloud.android.lib.common.utils.Log_OC; + import com.owncloud.android.operations.CreateFolderOperation; + import com.owncloud.android.ui.dialog.CreateFolderDialogFragment; + import com.owncloud.android.ui.dialog.LoadingDialog; + import com.owncloud.android.utils.CopyTmpFileAsyncTask; import com.owncloud.android.utils.DisplayUtils; + import com.owncloud.android.utils.ErrorMessageAdapter; + /** * This can be used to upload things to an ownCloud instance. @@@ -240,7 -325,7 +332,7 @@@ public class Uploader extends FileActiv public void onItemClick(AdapterView parent, View view, int position, long id) { // click on folder in the list Log_OC.d(TAG, "on item click"); - Vector tmpfiles = mStorageManager.getFolderContent(mFile, false); - Vector tmpfiles = getStorageManager().getFolderContent(mFile); ++ Vector tmpfiles = getStorageManager().getFolderContent(mFile, false); if (tmpfiles.size() <= 0) return; // filter on dirtype Vector files = new Vector(); @@@ -310,12 -405,12 +412,12 @@@ actionBar.setHomeButtonEnabled(notRoot); String full_path = generatePath(mParents); - + Log_OC.d(TAG, "Populating view with content of : " + full_path); - mFile = mStorageManager.getFileByPath(full_path); + mFile = getStorageManager().getFileByPath(full_path); if (mFile != null) { - Vector files = mStorageManager.getFolderContent(mFile, false); - Vector files = getStorageManager().getFolderContent(mFile); ++ Vector files = getStorageManager().getFolderContent(mFile, false); List> data = new LinkedList>(); for (OCFile f : files) { HashMap h = new HashMap(); diff --cc src/com/owncloud/android/ui/adapter/FileListListAdapter.java index db015de5,2c1aa9e9..c03003b5 --- a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java @@@ -351,7 -414,10 +414,10 @@@ public class FileListListAdapter extend mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); } if (mStorageManager != null) { - mFiles = mStorageManager.getFolderContent(mFile, onlyOnDevice); - mFiles = mStorageManager.getFolderContent(mFile); ++ mFiles = mStorageManager.getFolderContent(mFile, false); + mFilesOrig.clear(); + mFilesOrig.addAll(mFiles); + if (mJustFolders) { mFiles = getFolders(mFiles); } diff --cc src/com/owncloud/android/ui/fragment/OCFileListFragment.java index 5061526d,60ac78d9..9430f112 --- a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java @@@ -75,11 -74,10 +77,11 @@@ public class OCFileListFragment extend private OCFile mFile = null; private FileListListAdapter mAdapter; - private View mFooterView; + private boolean mJustFolders; private OCFile mTargetFile; - + + /** * {@inheritDoc} @@@ -392,58 -387,69 +395,69 @@@ directory = storageManager.getFileById(directory.getParentId()); } - mAdapter.swapDirectory(directory, storageManager); + mAdapter.swapDirectory(directory, storageManager, onlyOnDevice); if (mFile == null || !mFile.equals(directory)) { - mList.setSelectionFromTop(0, 0); + mCurrentListView.setSelection(0); } mFile = directory; - - // Update Footer - TextView footerText = (TextView) mFooterView.findViewById(R.id.footerText); - Log_OC.d("footer", String.valueOf(System.currentTimeMillis())); - footerText.setText(generateFooterText(directory, onlyOnDevice)); - Log_OC.d("footer", String.valueOf(System.currentTimeMillis())); + + updateLayout(); + } } - - private String generateFooterText(OCFile directory, boolean onlyOnDevice) { - Integer files = 0; - Integer folders = 0; - FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); - Vector mFiles = storageManager.getFolderContent(mFile, onlyOnDevice); + private void updateLayout() { + if (!mJustFolders) { + int filesCount = 0, foldersCount = 0, imagesCount = 0; + int count = mAdapter.getCount(); + OCFile file; + for (int i=0; i < count ; i++) { + file = (OCFile) mAdapter.getItem(i); + if (file.isFolder()) { + foldersCount++; + } else { + filesCount++; + if (file.isImage()){ + imagesCount++; + } + } + } + // set footer text + setFooterText(generateFooterText(filesCount, foldersCount)); - for (OCFile ocFile : mFiles) { - if (ocFile.isFolder()) { - folders++; + // decide grid vs list view + if (((double)imagesCount / (double)filesCount) >= THUMBNAIL_THRESHOLD) { + switchToGridView(); } else { - files++; + switchToListView(); } } + } + private String generateFooterText(int filesCount, int foldersCount) { String output = ""; - - if (files > 0){ - if (files == 1) { - output = output + files.toString() + " " + getResources().getString(R.string.file_list_file); + if (filesCount > 0){ + if (filesCount == 1) { + output = output + filesCount + " " + getResources().getString(R.string.file_list_file); } else { - output = output + files.toString() + " " + getResources().getString(R.string.file_list_files); + output = output + filesCount + " " + getResources().getString(R.string.file_list_files); } } - if (folders > 0 && files > 0){ + if (foldersCount > 0 && filesCount > 0){ output = output + ", "; } - if (folders == 1) { - output = output + folders.toString() + " " + getResources().getString(R.string.file_list_folder); - } else if (folders > 1) { - output = output + folders.toString() + " " + getResources().getString(R.string.file_list_folders); + if (foldersCount == 1) { + output = output + foldersCount + " " + getResources().getString(R.string.file_list_folder); + } else if (foldersCount > 1) { + output = output + foldersCount + " " + getResources().getString(R.string.file_list_folders); } - + return output; } - + + public void sortByName(boolean descending) { - mAdapter.setSortOrder(FileListListAdapter.SORT_NAME, descending); + mAdapter.setSortOrder(FileStorageUtils.SORT_NAME, descending); } public void sortByDate(boolean descending) { @@@ -451,9 -457,7 +465,9 @@@ } public void sortBySize(boolean descending) { - mAdapter.setSortOrder(FileListListAdapter.SORT_SIZE, descending); + mAdapter.setSortOrder(FileStorageUtils.SORT_SIZE, descending); } - + + + } diff --cc src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java index 9af52aac,6e7f19d4..fc99d289 --- a/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java +++ b/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java @@@ -72,7 -77,10 +77,10 @@@ public class PreviewImagePagerAdapter e mAccount = account; mStorageManager = storageManager; - mImageFiles = mStorageManager.getFolderImages(parentFolder, onlyOnDevice); - mImageFiles = mStorageManager.getFolderImages(parentFolder); ++ mImageFiles = mStorageManager.getFolderImages(parentFolder, false); + + mImageFiles = FileStorageUtils.sortFolder(mImageFiles); + mObsoleteFragments = new HashSet(); mObsoletePositions = new HashSet(); mDownloadErrors = new HashSet(); diff --cc src/com/owncloud/android/utils/BitmapUtils.java index 87cc4889,7b9382e6..7af6960f --- a/src/com/owncloud/android/utils/BitmapUtils.java +++ b/src/com/owncloud/android/utils/BitmapUtils.java @@@ -171,43 -178,16 +178,56 @@@ public class BitmapUtils } /** + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * from: http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c + * + * @param integer h The hue + * @param Integer s The saturation + * @param Integer l The lightness + * @return Array The RGB representation + */ + public static int[] hslToRgb(Double h, Double s, Double l){ + Double r, g, b; + + if(s == 0){ + r = g = b = l; // achromatic + } else { + Double q = l < 0.5 ? l * (1 + s) : l + s - l * s; + Double p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3) * 255; + g = hue2rgb(p, q, h) * 255; + b = hue2rgb(p, q, h - 1/3) * 255; + } + + + int[] array = {r.intValue(), g.intValue(), b.intValue()}; + return array; + } + + private static Double hue2rgb(Double p, Double q, Double t){ + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + ++ ++ /** + * Checks if file passed is an image + * @param file + * @return true/false + */ + public static boolean isImage(File file) { + Uri selectedUri = Uri.fromFile(file); + String fileExtension = MimeTypeMap.getFileExtensionFromUrl(selectedUri.toString().toLowerCase()); + String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension); + + return (mimeType != null && mimeType.startsWith("image/")); + } }