along with this program. If not, see <http://www.gnu.org/licenses/>.\r
-->\r
<manifest package="com.owncloud.android"\r
- android:versionCode="103015"\r
- android:versionName="1.3.15" xmlns:android="http://schemas.android.com/apk/res/android">\r
+ android:versionCode="103016"\r
+ android:versionName="1.3.16" xmlns:android="http://schemas.android.com/apk/res/android">\r
\r
<uses-permission android:name="android.permission.GET_ACCOUNTS" />\r
<uses-permission android:name="android.permission.USE_CREDENTIALS" />\r
<activity android:name=".extensions.ExtensionsListActivity"></activity>\r
<activity android:name=".ui.activity.AccountSelectActivity" android:uiOptions="none" android:label="@string/prefs_accounts"></activity>\r
<activity android:name=".ui.activity.ConflictsResolveActivity"/>
-
+ <activity android:name=".ui.activity.GenericExplanationActivity"/>\r
+ <activity android:name=".ui.activity.ErrorsWhileCopyingHandlerActivity"/>\r
+
<service android:name=".files.services.FileUploader" >\r
</service>
<service android:name=".files.services.InstantUploadService" />
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ownCloud Android client application
+
+ Copyright (C) 2012 Bartek Przybylski
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/owncloud_white"
+ android:id="@+id/explanation"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="2"
+ android:padding="10dip"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
+ android:text="@string/text_placeholder"
+ />
+
+ <ListView
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="3"
+ android:padding="10dip"
+ />
+
+ <LinearLayout
+ android:id="@+id/buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal" >
+
+ <!-- 'OK' / 'CANCEL' BUTTONS CHANGE THEIR ORDER FROM ANDROID 4.0 ; THANKS, GOOGLE -->
+ <Button
+ android:id="@+id/cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/common_cancel" />
+
+ <Button
+ android:id="@+id/ok"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/common_ok" />
+
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ownCloud Android client application
+
+ Copyright (C) 2012 Bartek Przybylski
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/owncloud_white"
+ android:id="@+id/explanation"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="2"
+ android:padding="10dip"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
+ android:text="@string/text_placeholder"
+ />
+
+ <ListView
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="3"
+ android:padding="10dip"
+ />
+
+ <LinearLayout
+ android:id="@+id/buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/ok"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/common_ok" />
+
+ <Button
+ android:id="@+id/cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/common_cancel" />
+
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
<string name="sync_conflicts_in_favourites_content">%1$d kept-in-sync files could not be sync\'ed</string>
<string name="sync_fail_in_favourites_ticker">Kept-in-sync files failed</string>
<string name="sync_fail_in_favourites_content">Contents of %1$d files could not be sync\'ed (%2$d conflicts)</string>
+ <string name="sync_foreign_files_forgotten_ticker">Some local files were forgotten</string>
+ <string name="sync_foreign_files_forgotten_content">%1$d files out of the ownCloud directory could not be copied into</string>
+ <string name="sync_foreign_files_forgotten_explanation">"As of version 1.3.16, files uploaded from this device are copied into the local %1$s folder to prevent data loss when a single file is synced with multiple accounts.\n\nDue to this change, all files uploaded in previous versions of this app were copied into the %2$s folder. However, an error prevented the completion of this operation during account synchronization. You may either leave the file(s) as is and remove the link to %3$s, or move the file(s) into the %1$s directory and retain the link to %4$s.\n\nListed below are the local file(s), and the the remote file(s) in %5$s they were linked to.</string>
+
+ <string name="foreign_files_move">"Move all"</string>
+ <string name="foreign_files_success">"All files were moved"</string>
+ <string name="foreign_files_fail">"Some files could not be moved"</string>
+ <string name="foreign_files_local_text">"Local: %1$s"</string>
+ <string name="foreign_files_remote_text">"Remote: %1$s"</string>
+
+ <string name="upload_query_move_foreign_files">There is not space enough to copy the selected files into the %1$s folder. Would like to move them into instead? </string>
+
<string name="use_ssl">Use Secure Connection</string>
<string name="location_no_provider">%1$s cannot track your device. Please check your location settings</string>
<string name="conflict_keep_both">Keep both</string>
<string name="conflict_overwrite">Overwrite</string>
<string name="conflict_dont_upload">Don\'t upload</string>
+
+ <!-- we need to improve the communication of errors to the user -->
+ <string name="error__upload__local_file_not_copied">%1$s could not be copied to %2$s local directory</string>
+
</resources>
// click on folder in the list\r
Log.d(TAG, "on item click");\r
Vector<OCFile> tmpfiles = mStorageManager.getDirectoryContent(mFile);\r
- if (tmpfiles == null) return;\r
+ if (tmpfiles.size() <= 0) return;\r
// filter on dirtype\r
Vector<OCFile> files = new Vector<OCFile>();\r
for (OCFile f : tmpfiles)\r
mFile = mStorageManager.getFileByPath(full_path);\r
if (mFile != null) {\r
Vector<OCFile> files = mStorageManager.getDirectoryContent(mFile);\r
- if (files != null) {\r
+ if (files.size() > 0) {\r
List<HashMap<String, Object>> data = new LinkedList<HashMap<String,Object>>();\r
for (OCFile f : files) {\r
HashMap<String, Object> h = new HashMap<String, Object>();\r
boolean overriden = false;
ContentValues cv = new ContentValues();
cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp());
+ cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData());
cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp());
cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength());
cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype());
file = filesIt.next();
ContentValues cv = new ContentValues();
cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp());
+ cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData());
cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp());
cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength());
cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype());
@Override
public Vector<OCFile> getDirectoryContent(OCFile f) {
+ Vector<OCFile> ret = new Vector<OCFile>();
if (f != null && f.isDirectory() && f.getFileId() != -1) {
- Vector<OCFile> ret = new Vector<OCFile>();
Uri req_uri = Uri.withAppendedPath(
ProviderTableMeta.CONTENT_URI_DIR,
Collections.sort(ret);
- return ret;
}
- return null;
+ return ret;
}
private boolean fileExists(String cmp_key, String value) {
.getColumnIndex(ProviderTableMeta.FILE_CREATION)));
file.setModificationTimestamp(c.getLong(c
.getColumnIndex(ProviderTableMeta.FILE_MODIFIED)));
+ file.setModificationTimestampAtLastSyncForData(c.getLong(c
+ .getColumnIndex(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA)));
file.setLastSyncDateForProperties(c.getLong(c
.getColumnIndex(ProviderTableMeta.FILE_LAST_SYNC_DATE)));
file.setLastSyncDateForData(c.getLong(c.
// TODO consider possible failures
if (dir != null && dir.isDirectory() && dir.getFileId() != -1) {
Vector<OCFile> children = getDirectoryContent(dir);
- if (children != null) {
+ if (children.size() > 0) {
OCFile child = null;
for (int i=0; i<children.size(); i++) {
child = children.get(i);
private long mLength;
private long mCreationTimestamp;
private long mModifiedTimestamp;
+ private long mModifiedTimestampAtLastSyncForData;
private String mRemotePath;
private String mLocalPath;
private String mMimeType;
mLength = source.readLong();
mCreationTimestamp = source.readLong();
mModifiedTimestamp = source.readLong();
+ mModifiedTimestampAtLastSyncForData = source.readLong();
mRemotePath = source.readString();
mLocalPath = source.readString();
mMimeType = source.readString();
dest.writeLong(mLength);
dest.writeLong(mCreationTimestamp);
dest.writeLong(mModifiedTimestamp);
+ dest.writeLong(mModifiedTimestampAtLastSyncForData);
dest.writeString(mRemotePath);
dest.writeString(mLocalPath);
dest.writeString(mMimeType);
}
/**
- * Get a UNIX timestamp of the file modification time
- *
- * @return A UNIX timestamp of the modification time
+ * Get a UNIX timestamp of the file modification time.
+ *
+ * @return A UNIX timestamp of the modification time, corresponding to the value returned by the server
+ * in the last synchronization of the properties of this file.
*/
public long getModificationTimestamp() {
return mModifiedTimestamp;
/**
* Set a UNIX timestamp of the time the time the file was modified.
*
+ * To update with the value returned by the server in every synchronization of the properties
+ * of this file.
+ *
* @param modification_timestamp to set
*/
public void setModificationTimestamp(long modification_timestamp) {
mModifiedTimestamp = modification_timestamp;
}
+
+ /**
+ * Get a UNIX timestamp of the file modification time.
+ *
+ * @return A UNIX timestamp of the modification time, corresponding to the value returned by the server
+ * in the last synchronization of THE CONTENTS of this file.
+ */
+ public long getModificationTimestampAtLastSyncForData() {
+ return mModifiedTimestampAtLastSyncForData;
+ }
+
+ /**
+ * Set a UNIX timestamp of the time the time the file was modified.
+ *
+ * To update with the value returned by the server in every synchronization of THE CONTENTS
+ * of this file.
+ *
+ * @param modification_timestamp to set
+ */
+ public void setModificationTimestampAtLastSyncForData(long modificationTimestamp) {
+ mModifiedTimestampAtLastSyncForData = modificationTimestamp;
+ }
+
+
+
/**
* Returns the filename and "/" for the root directory
*
mLength = 0;
mCreationTimestamp = 0;
mModifiedTimestamp = 0;
+ mModifiedTimestampAtLastSyncForData = 0;
mLastSyncDateForProperties = 0;
mLastSyncDateForData = 0;
mKeepInSync = false;
public static final String AUTHORITY_FILES = "org.owncloud";\r
public static final String DB_FILE = "owncloud.db";\r
public static final String DB_NAME = "filelist";\r
- //public static final int DB_VERSION = 2;\r
- public static final int DB_VERSION = 3;\r
+ public static final int DB_VERSION = 4;\r
\r
private ProviderMeta() {\r
}\r
public static final String FILE_NAME = "filename";\r
public static final String FILE_CREATION = "created";\r
public static final String FILE_MODIFIED = "modified";\r
+ public static final String FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA = "modified_at_last_sync_for_data";\r
public static final String FILE_CONTENT_LENGTH = "content_length";\r
public static final String FILE_CONTENT_TYPE = "content_type";\r
public static final String FILE_STORAGE_PATH = "media_path";\r
// remove successfull uploading, ignore rest for reupload on reconnect
if (intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false)) {
DbHandler db = new DbHandler(context);
- String localPath = intent.getStringExtra(FileUploader.EXTRA_FILE_PATH);
+ String localPath = intent.getStringExtra(FileUploader.EXTRA_OLD_FILE_PATH);
if (!db.removeIUPendingFile(localPath,
intent.getStringExtra(FileUploader.ACCOUNT_NAME))) {
Log.w(TAG, "Tried to remove non existing instant upload file " + localPath);
file.setLastSyncDateForProperties(syncDate);\r
file.setLastSyncDateForData(syncDate);\r
file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp());\r
+ file.setModificationTimestampAtLastSyncForData(mCurrentDownload.getModificationTimestamp());\r
// file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available\r
file.setMimetype(mCurrentDownload.getMimeType());\r
file.setStoragePath(mCurrentDownload.getSavePath());\r
import com.owncloud.android.operations.ChunkedUploadFileOperation;
import com.owncloud.android.operations.RemoteOperationResult;
import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.ui.activity.FileDetailActivity;
import com.owncloud.android.ui.fragment.FileDetailFragment;
-import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.OwnCloudVersion;
import eu.alefzero.webdav.OnDatatransferProgressListener;
public static final String EXTRA_UPLOAD_RESULT = "RESULT";
public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH";
- public static final String EXTRA_FILE_PATH = "FILE_PATH";
+ public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH";
public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
public static final String KEY_FILE = "FILE";
public static final String KEY_UPLOAD_TYPE = "UPLOAD_TYPE";
public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE";
public static final String KEY_INSTANT_UPLOAD = "INSTANT_UPLOAD";
+ public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR";
+
+ 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 UPLOAD_SINGLE_FILE = 0;
public static final int UPLOAD_MULTIPLE_FILES = 1;
FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver());
boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
- boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false);
+ boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false);
+ int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_COPY);
boolean fixed = false;
if (isInstant) {
fixed = checkAndFixInstantUploadDirectory(storageManager); // MUST be done BEFORE calling obtainNewOCFileToUpload
for (int i=0; i < files.length; i++) {
uploadKey = buildRemoteName(account, files[i].getRemotePath());
if (chunked) {
- newUpload = new ChunkedUploadFileOperation(account, files[i], isInstant, forceOverwrite);
+ newUpload = new ChunkedUploadFileOperation(account, files[i], isInstant, forceOverwrite, localAction);
} else {
- newUpload = new UploadFileOperation(account, files[i], isInstant, forceOverwrite);
+ newUpload = new UploadFileOperation(account, files[i], isInstant, forceOverwrite, localAction);
}
if (fixed && i==0) {
newUpload.setRemoteFolderToBeCreated();
} catch (Exception e) {
result = new RemoteOperationResult(e);
- Log.i(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " + result.getLogMessage(), e);
+ Log.e(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " + result.getLogMessage(), e);
} finally {
if (propfind != null)
propfind.releaseConnection();
}
-
+ /// maybe this would be better as part of UploadFileOperation... or maybe all this method
if (mCurrentUpload.wasRenamed()) {
OCFile oldFile = mCurrentUpload.getOldFile();
- if (!oldFile.fileExists()) {
- // just a name coincidence
- file.setStoragePath(oldFile.getStoragePath());
-
- } else {
- // conflict resolved with 'Keep both' by the user
- File localFile = new File(oldFile.getStoragePath());
- File newLocalFile = new File(FileStorageUtils.getDefaultSavePathFor(mCurrentUpload.getAccount().name, file));
- boolean renameSuccessed = localFile.renameTo(newLocalFile);
- if (renameSuccessed) {
- file.setStoragePath(newLocalFile.getAbsolutePath());
-
- } else {
- // poor solution
- Log.d(TAG, "DAMN IT: local rename failed after uploading a file with a new name already existing both in the remote account and the local database (should be due to a conflict solved with 'keep both'");
- file.setStoragePath(null);
- // not so fine:
- // - local file will be kept there as 'trash' until is download (and overwritten) again from the server;
- // - user will see as 'not down' a file that was just upload
- // BUT:
- // - no loss of data happened
- // - when the user downloads again the renamed and original file from the server, local file names and contents will be correctly synchronized with names and contents in server
- }
+ if (oldFile.fileExists()) {
oldFile.setStoragePath(null);
mStorageManager.saveFile(oldFile);
- }
+
+ } // else: it was just an automatic renaming due to a name coincidence; nothing else is needed, the storagePath is right in the instance returned by mCurrentUpload.getFile()
}
mStorageManager.saveFile(file);
file.setCreationTimestamp(we.createTimestamp());
file.setFileLength(we.contentLength());
file.setMimetype(we.contentType());
- file.setModificationTimestamp(we.modifiedTimesamp());
+ file.setModificationTimestamp(we.modifiedTimestamp());
+ file.setModificationTimestampAtLastSyncForData(we.modifiedTimestamp());
// file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available
}
mDefaultNotificationContentView = mNotification.contentView;
mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.progressbar_layout);
mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, false);
- mNotification.contentView.setTextViewText(R.id.status_text, String.format(getString(R.string.uploader_upload_in_progress_content), 0, new File(upload.getStoragePath()).getName()));
+ mNotification.contentView.setTextViewText(R.id.status_text, String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName()));
mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon);
/// includes a pending intent in the notification showing the details view of the file
mNotification.setLatestEventInfo( getApplicationContext(),
getString(R.string.uploader_upload_succeeded_ticker),
- String.format(getString(R.string.uploader_upload_succeeded_content_single), (new File(upload.getStoragePath())).getName()),
+ String.format(getString(R.string.uploader_upload_succeeded_content_single), upload.getFileName()),
mNotification.contentIntent);
mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); // NOT AN ERROR; uploader_upload_in_progress_ticker is the target, not a new notification
finalNotification.flags |= Notification.FLAG_AUTO_CANCEL;
// TODO put something smart in the contentIntent below
finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);
+
+ String content = null;
+ if (uploadResult.getCode() == ResultCode.LOCAL_STORAGE_FULL ||
+ uploadResult.getCode() == ResultCode.LOCAL_STORAGE_NOT_COPIED) {
+ // TODO we need a class to provide error messages for the users from a RemoteOperationResult and a RemoteOperation
+ content = String.format(getString(R.string.error__upload__local_file_not_copied), upload.getFileName(), getString(R.string.app_name));
+ } else {
+ content = String.format(getString(R.string.uploader_upload_failed_content_single), upload.getFileName());
+ }
finalNotification.setLatestEventInfo( getApplicationContext(),
getString(R.string.uploader_upload_failed_ticker),
- String.format(getString(R.string.uploader_upload_failed_content_single), (new File(upload.getStoragePath())).getName()),
+ content,
finalNotification.contentIntent);
mNotificationManager.notify(R.string.uploader_upload_failed_ticker, finalNotification);
if (upload.wasRenamed()) {
end.putExtra(EXTRA_OLD_REMOTE_PATH, upload.getOldFile().getRemotePath());
}
- end.putExtra(EXTRA_FILE_PATH, upload.getStoragePath());
+ end.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath());
end.putExtra(ACCOUNT_NAME, upload.getAccount().name);
end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess());
sendStickyBroadcast(end);
public ChunkedUploadFileOperation( Account account,
OCFile file,
boolean isInstant,
- boolean forceOverwrite) {
+ boolean forceOverwrite,
+ int localBehaviour) {
- super(account, file, isInstant, forceOverwrite);
+ super(account, file, isInstant, forceOverwrite, localBehaviour);
}
@Override
moved = tmpFile.renameTo(newFile);
}
if (!moved)
- result = new RemoteOperationResult(RemoteOperationResult.ResultCode.STORAGE_ERROR_MOVING_FROM_TMP);
+ result = new RemoteOperationResult(RemoteOperationResult.ResultCode.LOCAL_STORAGE_NOT_MOVED);
else
result = new RemoteOperationResult(isSuccess(status), status);
Log.i(TAG, "Download of " + mFile.getRemotePath() + " to " + getSavePath() + ": " + result.getLogMessage());
SSL_ERROR,
SSL_RECOVERABLE_PEER_UNVERIFIED,
BAD_OC_VERSION,
- STORAGE_ERROR_MOVING_FROM_TMP,
CANCELLED,
INVALID_LOCAL_FILE_NAME,
INVALID_OVERWRITE,
CONFLICT,
- SYNC_CONFLICT
+ SYNC_CONFLICT,
+ LOCAL_STORAGE_FULL,
+ LOCAL_STORAGE_NOT_MOVED,
+ LOCAL_STORAGE_NOT_COPIED
}
private boolean mSuccess = false;
} else if (mCode == ResultCode.BAD_OC_VERSION) {
return "No valid ownCloud version was found at the server";
- } else if (mCode == ResultCode.STORAGE_ERROR_MOVING_FROM_TMP) {
- return "Error while moving file from temporal to final directory";
+ } else if (mCode == ResultCode.LOCAL_STORAGE_FULL) {
+ return "Local storage full";
+
+ } else if (mCode == ResultCode.LOCAL_STORAGE_NOT_MOVED) {
+ return "Error while moving file to final directory";
}
return "Operation finished with HTTP status code " + mHttpCode + " (" + (isSuccess()?"success":"fail") + ")";
serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag())); // TODO could this be dangerous when the user upgrades the server from non-tagged to tagged?
} else {
// server without etags
- serverChanged = (mServerFile.getModificationTimestamp() > mLocalFile.getModificationTimestamp());
+ serverChanged = (mServerFile.getModificationTimestamp() > mLocalFile.getModificationTimestampAtLastSyncForData());
}
boolean localChanged = (mLocalChangeAlreadyKnown || mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData());
// TODO this will be always true after the app is upgraded to database version 3; will result in unnecessary uploads
file.setCreationTimestamp(we.createTimestamp());
file.setFileLength(we.contentLength());
file.setMimetype(we.contentType());
- file.setModificationTimestamp(we.modifiedTimesamp());
+ file.setModificationTimestamp(we.modifiedTimestamp());
return file;
}
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.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Vector;
import org.apache.http.HttpStatus;
private int mConflictsFound;
private int mFailsInFavouritesFound;
+
+ private Map<String, String> mForgottenLocalFiles;
public SynchronizeFolderOperation( String remotePath,
mStorageManager = dataStorageManager;
mAccount = account;
mContext = context;
+ mForgottenLocalFiles = new HashMap<String, String>();
}
return mFailsInFavouritesFound;
}
+ public Map<String, String> getForgottenLocalFiles() {
+ return mForgottenLocalFiles;
+ }
+
/**
* Returns the list of files and folders contained in the synchronized folder, if called after synchronization is complete.
*
RemoteOperationResult result = null;
mFailsInFavouritesFound = 0;
mConflictsFound = 0;
+ mForgottenLocalFiles.clear();
// code before in FileSyncAdapter.fetchData
PropFindMethod query = null;
if (oldFile != null) {
file.setKeepInSync(oldFile.keepInSync());
file.setLastSyncDateForData(oldFile.getLastSyncDateForData());
+ checkAndFixForeignStoragePath(oldFile);
file.setStoragePath(oldFile.getStoragePath());
}
file.setCreationTimestamp(we.createTimestamp());
file.setFileLength(we.contentLength());
file.setMimetype(we.contentType());
- file.setModificationTimestamp(we.modifiedTimesamp());
+ file.setModificationTimestamp(we.modifiedTimestamp());
file.setParentId(mParentId);
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.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.d(TAG, "Weird exception while closing input stream for " + storagePath + " (ignoring)", e);
+ }
+ try {
+ if (out != null) out.close();
+ } catch (Exception e) {
+ Log.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e);
+ }
+ }
+ }
+ }
+ }
+
+
}
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.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.http.HttpStatus;
import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.operations.RemoteOperation;
import com.owncloud.android.operations.RemoteOperationResult;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.utils.FileStorageUtils;
import eu.alefzero.webdav.FileRequestEntity;
import eu.alefzero.webdav.OnDatatransferProgressListener;
private boolean mIsInstant = false;
private boolean mRemoteFolderToBeCreated = false;
private boolean mForceOverwrite = false;
+ private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
private boolean mWasRenamed = false;
+ private String mOriginalFileName = null;
+ private String mOriginalStoragePath = null;
PutMethod mPutMethod = null;
private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
public UploadFileOperation( Account account,
OCFile file,
boolean isInstant,
- boolean forceOverwrite) {
+ boolean forceOverwrite,
+ int localBehaviour) {
if (account == null)
throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation creation");
if (file == null)
mRemotePath = file.getRemotePath();
mIsInstant = isInstant;
mForceOverwrite = forceOverwrite;
+ mLocalBehaviour = localBehaviour;
+ mOriginalStoragePath = mFile.getStoragePath();
+ mOriginalFileName = mFile.getFileName();
}
return mAccount;
}
+ public String getFileName() {
+ return mOriginalFileName;
+ }
+
public OCFile getFile() {
return mFile;
}
return mOldFile;
}
+ public String getOriginalStoragePath() {
+ return mOriginalStoragePath;
+ }
+
public String getStoragePath() {
return mFile.getStoragePath();
}
mDataTransferListeners.add(listener);
}
-
@Override
protected RemoteOperationResult run(WebdavClient client) {
RemoteOperationResult result = null;
- boolean nameCheckPassed = false;
+ boolean localCopyPassed = false, nameCheckPassed = false;
+ File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null;
try {
/// rename the file to upload, if necessary
if (!mForceOverwrite) {
createNewOCFile(remotePath);
}
}
+ nameCheckPassed = true;
+ String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); /// not before getAvailableRemotePath() !!!
+ expectedFile = new File(expectedPath);
+
+ /// check location of local file; if not the expected, copy to a temporal file before upload (if COPY is the expected behaviour)
+ if (!mOriginalStoragePath.equals(expectedPath) && mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY) {
+
+ if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
+ result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
+ return result; // error condition when the file should be copied
+
+ } else {
+ String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
+ mFile.setStoragePath(temporalPath);
+ temporalFile = new File(temporalPath);
+ if (!mOriginalStoragePath.equals(temporalPath)) { // preventing weird but possible situation
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ File temporalParent = temporalFile.getParentFile();
+ temporalParent.mkdirs();
+ if (!temporalParent.isDirectory()) {
+ throw new IOException("Unexpected error: parent directory could not be created");
+ }
+ temporalFile.createNewFile();
+ if (!temporalFile.isFile()) {
+ throw new IOException("Unexpected error: target file could not be created");
+ }
+ in = new FileInputStream(originalFile);
+ out = new FileOutputStream(temporalFile);
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = in.read(buf)) > 0){
+ out.write(buf, 0, len);
+ }
+
+ } catch (Exception e) {
+ result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED);
+ return result;
+
+ } finally {
+ try {
+ if (in != null) in.close();
+ } catch (Exception e) {
+ Log.d(TAG, "Weird exception while closing input stream for " + mOriginalStoragePath + " (ignoring)", e);
+ }
+ try {
+ if (out != null) out.close();
+ } catch (Exception e) {
+ Log.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e);
+ }
+ }
+ }
+ }
+ }
+ localCopyPassed = true;
+
/// perform the upload
- nameCheckPassed = true;
synchronized(mCancellationRequested) {
if (mCancellationRequested.get()) {
throw new OperationCancelledException();
} else {
- mPutMethod = new PutMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath));
+ mPutMethod = new PutMethod(client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()));
}
}
int status = uploadFile(client);
+
+
+ /// move local temporal file or original file to its corresponding location in the ownCloud local folder
+ if (isSuccess(status)) {
+ 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 veeery 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;
+ }
+ }
+ }
+ }
+
result = new RemoteOperationResult(isSuccess(status), status);
- Log.i(TAG, "Upload of " + mFile.getStoragePath() + " to " + mRemotePath + ": " + result.getLogMessage());
-
+
+
} catch (Exception e) {
- // TODO something cleaner
+ // TODO something cleaner with cancellations
if (mCancellationRequested.get()) {
result = new RemoteOperationResult(new OperationCancelledException());
} else {
result = new RemoteOperationResult(e);
}
- Log.e(TAG, "Upload of " + mFile.getStoragePath() + " to " + mRemotePath + ": " + result.getLogMessage() + (nameCheckPassed?"":" (while checking file existence in server)"), result.getException());
+
+
+ } finally {
+ if (temporalFile != null && !originalFile.equals(temporalFile)) {
+ temporalFile.delete();
+ }
+ if (result.isSuccess()) {
+ Log.i(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
+
+ } else {
+ if (result.getException() != null) {
+ String complement = "";
+ if (!nameCheckPassed) {
+ complement = " (while checking file existence in server)";
+ } else if (!localCopyPassed) {
+ complement = " (while copying local file to " + FileStorageUtils.getSavePath(mAccount.name) + ")";
+ }
+ Log.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage() + complement, result.getException());
+ } else {
+ Log.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage());
+ }
+ }
}
return result;
newFile.setFileLength(mFile.getFileLength());
newFile.setMimetype(mFile.getMimetype());
newFile.setModificationTimestamp(mFile.getModificationTimestamp());
+ newFile.setModificationTimestampAtLastSyncForData(mFile.getModificationTimestampAtLastSyncForData());
// newFile.setEtag(mFile.getEtag())
newFile.setKeepInSync(mFile.keepInSync());
newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
ProviderTableMeta.FILE_CREATION);\r
mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED,\r
ProviderTableMeta.FILE_MODIFIED);\r
+ mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA,\r
+ ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA);\r
mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH,\r
ProviderTableMeta.FILE_CONTENT_LENGTH);\r
mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE,\r
+ ProviderTableMeta.FILE_ACCOUNT_OWNER + " TEXT, "\r
+ ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, "\r
+ ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, "\r
- + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER );"\r
+ + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER, "\r
+ + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER );"\r
);\r
}\r
\r
" DEFAULT 0");\r
upgraded = true;\r
}\r
+ if (oldVersion < 4 && newVersion >= 4) {\r
+ Log.i("SQL", "Entering in the #3 ADD in onUpgrade");\r
+ db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +\r
+ " ADD COLUMN " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER " +\r
+ " DEFAULT 0");\r
+ upgraded = true;\r
+ }\r
if (!upgraded)\r
Log.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion);\r
}\r
\r
import java.io.IOException;\r
import java.net.UnknownHostException;\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
import java.util.List;\r
+import java.util.Map;\r
\r
import org.apache.jackrabbit.webdav.DavException;\r
\r
import com.owncloud.android.operations.SynchronizeFolderOperation;\r
import com.owncloud.android.operations.UpdateOCVersionOperation;\r
import com.owncloud.android.operations.RemoteOperationResult.ResultCode;\r
-\r
+import com.owncloud.android.ui.activity.ErrorsWhileCopyingHandlerActivity;\r
import android.accounts.Account;\r
import android.app.Notification;\r
import android.app.NotificationManager;\r
private SyncResult mSyncResult;\r
private int mConflictsFound;\r
private int mFailsInFavouritesFound;\r
+ private Map<String, String> mForgottenLocalFiles;\r
+\r
\r
public FileSyncAdapter(Context context, boolean autoInitialize) {\r
super(context, autoInitialize);\r
mLastFailedResult = null;\r
mConflictsFound = 0;\r
mFailsInFavouritesFound = 0;\r
+ mForgottenLocalFiles = new HashMap<String, String>();\r
mSyncResult = syncResult;\r
mSyncResult.fullSyncRequested = false;\r
mSyncResult.delayUntil = 60*60*24; // sync after 24h\r
/// notify the user about the failure of MANUAL synchronization\r
notifyFailedSynchronization();\r
\r
- } else if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {\r
+ }\r
+ if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {\r
notifyFailsInFavourites();\r
}\r
+ if (mForgottenLocalFiles.size() > 0) {\r
+ notifyForgottenLocalFiles();\r
+ \r
+ }\r
sendStickyBroadcast(false, null, mLastFailedResult); // message to signal the end to the UI\r
}\r
\r
mConflictsFound += synchFolderOp.getConflictsFound();\r
mFailsInFavouritesFound += synchFolderOp.getFailsInFavouritesFound();\r
}\r
+ if (synchFolderOp.getForgottenLocalFiles().size() > 0) {\r
+ mForgottenLocalFiles.putAll(synchFolderOp.getForgottenLocalFiles());\r
+ }\r
// synchronize children folders \r
List<OCFile> children = synchFolderOp.getChildren();\r
fetchChildren(children); // beware of the 'hidden' recursion here!\r
\r
\r
/**\r
- * Notifies the user about conflicts and strange fails when trying to synchronize the contents of favourite files.\r
+ * Notifies the user about conflicts and strange fails when trying to synchronize the contents of kept-in-sync files.\r
* \r
* By now, we won't consider a failed synchronization.\r
*/\r
} \r
}\r
\r
+ \r
+ /**\r
+ * Notifies the user about local copies of files out of the ownCloud local directory that were 'forgotten' because \r
+ * copying them inside the ownCloud local directory was not possible.\r
+ * \r
+ * We don't want links to files out of the ownCloud local directory (foreign files) anymore. It's easy to have \r
+ * synchronization problems if a local file is linked to more than one remote file.\r
+ * \r
+ * We won't consider a synchronization as failed when foreign files can not be copied to the ownCloud local directory.\r
+ */\r
+ private void notifyForgottenLocalFiles() {\r
+ Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_foreign_files_forgotten_ticker), System.currentTimeMillis());\r
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;\r
+\r
+ /// includes a pending intent in the notification showing a more detailed explanation\r
+ Intent explanationIntent = new Intent(getContext(), ErrorsWhileCopyingHandlerActivity.class);\r
+ explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_ACCOUNT, getAccount());\r
+ ArrayList<String> remotePaths = new ArrayList<String>();\r
+ ArrayList<String> localPaths = new ArrayList<String>();\r
+ remotePaths.addAll(mForgottenLocalFiles.keySet());\r
+ localPaths.addAll(mForgottenLocalFiles.values());\r
+ explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_LOCAL_PATHS, localPaths);\r
+ explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_REMOTE_PATHS, remotePaths); \r
+ explanationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);\r
+ \r
+ notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), explanationIntent, 0);\r
+ notification.setLatestEventInfo(getContext().getApplicationContext(), \r
+ getContext().getString(R.string.sync_foreign_files_forgotten_ticker), \r
+ String.format(getContext().getString(R.string.sync_foreign_files_forgotten_content), mForgottenLocalFiles.size()), \r
+ notification.contentIntent);\r
+ ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_foreign_files_forgotten_ticker, notification);\r
+ \r
+ }\r
+ \r
+ \r
}\r
return;
case OVERWRITE:
i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
- case KEEP_BOTH: // fallthrough
+ break;
+ case KEEP_BOTH:
+ i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
break;
default:
Log.wtf(TAG, "Unhandled conflict decision " + decision);
return;
}
i.putExtra(FileUploader.KEY_ACCOUNT, mOCAccount);
- //i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath);
- //i.putExtra(FileUploader.KEY_LOCAL_FILE, mLocalPath);
i.putExtra(FileUploader.KEY_FILE, mFile);
i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
--- /dev/null
+package com.owncloud.android.ui.activity;
+
+import java.io.File;
+import java.util.ArrayList;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.DialogFragment;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
+import com.owncloud.android.utils.FileStorageUtils;
+
+
+/**
+ * Activity reporting errors occurred when local files uploaded to an ownCloud account with an app in
+ * version under 1.3.16 where being copied to the ownCloud local folder.
+ *
+ * Allows the user move the files to the ownCloud local folder, or let them unlinked to the remote
+ * files.
+ *
+ * Shown when the error notification summarizing the list of errors is clicked by the user.
+ *
+ * @author David A. Velasco
+ */
+public class ErrorsWhileCopyingHandlerActivity extends SherlockFragmentActivity implements OnClickListener {
+
+ private static final String TAG = ErrorsWhileCopyingHandlerActivity.class.getSimpleName();
+
+ public static final String EXTRA_ACCOUNT = ErrorsWhileCopyingHandlerActivity.class.getCanonicalName() + ".EXTRA_ACCOUNT";
+ public static final String EXTRA_LOCAL_PATHS = ErrorsWhileCopyingHandlerActivity.class.getCanonicalName() + ".EXTRA_LOCAL_PATHS";
+ public static final String EXTRA_REMOTE_PATHS = ErrorsWhileCopyingHandlerActivity.class.getCanonicalName() + ".EXTRA_REMOTE_PATHS";
+
+ private static final String WAIT_DIALOG_TAG = "WAIT_DIALOG";
+
+ protected Account mAccount;
+ protected FileDataStorageManager mStorageManager;
+ protected ArrayList<String> mLocalPaths;
+ protected ArrayList<String> mRemotePaths;
+ protected ArrayAdapter<String> mAdapter;
+ protected Handler mHandler;
+ private DialogFragment mCurrentDialog;
+
+ /**
+ * {@link}
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ /// read extra parameters in intent
+ Intent intent = getIntent();
+ mAccount = intent.getParcelableExtra(EXTRA_ACCOUNT);
+ mRemotePaths = intent.getStringArrayListExtra(EXTRA_REMOTE_PATHS);
+ mLocalPaths = intent.getStringArrayListExtra(EXTRA_LOCAL_PATHS);
+ mStorageManager = new FileDataStorageManager(mAccount, getContentResolver());
+ mHandler = new Handler();
+ if (mCurrentDialog != null) {
+ mCurrentDialog.dismiss();
+ mCurrentDialog = null;
+ }
+
+ /// load generic layout
+ setContentView(R.layout.generic_explanation);
+
+ /// customize text message
+ TextView textView = (TextView) findViewById(R.id.message);
+ String appName = getString(R.string.app_name);
+ String message = String.format(getString(R.string.sync_foreign_files_forgotten_explanation), appName, appName, appName, appName, mAccount.name);
+ textView.setText(message);
+ textView.setMovementMethod(new ScrollingMovementMethod());
+
+ /// load the list of local and remote files that failed
+ ListView listView = (ListView) findViewById(R.id.list);
+ if (mLocalPaths != null && mLocalPaths.size() > 0) {
+ mAdapter = new ErrorsWhileCopyingListAdapter();
+ listView.setAdapter(mAdapter);
+ } else {
+ listView.setVisibility(View.GONE);
+ mAdapter = null;
+ }
+
+ /// customize buttons
+ Button cancelBtn = (Button) findViewById(R.id.cancel);
+ Button okBtn = (Button) findViewById(R.id.ok);
+ okBtn.setText(R.string.foreign_files_move);
+ cancelBtn.setOnClickListener(this);
+ okBtn.setOnClickListener(this);
+ }
+
+
+ /**
+ * Customized adapter, showing the local files as main text in two-lines list item and the remote files
+ * as the secondary text.
+ *
+ * @author David A. Velasco
+ */
+ public class ErrorsWhileCopyingListAdapter extends ArrayAdapter<String> {
+
+ ErrorsWhileCopyingListAdapter() {
+ super(ErrorsWhileCopyingHandlerActivity.this, android.R.layout.two_line_list_item, android.R.id.text1, mLocalPaths);
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View getView (int position, View convertView, ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ view = vi.inflate(android.R.layout.two_line_list_item, null);
+ }
+ if (view != null) {
+ String localPath = getItem(position);
+ if (localPath != null) {
+ TextView text1 = (TextView) view.findViewById(android.R.id.text1);
+ if (text1 != null) {
+ text1.setText(String.format(getString(R.string.foreign_files_local_text), localPath));
+ }
+ }
+ if (mRemotePaths != null && mRemotePaths.size() > 0 && position >= 0 && position < mRemotePaths.size()) {
+ TextView text2 = (TextView) view.findViewById(android.R.id.text2);
+ String remotePath = mRemotePaths.get(position);
+ if (text2 != null && remotePath != null) {
+ text2.setText(String.format(getString(R.string.foreign_files_remote_text), remotePath));
+ }
+ }
+ }
+ return view;
+ }
+ }
+
+
+ /**
+ * Listener method to perform the MOVE / CANCEL action available in this activity.
+ *
+ * @param v Clicked view (button MOVE or CANCEL)
+ */
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.ok) {
+ /// perform movement operation in background thread
+ Log.d(TAG, "Clicked MOVE, start movement");
+ new MoveFilesTask().execute();
+
+ } else if (v.getId() == R.id.cancel) {
+ /// just finish
+ Log.d(TAG, "Clicked CANCEL, bye");
+ finish();
+
+ } else {
+ Log.e(TAG, "Clicked phantom button, id: " + v.getId());
+ }
+ }
+
+
+ /**
+ * Asynchronous task performing the move of all the local files to the ownCloud folder.
+ *
+ * @author David A. Velasco
+ */
+ private class MoveFilesTask extends AsyncTask<Void, Void, Boolean> {
+
+ /**
+ * Updates the UI before trying the movement
+ */
+ @Override
+ protected void onPreExecute () {
+ /// progress dialog and disable 'Move' button
+ mCurrentDialog = IndeterminateProgressDialog.newInstance(R.string.wait_a_moment, false);
+ mCurrentDialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
+ findViewById(R.id.ok).setEnabled(false);
+ }
+
+
+ /**
+ * Performs the movement
+ *
+ * @return 'False' when the movement of any file fails.
+ */
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ while (!mLocalPaths.isEmpty()) {
+ String currentPath = mLocalPaths.get(0);
+ File currentFile = new File(currentPath);
+ String expectedPath = FileStorageUtils.getSavePath(mAccount.name) + mRemotePaths.get(0);
+ File expectedFile = new File(expectedPath);
+
+ if (expectedFile.equals(currentFile) || currentFile.renameTo(expectedFile)) {
+ // SUCCESS
+ OCFile file = mStorageManager.getFileByPath(mRemotePaths.get(0));
+ file.setStoragePath(expectedPath);
+ mStorageManager.saveFile(file);
+ mRemotePaths.remove(0);
+ mLocalPaths.remove(0);
+
+ } else {
+ // FAIL
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Updates the activity UI after the movement of local files is tried.
+ *
+ * If the movement was successful for all the files, finishes the activity immediately.
+ *
+ * In other case, the list of remaining files is still available to retry the movement.
+ *
+ * @param result 'True' when the movement was successful.
+ */
+ @Override
+ protected void onPostExecute(Boolean result) {
+ mAdapter.notifyDataSetChanged();
+ mCurrentDialog.dismiss();
+ mCurrentDialog = null;
+ findViewById(R.id.ok).setEnabled(true);
+
+ if (result) {
+ // nothing else to do in this activity
+ Toast t = Toast.makeText(ErrorsWhileCopyingHandlerActivity.this, getString(R.string.foreign_files_success), Toast.LENGTH_LONG);
+ t.show();
+ finish();
+
+ } else {
+ Toast t = Toast.makeText(ErrorsWhileCopyingHandlerActivity.this, getString(R.string.foreign_files_fail), Toast.LENGTH_LONG);
+ t.show();
+ }
+ }
+ }
+
+}
*/\r
public void onActivityResult(int requestCode, int resultCode, Intent data) {\r
\r
- if (requestCode == ACTION_SELECT_CONTENT_FROM_APPS && resultCode == RESULT_OK) {\r
- requestSimpleUpload(data);\r
+ if (requestCode == ACTION_SELECT_CONTENT_FROM_APPS && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {\r
+ requestSimpleUpload(data, resultCode);\r
\r
- } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && resultCode == RESULT_OK) {\r
- requestMultipleUpload(data);\r
+ } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {\r
+ requestMultipleUpload(data, resultCode);\r
\r
}\r
}\r
\r
- private void requestMultipleUpload(Intent data) {\r
+ private void requestMultipleUpload(Intent data, int resultCode) {\r
String[] filePaths = data.getStringArrayExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES);\r
if (filePaths != null) {\r
String[] remotePaths = new String[filePaths.length];\r
i.putExtra(FileUploader.KEY_LOCAL_FILE, filePaths);\r
i.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);\r
i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);\r
+ if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)\r
+ i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);\r
startService(i);\r
\r
} else {\r
}\r
\r
\r
- private void requestSimpleUpload(Intent data) {\r
+ private void requestSimpleUpload(Intent data, int resultCode) {\r
String filepath = null;\r
try {\r
Uri selectedImageUri = data.getData();\r
i.putExtra(FileUploader.KEY_LOCAL_FILE, filepath);\r
i.putExtra(FileUploader.KEY_REMOTE_FILE, remotepath);\r
i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);\r
+ if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)\r
+ i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);\r
startService(i);\r
}\r
\r
if (item == 0) {\r
//if (!mDualPane) { \r
Intent action = new Intent(FileDisplayActivity.this, UploadFilesActivity.class);\r
+ action.putExtra(UploadFilesActivity.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this));\r
startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES);\r
//} else {\r
// TODO create and handle new fragment LocalFileListFragment\r
--- /dev/null
+package com.owncloud.android.ui.activity;
+
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.method.ScrollingMovementMethod;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.owncloud.android.R;
+
+/**
+ * Activity showing a text message and, optionally, a couple list of single or paired text strings.
+ *
+ * Added to show explanations for notifications when the user clicks on them, and there no place
+ * better to show them.
+ *
+ * @author David A. Velasco
+ */
+public class GenericExplanationActivity extends SherlockFragmentActivity {
+
+ public static final String EXTRA_LIST = GenericExplanationActivity.class.getCanonicalName() + ".EXTRA_LIST";
+ public static final String EXTRA_LIST_2 = GenericExplanationActivity.class.getCanonicalName() + ".EXTRA_LIST_2";
+ public static final String MESSAGE = GenericExplanationActivity.class.getCanonicalName() + ".MESSAGE";
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ String message = intent.getStringExtra(MESSAGE);
+ ArrayList<String> list = intent.getStringArrayListExtra(EXTRA_LIST);
+ ArrayList<String> list2 = intent.getStringArrayListExtra(EXTRA_LIST_2);
+
+ setContentView(R.layout.generic_explanation);
+
+ if (message != null) {
+ TextView textView = (TextView) findViewById(R.id.message);
+ textView.setText(message);
+ textView.setMovementMethod(new ScrollingMovementMethod());
+ }
+
+ ListView listView = (ListView) findViewById(R.id.list);
+ if (list != null && list.size() > 0) {
+ //ListAdapter adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list);
+ ListAdapter adapter = new ExplanationListAdapterView(this, list, list2);
+ listView.setAdapter(adapter);
+ } else {
+ listView.setVisibility(View.GONE);
+ }
+ }
+
+ public class ExplanationListAdapterView extends ArrayAdapter<String> {
+
+ ArrayList<String> mList;
+ ArrayList<String> mList2;
+
+ ExplanationListAdapterView(Context context, ArrayList<String> list, ArrayList<String> list2) {
+ super(context, android.R.layout.two_line_list_item, android.R.id.text1, list);
+ mList = list;
+ mList2 = list2;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View getView (int position, View convertView, ViewGroup parent) {
+ View view = super.getView(position, convertView, parent);
+ if (view != null) {
+ if (mList2 != null && mList2.size() > 0 && position >= 0 && position < mList2.size()) {
+ TextView text2 = (TextView) view.findViewById(android.R.id.text2);
+ if (text2 != null) {
+ text2.setText(mList2.get(position));
+ }
+ }
+ }
+ return view;
+ }
+ }
+
+}
import java.io.File;
+import android.accounts.Account;
import android.content.Intent;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
+import android.support.v4.app.DialogFragment;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import com.actionbarsherlock.app.ActionBar.OnNavigationListener;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.MenuItem;
+import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
+import com.owncloud.android.ui.fragment.ConfirmationDialogFragment;
import com.owncloud.android.ui.fragment.LocalFileListFragment;
+import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
+import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.R;
*/
public class UploadFilesActivity extends SherlockFragmentActivity implements
- LocalFileListFragment.ContainerActivity, OnNavigationListener, OnClickListener {
+ LocalFileListFragment.ContainerActivity, OnNavigationListener, OnClickListener, ConfirmationDialogFragmentListener {
private ArrayAdapter<String> mDirectories;
private File mCurrentDir = null;
private LocalFileListFragment mFileListFragment;
private Button mCancelBtn;
private Button mUploadBtn;
+ private Account mAccount;
+ private DialogFragment mCurrentDialog;
- public static final String EXTRA_DIRECTORY_PATH = "com.owncloud.android.Directory";
- public static final String EXTRA_CHOSEN_FILES = "com.owncloud.android.ChosenFiles";
+ public static final String EXTRA_ACCOUNT = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_ACCOUNT";
+ public static final String EXTRA_CHOSEN_FILES = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_CHOSEN_FILES";
+
+ public static final int RESULT_OK_AND_MOVE = RESULT_FIRST_USER;
+ private static final String KEY_DIRECTORY_PATH = UploadFilesActivity.class.getCanonicalName() + ".KEY_DIRECTORY_PATH";
private static final String TAG = "UploadFilesActivity";
+ private static final String WAIT_DIALOG_TAG = "WAIT";
+ private static final String QUERY_TO_MOVE_DIALOG_TAG = "QUERY_TO_MOVE";
@Override
super.onCreate(savedInstanceState);
if(savedInstanceState != null) {
- mCurrentDir = new File(savedInstanceState.getString(UploadFilesActivity.EXTRA_DIRECTORY_PATH));
+ mCurrentDir = new File(savedInstanceState.getString(UploadFilesActivity.KEY_DIRECTORY_PATH));
} else {
mCurrentDir = Environment.getExternalStorageDirectory();
}
+ mAccount = getIntent().getParcelableExtra(EXTRA_ACCOUNT);
+
/// USER INTERFACE
// Drop-down navigation
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
actionBar.setListNavigationCallbacks(mDirectories, this);
+
+ // wait dialog
+ if (mCurrentDialog != null) {
+ mCurrentDialog.dismiss();
+ mCurrentDialog = null;
+ }
Log.d(TAG, "onCreate() end");
}
// responsibility of restore is preferred in onCreate() before than in onRestoreInstanceState when there are Fragments involved
Log.d(TAG, "onSaveInstanceState() start");
super.onSaveInstanceState(outState);
- outState.putString(UploadFilesActivity.EXTRA_DIRECTORY_PATH, mCurrentDir.getAbsolutePath());
+ outState.putString(UploadFilesActivity.KEY_DIRECTORY_PATH, mCurrentDir.getAbsolutePath());
Log.d(TAG, "onSaveInstanceState() end");
}
/**
* Performs corresponding action when user presses 'Cancel' or 'Upload' button
+ *
+ * TODO Make here the real request to the Upload service ; will require to receive the account and
+ * target folder where the upload must be done in the received intent.
*/
@Override
public void onClick(View v) {
finish();
} else if (v.getId() == R.id.upload_files_btn_upload) {
+ new CheckAvailableSpaceTask().execute();
+ }
+ }
+
+
+ /**
+ * Asynchronous task checking if there is space enough to copy all the files chosen
+ * to upload into the ownCloud local folder.
+ *
+ * Maybe an AsyncTask is not strictly necessary, but who really knows.
+ *
+ * @author David A. Velasco
+ */
+ private class CheckAvailableSpaceTask extends AsyncTask<Void, Void, Boolean> {
+
+ /**
+ * Updates the UI before trying the movement
+ */
+ @Override
+ protected void onPreExecute () {
+ /// progress dialog and disable 'Move' button
+ mCurrentDialog = IndeterminateProgressDialog.newInstance(R.string.wait_a_moment, false);
+ mCurrentDialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
+ }
+
+
+ /**
+ * Checks the available space
+ *
+ * @return 'True' if there is space enough.
+ */
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ String[] checkedFilePaths = mFileListFragment.getCheckedFilePaths();
+ long total = 0;
+ for (int i=0; i < checkedFilePaths.length ; i++) {
+ String localPath = checkedFilePaths[i];
+ File localFile = new File(localPath);
+ total += localFile.length();
+ }
+ return (FileStorageUtils.getUsableSpace(mAccount.name) >= total);
+ }
+
+ /**
+ * Updates the activity UI after the check of space is done.
+ *
+ * If there is not space enough. shows a new dialog to query the user if wants to move the files instead
+ * of copy them.
+ *
+ * @param result 'True' when there is space enough to copy all the selected files.
+ */
+ @Override
+ protected void onPostExecute(Boolean result) {
+ mCurrentDialog.dismiss();
+ mCurrentDialog = null;
+
+ if (result) {
+ // return the list of selected files (success)
+ Intent data = new Intent();
+ data.putExtra(EXTRA_CHOSEN_FILES, mFileListFragment.getCheckedFilePaths());
+ setResult(RESULT_OK, data);
+ finish();
+
+ } else {
+ // show a dialog to query the user if wants to move the selected files to the ownCloud folder instead of copying
+ String[] args = {getString(R.string.app_name)};
+ ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(R.string.upload_query_move_foreign_files, args, R.string.common_yes, -1, R.string.common_no);
+ dialog.setOnConfirmationListener(UploadFilesActivity.this);
+ mCurrentDialog = dialog;
+ mCurrentDialog.show(getSupportFragmentManager(), QUERY_TO_MOVE_DIALOG_TAG);
+ }
+ }
+ }
+
+ @Override
+ public void onConfirmation(String callerTag) {
+ Log.d(TAG, "Positive button in dialog was clicked; dialog tag is " + callerTag);
+ if (callerTag.equals(QUERY_TO_MOVE_DIALOG_TAG)) {
+ // return the list of selected files to the caller activity (success), signaling that they should be moved to the ownCloud folder, instead of copied
Intent data = new Intent();
data.putExtra(EXTRA_CHOSEN_FILES, mFileListFragment.getCheckedFilePaths());
- setResult(RESULT_OK, data);
+ setResult(RESULT_OK_AND_MOVE, data);
finish();
}
+ mCurrentDialog.dismiss();
+ mCurrentDialog = null;
}
+
+
+ @Override
+ public void onNeutral(String callerTag) {
+ Log.d(TAG, "Phantom neutral button in dialog was clicked; dialog tag is " + callerTag);
+ mCurrentDialog.dismiss();
+ mCurrentDialog = null;
+ }
+
+
+ @Override
+ public void onCancel(String callerTag) {
+ /// nothing to do; don't finish, let the user change the selection
+ Log.d(TAG, "Negative button in dialog was clicked; dialog tag is " + callerTag);
+ mCurrentDialog.dismiss();
+ mCurrentDialog = null;
+ }
+
}
--- /dev/null
+package com.owncloud.android.ui.dialog;
+
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnKeyListener;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+import com.actionbarsherlock.app.SherlockDialogFragment;
+import com.owncloud.android.R;
+
+public class IndeterminateProgressDialog extends SherlockDialogFragment {
+
+ private static final String ARG_MESSAGE_ID = IndeterminateProgressDialog.class.getCanonicalName() + ".ARG_MESSAGE_ID";
+ private static final String ARG_CANCELABLE = IndeterminateProgressDialog.class.getCanonicalName() + ".ARG_CANCELABLE";
+
+
+ /**
+ * Public factory method to get dialog instances.
+ *
+ * @param messageId Resource id for a message to show in the dialog.
+ * @param cancelable If 'true', the dialog can be cancelled by the user input (BACK button, touch outside...)
+ * @return New dialog instance, ready to show.
+ */
+ public static IndeterminateProgressDialog newInstance(int messageId, boolean cancelable) {
+ IndeterminateProgressDialog fragment = new IndeterminateProgressDialog();
+ Bundle args = new Bundle();
+ args.putInt(ARG_MESSAGE_ID, messageId);
+ args.putBoolean(ARG_CANCELABLE, cancelable);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ /// create indeterminate progress dialog
+ final ProgressDialog dialog = new ProgressDialog(getActivity());
+ dialog.setIndeterminate(true);
+
+ /// set message
+ int messageId = getArguments().getInt(ARG_MESSAGE_ID, R.string.text_placeholder);
+ dialog.setMessage(getString(messageId));
+
+ /// set cancellation behavior
+ boolean cancelable = getArguments().getBoolean(ARG_CANCELABLE, false);
+ if (!cancelable) {
+ dialog.setCancelable(false);
+ // disable the back button
+ OnKeyListener keyListener = new OnKeyListener() {
+ @Override
+ public boolean onKey(DialogInterface dialog, int keyCode,
+ KeyEvent event) {
+
+ if( keyCode == KeyEvent.KEYCODE_BACK){
+ return true;
+ }
+ return false;
+ }
+
+ };
+ dialog.setOnKeyListener(keyListener);
+ }
+
+ return dialog;
+ }
+
+}
+
+
private ConfirmationDialogFragmentListener mListener;
+ /**
+ * Public factory method to create new ConfirmationDialogFragment instances.
+ *
+ * @param string_id Resource id for a message to show in the dialog.
+ * @param arguments Arguments to complete the message, if it's a format string.
+ * @param posBtn Resource id for the text of the positive button.
+ * @param neuBtn Resource id for the text of the neutral button.
+ * @param negBtn Resource id for the text of the negative button.
+ * @return Dialog ready to show.
+ */
public static ConfirmationDialogFragment newInstance(int string_id, String[] arguments, int posBtn, int neuBtn, int negBtn) {
ConfirmationDialogFragment frag = new ConfirmationDialogFragment();
Bundle args = new Bundle();
import android.os.AsyncTask;\r
import android.os.Bundle;\r
import android.os.Handler;\r
+import android.support.v4.app.DialogFragment;\r
import android.support.v4.app.FragmentTransaction;\r
import android.util.Log;\r
import android.view.Display;\r
\r
private Handler mHandler;\r
private RemoteOperation mLastRemoteOperation;\r
+ private DialogFragment mCurrentDialog;\r
\r
private static final String TAG = FileDetailFragment.class.getSimpleName();\r
public static final String FTAG = "FileDetails"; \r
mFile.isDown() ? R.string.confirmation_remove_local : -1,\r
R.string.common_cancel);\r
confDialog.setOnConfirmationListener(this);\r
- confDialog.show(getFragmentManager(), FTAG_CONFIRMATION);\r
+ mCurrentDialog = confDialog;\r
+ mCurrentDialog.show(getFragmentManager(), FTAG_CONFIRMATION);\r
break;\r
}\r
case R.id.fdOpenBtn: {\r
getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);\r
}\r
}\r
+ mCurrentDialog.dismiss();\r
+ mCurrentDialog = null;\r
}\r
\r
@Override\r
mStorageManager.saveFile(mFile);\r
updateFileDetails(mFile, mAccount);\r
}\r
+ mCurrentDialog.dismiss();\r
+ mCurrentDialog = null;\r
}\r
\r
@Override\r
public void onCancel(String callerTag) {\r
Log.d(TAG, "REMOVAL CANCELED");\r
+ mCurrentDialog.dismiss();\r
+ mCurrentDialog = null;\r
}\r
\r
\r
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.support.v4.app.DialogFragment;
import android.util.Log;
import android.view.ContextMenu;
import android.view.MenuInflater;
private Handler mHandler;
private OCFile mTargetFile;
-
+
+ private DialogFragment mCurrentDialog;
/**
* {@inheritDoc}
neuBtnStringId,
R.string.common_cancel);
confDialog.setOnConfirmationListener(this);
- confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
+ mCurrentDialog = confDialog;
+ mCurrentDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
return true;
}
case R.id.open_file_item: {
getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
}
+ if (mCurrentDialog != null) {
+ mCurrentDialog.dismiss();
+ mCurrentDialog = null;
+ }
}
}
mTargetFile.setStoragePath(null);
mContainerActivity.getStorageManager().saveFile(mTargetFile);
}
+ if (mCurrentDialog != null) {
+ mCurrentDialog.dismiss();
+ mCurrentDialog = null;
+ }
listDirectory();
mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
}
@Override
public void onCancel(String callerTag) {
Log.d(TAG, "REMOVAL CANCELED");
+ if (mCurrentDialog != null) {
+ mCurrentDialog.dismiss();
+ mCurrentDialog = null;
+ }
}
import android.net.Uri;
import android.os.Environment;
+import android.os.StatFs;
+
import com.owncloud.android.datamodel.OCFile;
// URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
}
+ public static final long getUsableSpace(String accountName) {
+ File savePath = Environment.getExternalStorageDirectory();
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) {
+ return savePath.getUsableSpace();
+
+ } else {
+ StatFs stats = new StatFs(savePath.getAbsolutePath());
+ return stats.getAvailableBlocks() * stats.getBlockSize();
+ }
+
+ }
}
\ No newline at end of file
return mCreateTimestamp;
}
- public long modifiedTimesamp() {
+ public long modifiedTimestamp() {
return mModifiedTimestamp;
}