import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
+import android.provider.BaseColumns;
import android.provider.MediaStore;
import com.owncloud.android.MainApp;
return ret;
}
+ public void migrateStoredFiles(String srcPath, String dstPath) throws Exception {
+ Cursor c = null;
+ if (getContentResolver() != null) {
+ c = getContentResolver().query(ProviderTableMeta.CONTENT_URI_FILE,
+ null,
+ ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL",
+ null,
+ null);
+
+ } else {
+ try {
+ c = getContentProviderClient().query(ProviderTableMeta.CONTENT_URI_FILE,
+ new String[]{ProviderTableMeta._ID, ProviderTableMeta.FILE_STORAGE_PATH},
+ ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL",
+ null,
+ null);
+ } catch (RemoteException e) {
+ Log_OC.e(TAG, e.getMessage());
+ throw e;
+ }
+ }
+
+ ArrayList<ContentProviderOperation> operations =
+ new ArrayList<ContentProviderOperation>(c.getCount());
+ if (c.moveToFirst()) {
+ do {
+ ContentValues cv = new ContentValues();
+ long fileId = c.getLong(c.getColumnIndex(ProviderTableMeta._ID));
+ String oldFileStoragePath = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH));
+
+ if (oldFileStoragePath.startsWith(srcPath)) {
+
+ cv.put(
+ ProviderTableMeta.FILE_STORAGE_PATH,
+ oldFileStoragePath.replaceFirst(srcPath, dstPath));
+
+ operations.add(
+ ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
+ withValues(cv).
+ withSelection(
+ ProviderTableMeta._ID + "=?",
+ new String[]{String.valueOf(fileId)}
+ )
+ .build());
+ }
+
+ } while (c.moveToNext());
+ }
+ c.close();
+
+ /// 3. apply updates in batch
+ try {
+ if (getContentResolver() != null) {
+ getContentResolver().applyBatch(MainApp.getAuthority(), operations);
+
+ } else {
+ getContentProviderClient().applyBatch(operations);
+ }
+
+ } catch (Exception e) {
+ throw e;
+ }
+ }
private Vector<OCFile> getFolderContent(long parentId/*, boolean onlyOnDevice*/) {
*/
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 java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
/**
* 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<String, Integer, Integer> {
+
+ private String mStorageTarget;
+ private String mStorageSource;
+
+ 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];
+
+ int progress = 0;
+ publishProgress(progress++, 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];
+
+ try {
+ publishProgress(progress++, R.string.file_migration_checking_destination);
+
+ checkDestinationAvailability();
+
+ publishProgress(progress++, R.string.file_migration_saving_accounts_configuration);
+ saveAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
+
+ publishProgress(progress++, R.string.file_migration_waiting_for_unfinished_sync);
+ stopAccountsSyncing(ocAuthority, ocAccounts);
+ waitForUnfinishedSynchronizations(ocAuthority, ocAccounts);
+
+ publishProgress(progress++, R.string.file_migration_migrating);
+ copyFiles();
+
+ publishProgress(progress++, R.string.file_migration_updating_index);
+ updateIndex(context);
+
+ publishProgress(progress++, R.string.file_migration_cleaning);
+ cleanup();
+
+ } catch (MigrationException e) {
+ return e.getResId();
+ } finally {
+ publishProgress(progress++, R.string.file_migration_restoring_accounts_configuration);
+ restoreAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
+ }
+
+ publishProgress(progress++, 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() < calculateUsedSpace(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);
+ }
+
+ private boolean copyFile(File src, File target) {
+ boolean ret = true;
+
+ InputStream in = null;
+ OutputStream out = null;
+
+ try {
+ in = new FileInputStream(src);
+ out = new FileOutputStream(target);
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = in.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ } catch (IOException ex) {
+ ret = false;
+ } finally {
+ if (in != null) try {
+ in.close();
+ } catch (IOException e) {
+ e.printStackTrace(System.err);
+ }
+ if (out != null) try {
+ out.close();
+ } catch (IOException e) {
+ e.printStackTrace(System.err);
+ }
+ }
+
+ return ret;
+ }
+
+ 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()) {
+ if (f.isDirectory())
+ copyDirs(f, new File(dst, f.getName()));
+ else if (!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() {
+
+ }
+
+ long calculateUsedSpace(File dir) {
+ long result = 0;
+
+ for (File f : dir.listFiles()) {
+ if (f.isDirectory())
+ result += calculateUsedSpace(f);
+ else
+ result += f.length();
+ }
+
+ return result;
+ }
+
+ 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");
+ }
+ }
+
+
+ void restoreAccountsSyncStatus(String authority, Account accounts[], boolean oldSync[]) {
+ for (int i = 0; i < accounts.length; ++i)
+ ContentResolver.setSyncAutomatically(accounts[i], authority, oldSync[i]);
+ }
+
+ }
}