StorageMigrationActivity.java 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /**
  2. * ownCloud Android client application
  3. *
  4. * @author Bartosz Przybylski
  5. * Copyright (C) 2015 ownCloud Inc.
  6. * Copyright (C) 2015 Bartosz Przybylski
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License version 2,
  10. * as published by the Free Software Foundation.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. */
  21. package com.owncloud.android.ui.activity;
  22. import android.accounts.Account;
  23. import android.accounts.AccountManager;
  24. import android.content.ContentResolver;
  25. import android.content.Context;
  26. import android.content.Intent;
  27. import android.os.AsyncTask;
  28. import android.os.Bundle;
  29. import android.support.v7.app.AppCompatActivity;
  30. import android.view.View;
  31. import android.widget.Button;
  32. import android.widget.ProgressBar;
  33. import android.widget.TextView;
  34. import com.owncloud.android.MainApp;
  35. import com.owncloud.android.R;
  36. import com.owncloud.android.datamodel.FileDataStorageManager;
  37. import com.owncloud.android.lib.common.utils.Log_OC;
  38. import com.owncloud.android.utils.FileStorageUtils;
  39. import java.io.File;
  40. /**
  41. * Created by Bartosz Przybylski on 07.11.2015.
  42. */
  43. public class StorageMigrationActivity extends AppCompatActivity {
  44. private static final String TAG = StorageMigrationActivity.class.getName();
  45. public static final String KEY_MIGRATION_TARGET_DIR = "MIGRATION_TARGET";
  46. public static final String KEY_MIGRATION_SOURCE_DIR = "MIGRATION_SOURCE";
  47. private ProgressBar mProgressBar;
  48. private Button mFinishButton;
  49. private TextView mFeedbackText;
  50. @Override
  51. public void onCreate(Bundle savedInstanceState) {
  52. super.onCreate(savedInstanceState);
  53. setContentView(R.layout.migration_layout);
  54. mProgressBar = (ProgressBar)findViewById(R.id.migrationProgress);
  55. mFinishButton = (Button)findViewById(R.id.finishButton);
  56. mFeedbackText = (TextView)findViewById(R.id.migrationText);
  57. mProgressBar.setProgress(0);
  58. mFinishButton.setVisibility(View.INVISIBLE);
  59. mFeedbackText.setText(R.string.file_migration_preparing);
  60. mFinishButton.setOnClickListener(new View.OnClickListener() {
  61. @Override
  62. public void onClick(View view) {
  63. setResult(RESULT_CANCELED);
  64. finish();
  65. }
  66. });
  67. String source = getIntent().getStringExtra(KEY_MIGRATION_SOURCE_DIR);
  68. String destination = getIntent().getStringExtra(KEY_MIGRATION_TARGET_DIR);
  69. if (source == null || destination == null) {
  70. Log_OC.e(TAG, "source or destination is null");
  71. finish();
  72. }
  73. new FileMigrationTask().execute(source, destination);
  74. }
  75. private class FileMigrationTask extends AsyncTask<String, Integer, Integer> {
  76. private String mStorageTarget;
  77. private String mStorageSource;
  78. private int mProgress;
  79. private static final int mProgressCopyUpperBound = 98;
  80. private class MigrationException extends Exception {
  81. private int mResId;
  82. /*
  83. * @param resId resource identifier to use for displaying error
  84. */
  85. MigrationException(int resId) {
  86. super();
  87. this.mResId = resId;
  88. }
  89. int getResId() { return mResId; }
  90. }
  91. @Override
  92. protected Integer doInBackground(String... args) {
  93. mStorageSource = args[0];
  94. mStorageTarget = args[1];
  95. mProgress = 0;
  96. publishProgress(mProgress++, R.string.file_migration_preparing);
  97. Context context = StorageMigrationActivity.this;
  98. String ocAuthority = context.getString(R.string.authority);
  99. Account[] ocAccounts = AccountManager.get(context).getAccountsByType(MainApp.getAccountType());
  100. boolean[] oldAutoSync = new boolean[ocAccounts.length];
  101. Log_OC.stopLogging();
  102. try {
  103. publishProgress(mProgress++, R.string.file_migration_checking_destination);
  104. checkDestinationAvailability();
  105. publishProgress(mProgress++, R.string.file_migration_saving_accounts_configuration);
  106. saveAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
  107. publishProgress(mProgress++, R.string.file_migration_waiting_for_unfinished_sync);
  108. stopAccountsSyncing(ocAuthority, ocAccounts);
  109. waitForUnfinishedSynchronizations(ocAuthority, ocAccounts);
  110. publishProgress(mProgress++, R.string.file_migration_migrating);
  111. copyFiles();
  112. publishProgress(mProgress++, R.string.file_migration_updating_index);
  113. updateIndex(context);
  114. publishProgress(mProgress++, R.string.file_migration_cleaning);
  115. cleanup();
  116. } catch (MigrationException e) {
  117. rollback();
  118. Log_OC.startLogging(mStorageSource);
  119. return e.getResId();
  120. } finally {
  121. publishProgress(mProgress++, R.string.file_migration_restoring_accounts_configuration);
  122. restoreAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
  123. }
  124. Log_OC.startLogging(mStorageTarget);
  125. publishProgress(mProgress++, R.string.file_migration_ok_finished);
  126. return 0;
  127. }
  128. @Override
  129. protected void onProgressUpdate(Integer... progress) {
  130. mProgressBar.setProgress(progress[0]);
  131. if (progress.length > 1)
  132. mFeedbackText.setText(progress[1]);
  133. }
  134. @Override
  135. protected void onPostExecute(Integer code) {
  136. mFinishButton.setVisibility(View.VISIBLE);
  137. if (code != 0) {
  138. mFeedbackText.setText(code);
  139. } else {
  140. mFeedbackText.setText(R.string.file_migration_ok_finished);
  141. mFinishButton.setOnClickListener(new View.OnClickListener() {
  142. @Override
  143. public void onClick(View view) {
  144. Intent resultIntent = new Intent();
  145. resultIntent.putExtra(KEY_MIGRATION_TARGET_DIR, mStorageTarget);
  146. setResult(RESULT_OK, resultIntent);
  147. finish();
  148. }
  149. });
  150. }
  151. }
  152. void checkDestinationAvailability() throws MigrationException {
  153. File srcFile = new File(mStorageSource);
  154. File dstFile = new File(mStorageTarget);
  155. if (!dstFile.canRead() || !srcFile.canRead())
  156. throw new MigrationException(R.string.file_migration_failed_not_readable);
  157. if (!dstFile.canWrite() || !srcFile.canWrite())
  158. throw new MigrationException(R.string.file_migration_failed_not_writable);
  159. if (new File(dstFile, MainApp.getDataFolder()).exists())
  160. throw new MigrationException(R.string.file_migration_failed_dir_already_exists);
  161. if (dstFile.getFreeSpace() < FileStorageUtils.getFolderSize(new File(srcFile, MainApp.getDataFolder())))
  162. throw new MigrationException(R.string.file_migration_failed_not_enough_space);
  163. }
  164. void copyFiles() throws MigrationException {
  165. File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
  166. File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
  167. copyDirs(srcFile, dstFile);
  168. mProgress = Math.max(mProgress, mProgressCopyUpperBound);
  169. publishProgress(mProgress);
  170. }
  171. void copyDirs(File src, File dst) throws MigrationException {
  172. if (!dst.mkdirs())
  173. throw new MigrationException(R.string.file_migration_failed_while_coping);
  174. for (File f : src.listFiles()) {
  175. mProgress = Math.min(mProgress+1, mProgressCopyUpperBound);
  176. publishProgress(mProgress);
  177. if (f.isDirectory())
  178. copyDirs(f, new File(dst, f.getName()));
  179. else if (!FileStorageUtils.copyFile(f, new File(dst, f.getName())))
  180. throw new MigrationException(R.string.file_migration_failed_while_coping);
  181. }
  182. }
  183. void updateIndex(Context context) throws MigrationException {
  184. FileDataStorageManager manager = new FileDataStorageManager(null, context.getContentResolver());
  185. try {
  186. manager.migrateStoredFiles(mStorageSource, mStorageTarget);
  187. } catch (Exception e) {
  188. throw new MigrationException(R.string.file_migration_failed_while_updating_index);
  189. }
  190. }
  191. void cleanup() {
  192. File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
  193. if (!deleteRecursive(srcFile))
  194. Log_OC.w(TAG, "Migration cleanup step failed");
  195. }
  196. boolean deleteRecursive(File f) {
  197. boolean res = true;
  198. if (f.isDirectory())
  199. for (File c : f.listFiles())
  200. res = deleteRecursive(c) && res;
  201. return f.delete() && res;
  202. }
  203. void rollback() {
  204. File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
  205. if (dstFile.exists())
  206. if (!dstFile.delete())
  207. Log_OC.w(TAG, "Rollback step failed");
  208. }
  209. void saveAccountsSyncStatus(String authority, Account accounts[], boolean syncs[]) {
  210. for (int i = 0; i < accounts.length; ++i)
  211. syncs[i] = ContentResolver.getSyncAutomatically(accounts[i], authority);
  212. }
  213. void stopAccountsSyncing(String authority, Account accounts[]) {
  214. for (int i = 0; i < accounts.length; ++i)
  215. ContentResolver.setSyncAutomatically(accounts[i], authority, false);
  216. }
  217. void waitForUnfinishedSynchronizations(String authority, Account accounts[]) {
  218. for (int i = 0; i < accounts.length; ++i)
  219. while (ContentResolver.isSyncActive(accounts[i], authority))
  220. try {
  221. Thread.sleep(1000);
  222. } catch (InterruptedException e) {
  223. Log_OC.w(TAG, "Thread interrupted while waiting for account to end syncing");
  224. Thread.currentThread().interrupt();
  225. }
  226. }
  227. void restoreAccountsSyncStatus(String authority, Account accounts[], boolean oldSync[]) {
  228. for (int i = 0; i < accounts.length; ++i)
  229. ContentResolver.setSyncAutomatically(accounts[i], authority, oldSync[i]);
  230. }
  231. }
  232. }