|
| 1 | +/* |
| 2 | + * ownCloud Android client application |
| 3 | + * |
| 4 | + * @author Bartek Przybylski |
| 5 | + * @author David A. Velasco Copyright (C) 2012 Bartek Przybylski Copyright (C) 2016 ownCloud Inc. |
| 6 | + * <p> |
| 7 | + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public |
| 8 | + * License version 2, as published by the Free Software Foundation. |
| 9 | + * <p> |
| 10 | + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied |
| 11 | + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| 12 | + * details. |
| 13 | + * <p/> |
| 14 | + * You should have received a copy of the GNU General Public License along with this program. If not, see |
| 15 | + * <http://www.gnu.org/licenses/>. |
| 16 | + */ |
| 17 | +package com.owncloud.android.ui.activity |
| 18 | + |
| 19 | +import android.content.Context |
| 20 | +import android.content.Intent |
| 21 | +import android.os.Bundle |
| 22 | +import android.widget.Toast |
| 23 | +import com.nextcloud.client.account.User |
| 24 | +import com.nextcloud.utils.extensions.getParcelableArgument |
| 25 | +import com.owncloud.android.R |
| 26 | +import com.owncloud.android.datamodel.OCFile |
| 27 | +import com.owncloud.android.datamodel.UploadsStorageManager |
| 28 | +import com.owncloud.android.db.OCUpload |
| 29 | +import com.owncloud.android.files.services.FileDownloader |
| 30 | +import com.owncloud.android.files.services.FileUploader |
| 31 | +import com.owncloud.android.files.services.NameCollisionPolicy |
| 32 | +import com.owncloud.android.lib.common.utils.Log_OC |
| 33 | +import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation |
| 34 | +import com.owncloud.android.lib.resources.files.model.RemoteFile |
| 35 | +import com.owncloud.android.ui.dialog.ConflictsResolveDialog |
| 36 | +import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision |
| 37 | +import com.owncloud.android.ui.dialog.ConflictsResolveDialog.OnConflictDecisionMadeListener |
| 38 | +import com.owncloud.android.utils.FileStorageUtils |
| 39 | +import javax.inject.Inject |
| 40 | + |
| 41 | +/** |
| 42 | + * Wrapper activity which will be launched if keep-in-sync file will be modified by external application. |
| 43 | + */ |
| 44 | +class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener { |
| 45 | + |
| 46 | + @JvmField |
| 47 | + @Inject |
| 48 | + var uploadsStorageManager: UploadsStorageManager? = null |
| 49 | + |
| 50 | + private var conflictUploadId: Long = 0 |
| 51 | + private var existingFile: OCFile? = null |
| 52 | + private var newFile: OCFile? = null |
| 53 | + private var localBehaviour = FileUploader.LOCAL_BEHAVIOUR_FORGET |
| 54 | + |
| 55 | + @JvmField |
| 56 | + var listener: OnConflictDecisionMadeListener? = null |
| 57 | + |
| 58 | + override fun onCreate(savedInstanceState: Bundle?) { |
| 59 | + super.onCreate(savedInstanceState) |
| 60 | + |
| 61 | + getArguments(savedInstanceState) |
| 62 | + |
| 63 | + val upload = uploadsStorageManager?.getUploadById(conflictUploadId) |
| 64 | + if (upload != null) { |
| 65 | + localBehaviour = upload.localAction |
| 66 | + } |
| 67 | + |
| 68 | + // new file was modified locally in file system |
| 69 | + newFile = file |
| 70 | + setupOnConflictDecisionMadeListener(upload) |
| 71 | + } |
| 72 | + |
| 73 | + private fun getArguments(savedInstanceState: Bundle?) { |
| 74 | + if (savedInstanceState != null) { |
| 75 | + conflictUploadId = savedInstanceState.getLong(EXTRA_CONFLICT_UPLOAD_ID) |
| 76 | + existingFile = savedInstanceState.getParcelableArgument(EXTRA_EXISTING_FILE, OCFile::class.java) |
| 77 | + localBehaviour = savedInstanceState.getInt(EXTRA_LOCAL_BEHAVIOUR) |
| 78 | + } else { |
| 79 | + conflictUploadId = intent.getLongExtra(EXTRA_CONFLICT_UPLOAD_ID, -1) |
| 80 | + existingFile = intent.getParcelableExtra(EXTRA_EXISTING_FILE) |
| 81 | + localBehaviour = intent.getIntExtra(EXTRA_LOCAL_BEHAVIOUR, localBehaviour) |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + private fun setupOnConflictDecisionMadeListener(upload: OCUpload?) { |
| 86 | + listener = OnConflictDecisionMadeListener { decision: Decision? -> |
| 87 | + val file = newFile // local file got changed, so either upload it or replace it again by server |
| 88 | + // version |
| 89 | + val user = user.orElseThrow { RuntimeException() } |
| 90 | + when (decision) { |
| 91 | + Decision.CANCEL -> {} |
| 92 | + Decision.KEEP_LOCAL -> { |
| 93 | + FileUploader.uploadUpdateFile( |
| 94 | + baseContext, |
| 95 | + user, |
| 96 | + file, |
| 97 | + localBehaviour, |
| 98 | + NameCollisionPolicy.OVERWRITE |
| 99 | + ) |
| 100 | + uploadsStorageManager!!.removeUpload(upload) |
| 101 | + } |
| 102 | + |
| 103 | + Decision.KEEP_BOTH -> { |
| 104 | + FileUploader.uploadUpdateFile( |
| 105 | + baseContext, |
| 106 | + user, |
| 107 | + file, |
| 108 | + localBehaviour, |
| 109 | + NameCollisionPolicy.RENAME |
| 110 | + ) |
| 111 | + uploadsStorageManager!!.removeUpload(upload) |
| 112 | + } |
| 113 | + |
| 114 | + Decision.KEEP_SERVER -> if (!shouldDeleteLocal()) { |
| 115 | + // Overwrite local file |
| 116 | + val intent = Intent(baseContext, FileDownloader::class.java) |
| 117 | + intent.putExtra(FileDownloader.EXTRA_USER, getUser().orElseThrow { RuntimeException() }) |
| 118 | + intent.putExtra(FileDownloader.EXTRA_FILE, file) |
| 119 | + intent.putExtra(EXTRA_CONFLICT_UPLOAD_ID, conflictUploadId) |
| 120 | + startService(intent) |
| 121 | + } else { |
| 122 | + uploadsStorageManager!!.removeUpload(upload) |
| 123 | + } |
| 124 | + |
| 125 | + else -> {} |
| 126 | + } |
| 127 | + finish() |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + override fun onSaveInstanceState(outState: Bundle) { |
| 132 | + super.onSaveInstanceState(outState) |
| 133 | + outState.putLong(EXTRA_CONFLICT_UPLOAD_ID, conflictUploadId) |
| 134 | + outState.putParcelable(EXTRA_EXISTING_FILE, existingFile) |
| 135 | + outState.putInt(EXTRA_LOCAL_BEHAVIOUR, localBehaviour) |
| 136 | + } |
| 137 | + |
| 138 | + override fun conflictDecisionMade(decision: Decision) { |
| 139 | + listener?.conflictDecisionMade(decision) |
| 140 | + } |
| 141 | + |
| 142 | + override fun onStart() { |
| 143 | + super.onStart() |
| 144 | + if (account == null) { |
| 145 | + finish() |
| 146 | + return |
| 147 | + } |
| 148 | + if (newFile == null) { |
| 149 | + Log_OC.e(TAG, "No file received") |
| 150 | + finish() |
| 151 | + return |
| 152 | + } |
| 153 | + if (existingFile == null) { |
| 154 | + // fetch info of existing file from server |
| 155 | + val operation = ReadFileRemoteOperation(newFile!!.remotePath) |
| 156 | + |
| 157 | + @Suppress("TooGenericExceptionCaught") |
| 158 | + Thread { |
| 159 | + try { |
| 160 | + val result = operation.execute(account, this) |
| 161 | + if (result.isSuccess) { |
| 162 | + existingFile = FileStorageUtils.fillOCFile(result.data[0] as RemoteFile) |
| 163 | + existingFile?.lastSyncDateForProperties = System.currentTimeMillis() |
| 164 | + startDialog() |
| 165 | + } else { |
| 166 | + Log_OC.e(TAG, "ReadFileRemoteOp returned failure with code: " + result.httpCode) |
| 167 | + showErrorAndFinish() |
| 168 | + } |
| 169 | + } catch (e: Exception) { |
| 170 | + Log_OC.e(TAG, "Error when trying to fetch remote file", e) |
| 171 | + showErrorAndFinish() |
| 172 | + } |
| 173 | + }.start() |
| 174 | + } else { |
| 175 | + startDialog() |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + private fun startDialog() { |
| 180 | + val userOptional = user |
| 181 | + if (!userOptional.isPresent) { |
| 182 | + Log_OC.e(TAG, "User not present") |
| 183 | + showErrorAndFinish() |
| 184 | + } |
| 185 | + |
| 186 | + // Check whether the file is contained in the current Account |
| 187 | + val prev = supportFragmentManager.findFragmentByTag("conflictDialog") |
| 188 | + val fragmentTransaction = supportFragmentManager.beginTransaction() |
| 189 | + if (prev != null) { |
| 190 | + fragmentTransaction.remove(prev) |
| 191 | + } |
| 192 | + if (existingFile != null && storageManager.fileExists(newFile!!.remotePath)) { |
| 193 | + val dialog = ConflictsResolveDialog.newInstance( |
| 194 | + existingFile, |
| 195 | + newFile, |
| 196 | + userOptional.get() |
| 197 | + ) |
| 198 | + dialog.show(fragmentTransaction, "conflictDialog") |
| 199 | + } else { |
| 200 | + // Account was changed to a different one - just finish |
| 201 | + Log_OC.e(TAG, "Account was changed, finishing") |
| 202 | + showErrorAndFinish() |
| 203 | + } |
| 204 | + } |
| 205 | + |
| 206 | + private fun showErrorAndFinish() { |
| 207 | + runOnUiThread { Toast.makeText(this, R.string.conflict_dialog_error, Toast.LENGTH_LONG).show() } |
| 208 | + finish() |
| 209 | + } |
| 210 | + |
| 211 | + /** |
| 212 | + * @return whether the local version of the files is to be deleted. |
| 213 | + */ |
| 214 | + private fun shouldDeleteLocal(): Boolean { |
| 215 | + return localBehaviour == FileUploader.LOCAL_BEHAVIOUR_DELETE |
| 216 | + } |
| 217 | + |
| 218 | + companion object { |
| 219 | + /** |
| 220 | + * A nullable upload entry that must be removed when and if the conflict is resolved. |
| 221 | + */ |
| 222 | + const val EXTRA_CONFLICT_UPLOAD_ID = "CONFLICT_UPLOAD_ID" |
| 223 | + |
| 224 | + /** |
| 225 | + * Specify the upload local behaviour when there is no CONFLICT_UPLOAD. |
| 226 | + */ |
| 227 | + const val EXTRA_LOCAL_BEHAVIOUR = "LOCAL_BEHAVIOUR" |
| 228 | + const val EXTRA_EXISTING_FILE = "EXISTING_FILE" |
| 229 | + private val TAG = ConflictsResolveActivity::class.java.simpleName |
| 230 | + |
| 231 | + @JvmStatic |
| 232 | + fun createIntent( |
| 233 | + file: OCFile?, |
| 234 | + user: User?, |
| 235 | + conflictUploadId: Long, |
| 236 | + flag: Int?, |
| 237 | + context: Context? |
| 238 | + ): Intent { |
| 239 | + val intent = Intent(context, ConflictsResolveActivity::class.java) |
| 240 | + if (flag != null) { |
| 241 | + intent.flags = intent.flags or flag |
| 242 | + } |
| 243 | + intent.putExtra(EXTRA_FILE, file) |
| 244 | + intent.putExtra(EXTRA_USER, user) |
| 245 | + intent.putExtra(EXTRA_CONFLICT_UPLOAD_ID, conflictUploadId) |
| 246 | + return intent |
| 247 | + } |
| 248 | + } |
| 249 | +} |
0 commit comments