/* * ownCloud Android client application * * @author Bartek Przybylski * @author David A. Velasco Copyright (C) 2012 Bartek Przybylski Copyright (C) 2016 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.ui.activity; import android.accounts.Account; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.widget.Toast; import com.nextcloud.client.account.User; import com.nextcloud.java.util.Optional; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.UploadsStorageManager; import com.owncloud.android.db.OCUpload; import com.owncloud.android.files.services.FileDownloader; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.files.services.NameCollisionPolicy; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation; import com.owncloud.android.lib.resources.files.model.RemoteFile; import com.owncloud.android.ui.dialog.ConflictsResolveDialog; import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision; import com.owncloud.android.ui.dialog.ConflictsResolveDialog.OnConflictDecisionMadeListener; import com.owncloud.android.utils.FileStorageUtils; import javax.inject.Inject; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; /** * Wrapper activity which will be launched if keep-in-sync file will be modified by external application. */ public class ConflictsResolveActivity extends FileActivity implements OnConflictDecisionMadeListener { /** * A nullable upload entry that must be removed when and if the conflict is resolved. */ public static final String EXTRA_CONFLICT_UPLOAD_ID = "CONFLICT_UPLOAD_ID"; /** * Specify the upload local behaviour when there is no CONFLICT_UPLOAD. */ public static final String EXTRA_LOCAL_BEHAVIOUR = "LOCAL_BEHAVIOUR"; public static final String EXTRA_EXISTING_FILE = "EXISTING_FILE"; private static final String TAG = ConflictsResolveActivity.class.getSimpleName(); @Inject UploadsStorageManager uploadsStorageManager; private long conflictUploadId; private OCFile existingFile; private OCFile newFile; private int localBehaviour = FileUploader.LOCAL_BEHAVIOUR_FORGET; protected OnConflictDecisionMadeListener listener; public static Intent createIntent(OCFile file, User user, long conflictUploadId, Integer flag, Context context) { Intent intent = new Intent(context, ConflictsResolveActivity.class); if (flag != null) { intent.setFlags(intent.getFlags() | flag); } intent.putExtra(EXTRA_FILE, file); intent.putExtra(EXTRA_USER, user); intent.putExtra(EXTRA_CONFLICT_UPLOAD_ID, conflictUploadId); return intent; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { conflictUploadId = savedInstanceState.getLong(EXTRA_CONFLICT_UPLOAD_ID); existingFile = savedInstanceState.getParcelable(EXTRA_EXISTING_FILE); localBehaviour = savedInstanceState.getInt(EXTRA_LOCAL_BEHAVIOUR); } else { conflictUploadId = getIntent().getLongExtra(EXTRA_CONFLICT_UPLOAD_ID, -1); existingFile = getIntent().getParcelableExtra(EXTRA_EXISTING_FILE); localBehaviour = getIntent().getIntExtra(EXTRA_LOCAL_BEHAVIOUR, localBehaviour); } OCUpload upload = uploadsStorageManager.getUploadById(conflictUploadId); if (upload != null) { localBehaviour = upload.getLocalAction(); } // new file was modified locally in file system newFile = getFile(); listener = decision -> { OCFile file = newFile; // local file got changed, so either upload it or replace it again by server // version switch (decision) { case CANCEL: // nothing to do break; case KEEP_LOCAL: // Upload FileUploader.uploadUpdateFile( getBaseContext(), getAccount(), file, localBehaviour, NameCollisionPolicy.OVERWRITE ); uploadsStorageManager.removeUpload(upload); break; case KEEP_BOTH: // Upload FileUploader.uploadUpdateFile( getBaseContext(), getAccount(), file, localBehaviour, NameCollisionPolicy.RENAME ); uploadsStorageManager.removeUpload(upload); break; case KEEP_SERVER: // Download if (!shouldDeleteLocal()) { // Overwrite local file Intent intent = new Intent(getBaseContext(), FileDownloader.class); intent.putExtra(FileDownloader.EXTRA_USER, getUser().orElseThrow(RuntimeException::new)); intent.putExtra(FileDownloader.EXTRA_FILE, file); intent.putExtra(EXTRA_CONFLICT_UPLOAD_ID, conflictUploadId); startService(intent); } else { uploadsStorageManager.removeUpload(upload); } break; } finish(); }; } @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putLong(EXTRA_CONFLICT_UPLOAD_ID, conflictUploadId); outState.putParcelable(EXTRA_EXISTING_FILE, existingFile); outState.putInt(EXTRA_LOCAL_BEHAVIOUR, localBehaviour); } @Override public void conflictDecisionMade(Decision decision) { listener.conflictDecisionMade(decision); } @Override protected void onStart() { super.onStart(); if (getAccount() == null) { finish(); return; } if (newFile == null) { Log_OC.e(TAG, "No file received"); finish(); return; } if (existingFile == null) { // fetch info of existing file from server ReadFileRemoteOperation operation = new ReadFileRemoteOperation(newFile.getRemotePath()); new Thread(() -> { try { RemoteOperationResult result = operation.execute(getAccount(), this); if (result.isSuccess()) { existingFile = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0)); existingFile.setLastSyncDateForProperties(System.currentTimeMillis()); startDialog(); } else { showErrorAndFinish(); } } catch (Exception e) { showErrorAndFinish(); } }).start(); } else { startDialog(); } } private void startDialog() { Optional userOptional = getUser(); if (!userOptional.isPresent()) { showErrorAndFinish(); } // Check whether the file is contained in the current Account Fragment prev = getSupportFragmentManager().findFragmentByTag("conflictDialog"); FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); if (prev != null) { fragmentTransaction.remove(prev); } if (existingFile != null && getStorageManager().fileExists(newFile.getRemotePath())) { ConflictsResolveDialog dialog = ConflictsResolveDialog.newInstance(existingFile, newFile, userOptional.get()); dialog.show(fragmentTransaction, "conflictDialog"); } else { // Account was changed to a different one - just finish showErrorAndFinish(); } } private void showErrorAndFinish() { runOnUiThread(() -> Toast.makeText(this, R.string.conflict_dialog_error, Toast.LENGTH_LONG).show()); finish(); } /** * @return whether the local version of the files is to be deleted. */ private boolean shouldDeleteLocal() { return localBehaviour == FileUploader.LOCAL_BEHAVIOUR_DELETE; } }