/** * 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]); } } }