/**
* ownCloud Android client application
*
* @author Bartosz Przybylski
* Copyright (C) 2015 ownCloud Inc.
* Copyright (C) 2015 Bartosz Przybylski
*
* 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.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.utils.FileStorageUtils;
import java.io.File;
/**
* Created by Bartosz Przybylski on 07.11.2015.
*/
public class StorageMigrationActivity extends AppCompatActivity {
private static final String TAG = StorageMigrationActivity.class.getName();
public static final String KEY_MIGRATION_TARGET_DIR = "MIGRATION_TARGET";
public static final String KEY_MIGRATION_SOURCE_DIR = "MIGRATION_SOURCE";
private ProgressBar mProgressBar;
private Button mFinishButton;
private TextView mFeedbackText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.migration_layout);
mProgressBar = (ProgressBar)findViewById(R.id.migrationProgress);
mFinishButton = (Button)findViewById(R.id.finishButton);
mFeedbackText = (TextView)findViewById(R.id.migrationText);
mProgressBar.setProgress(0);
mFinishButton.setVisibility(View.INVISIBLE);
mFeedbackText.setText(R.string.file_migration_preparing);
mFinishButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
setResult(RESULT_CANCELED);
finish();
}
});
String source = getIntent().getStringExtra(KEY_MIGRATION_SOURCE_DIR);
String destination = getIntent().getStringExtra(KEY_MIGRATION_TARGET_DIR);
if (source == null || destination == null) {
Log_OC.e(TAG, "source or destination is null");
finish();
}
new FileMigrationTask().execute(source, destination);
}
private class FileMigrationTask extends AsyncTask {
private String mStorageTarget;
private String mStorageSource;
private int mProgress;
private static final int mProgressCopyUpperBound = 98;
private class MigrationException extends Exception {
private int mResId;
/*
* @param resId resource identifier to use for displaying error
*/
MigrationException(int resId) {
super();
this.mResId = resId;
}
int getResId() { return mResId; }
}
@Override
protected Integer doInBackground(String... args) {
mStorageSource = args[0];
mStorageTarget = args[1];
mProgress = 0;
publishProgress(mProgress++, R.string.file_migration_preparing);
Context context = StorageMigrationActivity.this;
String ocAuthority = context.getString(R.string.authority);
Account[] ocAccounts = AccountManager.get(context).getAccountsByType(MainApp.getAccountType());
boolean[] oldAutoSync = new boolean[ocAccounts.length];
Log_OC.stopLogging();
try {
publishProgress(mProgress++, R.string.file_migration_checking_destination);
checkDestinationAvailability();
publishProgress(mProgress++, R.string.file_migration_saving_accounts_configuration);
saveAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
publishProgress(mProgress++, R.string.file_migration_waiting_for_unfinished_sync);
stopAccountsSyncing(ocAuthority, ocAccounts);
waitForUnfinishedSynchronizations(ocAuthority, ocAccounts);
publishProgress(mProgress++, R.string.file_migration_migrating);
copyFiles();
publishProgress(mProgress++, R.string.file_migration_updating_index);
updateIndex(context);
publishProgress(mProgress++, R.string.file_migration_cleaning);
cleanup();
} catch (MigrationException e) {
rollback();
Log_OC.startLogging(mStorageSource);
return e.getResId();
} finally {
publishProgress(mProgress++, R.string.file_migration_restoring_accounts_configuration);
restoreAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
}
Log_OC.startLogging(mStorageTarget);
publishProgress(mProgress++, R.string.file_migration_ok_finished);
return 0;
}
@Override
protected void onProgressUpdate(Integer... progress) {
mProgressBar.setProgress(progress[0]);
if (progress.length > 1)
mFeedbackText.setText(progress[1]);
}
@Override
protected void onPostExecute(Integer code) {
mFinishButton.setVisibility(View.VISIBLE);
if (code != 0) {
mFeedbackText.setText(code);
} else {
mFeedbackText.setText(R.string.file_migration_ok_finished);
mFinishButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent resultIntent = new Intent();
resultIntent.putExtra(KEY_MIGRATION_TARGET_DIR, mStorageTarget);
setResult(RESULT_OK, resultIntent);
finish();
}
});
}
}
void checkDestinationAvailability() throws MigrationException {
File srcFile = new File(mStorageSource);
File dstFile = new File(mStorageTarget);
if (!dstFile.canRead() || !srcFile.canRead())
throw new MigrationException(R.string.file_migration_failed_not_readable);
if (!dstFile.canWrite() || !srcFile.canWrite())
throw new MigrationException(R.string.file_migration_failed_not_writable);
if (new File(dstFile, MainApp.getDataFolder()).exists())
throw new MigrationException(R.string.file_migration_failed_dir_already_exists);
if (dstFile.getFreeSpace() < FileStorageUtils.getFolderSize(new File(srcFile, MainApp.getDataFolder())))
throw new MigrationException(R.string.file_migration_failed_not_enough_space);
}
void copyFiles() throws MigrationException {
File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
copyDirs(srcFile, dstFile);
mProgress = Math.max(mProgress, mProgressCopyUpperBound);
publishProgress(mProgress);
}
void copyDirs(File src, File dst) throws MigrationException {
if (!dst.mkdirs())
throw new MigrationException(R.string.file_migration_failed_while_coping);
for (File f : src.listFiles()) {
mProgress = Math.min(mProgress+1, mProgressCopyUpperBound);
publishProgress(mProgress);
if (f.isDirectory())
copyDirs(f, new File(dst, f.getName()));
else if (!FileStorageUtils.copyFile(f, new File(dst, f.getName())))
throw new MigrationException(R.string.file_migration_failed_while_coping);
}
}
void updateIndex(Context context) throws MigrationException {
FileDataStorageManager manager = new FileDataStorageManager(null, context.getContentResolver());
try {
manager.migrateStoredFiles(mStorageSource, mStorageTarget);
} catch (Exception e) {
throw new MigrationException(R.string.file_migration_failed_while_updating_index);
}
}
void cleanup() {
File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
if (!deleteRecursive(srcFile))
Log_OC.w(TAG, "Migration cleanup step failed");
}
boolean deleteRecursive(File f) {
boolean res = true;
if (f.isDirectory())
for (File c : f.listFiles())
res = deleteRecursive(c) && res;
return f.delete() && res;
}
void rollback() {
File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
if (dstFile.exists())
if (!dstFile.delete())
Log_OC.w(TAG, "Rollback step failed");
}
void saveAccountsSyncStatus(String authority, Account accounts[], boolean syncs[]) {
for (int i = 0; i < accounts.length; ++i)
syncs[i] = ContentResolver.getSyncAutomatically(accounts[i], authority);
}
void stopAccountsSyncing(String authority, Account accounts[]) {
for (int i = 0; i < accounts.length; ++i)
ContentResolver.setSyncAutomatically(accounts[i], authority, false);
}
void waitForUnfinishedSynchronizations(String authority, Account accounts[]) {
for (int i = 0; i < accounts.length; ++i)
while (ContentResolver.isSyncActive(accounts[i], authority))
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log_OC.w(TAG, "Thread interrupted while waiting for account to end syncing");
Thread.currentThread().interrupt();
}
}
void restoreAccountsSyncStatus(String authority, Account accounts[], boolean oldSync[]) {
for (int i = 0; i < accounts.length; ++i)
ContentResolver.setSyncAutomatically(accounts[i], authority, oldSync[i]);
}
}
}