Code cleanups
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / activity / StorageMigrationActivity.java
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
23 import android.accounts.Account;
24 import android.accounts.AccountManager;
25 import android.content.ContentResolver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.os.AsyncTask;
29 import android.os.Bundle;
30 import android.support.v7.app.AppCompatActivity;
31 import android.view.View;
32 import android.widget.Button;
33 import android.widget.ProgressBar;
34 import android.widget.TextView;
35
36 import com.owncloud.android.MainApp;
37 import com.owncloud.android.R;
38 import com.owncloud.android.datamodel.FileDataStorageManager;
39 import com.owncloud.android.lib.common.utils.Log_OC;
40 import com.owncloud.android.utils.FileStorageUtils;
41
42 import java.io.File;
43
44 /**
45 * Created by Bartosz Przybylski on 07.11.2015.
46 */
47 public class StorageMigrationActivity extends AppCompatActivity {
48 private static final String TAG = StorageMigrationActivity.class.getName();
49 public static final String KEY_MIGRATION_TARGET_DIR = "MIGRATION_TARGET";
50 public static final String KEY_MIGRATION_SOURCE_DIR = "MIGRATION_SOURCE";
51
52 private ProgressBar mProgressBar;
53 private Button mFinishButton;
54 private TextView mFeedbackText;
55
56 @Override
57 public void onCreate(Bundle savedInstanceState) {
58 super.onCreate(savedInstanceState);
59
60 setContentView(R.layout.migration_layout);
61 mProgressBar = (ProgressBar)findViewById(R.id.migrationProgress);
62 mFinishButton = (Button)findViewById(R.id.finishButton);
63 mFeedbackText = (TextView)findViewById(R.id.migrationText);
64
65 mProgressBar.setProgress(0);
66 mFinishButton.setVisibility(View.INVISIBLE);
67 mFeedbackText.setText(R.string.file_migration_preparing);
68
69 mFinishButton.setOnClickListener(new View.OnClickListener() {
70 @Override
71 public void onClick(View view) {
72 setResult(RESULT_CANCELED);
73 finish();
74 }
75 });
76
77 String source = getIntent().getStringExtra(KEY_MIGRATION_SOURCE_DIR);
78 String destination = getIntent().getStringExtra(KEY_MIGRATION_TARGET_DIR);
79
80 if (source == null || destination == null) {
81 Log_OC.e(TAG, "source or destination is null");
82 finish();
83 }
84
85 new FileMigrationTask().execute(source, destination);
86 }
87
88 private class FileMigrationTask extends AsyncTask<String, Integer, Integer> {
89
90 private String mStorageTarget;
91 private String mStorageSource;
92 private int mProgress;
93
94 private static final int mProgressCopyUpperBound = 98;
95
96 private class MigrationException extends Exception {
97 private int mResId;
98 /*
99 * @param resId resource identifier to use for displaying error
100 */
101 MigrationException(int resId) {
102 super();
103 this.mResId = resId;
104 }
105
106 int getResId() { return mResId; }
107 }
108
109 private class MigrationCleanupException extends Exception {
110 MigrationCleanupException() {}
111 }
112
113 @Override
114 protected Integer doInBackground(String... args) {
115
116 mStorageSource = args[0];
117 mStorageTarget = args[1];
118 mProgress = 0;
119
120 publishProgress(mProgress++, R.string.file_migration_preparing);
121
122 Context context = StorageMigrationActivity.this;
123 String ocAuthority = context.getString(R.string.authority);
124
125 Account[] ocAccounts = AccountManager.get(context).getAccountsByType(MainApp.getAccountType());
126 boolean[] oldAutoSync = new boolean[ocAccounts.length];
127
128 try {
129 publishProgress(mProgress++, R.string.file_migration_checking_destination);
130
131 checkDestinationAvailability();
132
133 publishProgress(mProgress++, R.string.file_migration_saving_accounts_configuration);
134 saveAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
135
136 publishProgress(mProgress++, R.string.file_migration_waiting_for_unfinished_sync);
137 stopAccountsSyncing(ocAuthority, ocAccounts);
138 waitForUnfinishedSynchronizations(ocAuthority, ocAccounts);
139
140 publishProgress(mProgress++, R.string.file_migration_migrating);
141 copyFiles();
142
143 publishProgress(mProgress++, R.string.file_migration_updating_index);
144 updateIndex(context);
145
146 publishProgress(mProgress++, R.string.file_migration_cleaning);
147 cleanup();
148
149 } catch (MigrationException e) {
150 rollback();
151 return e.getResId();
152 } catch (MigrationCleanupException e) {
153 Log_OC.w(TAG, "Migration cleanup step failed");
154 return 0;
155 } finally {
156 publishProgress(mProgress++, R.string.file_migration_restoring_accounts_configuration);
157 restoreAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
158 }
159
160 publishProgress(mProgress++, R.string.file_migration_ok_finished);
161
162 return 0;
163 }
164
165 @Override
166 protected void onProgressUpdate(Integer... progress) {
167 mProgressBar.setProgress(progress[0]);
168 if (progress.length > 1)
169 mFeedbackText.setText(progress[1]);
170 }
171
172 @Override
173 protected void onPostExecute(Integer code) {
174 mFinishButton.setVisibility(View.VISIBLE);
175 if (code != 0) {
176 mFeedbackText.setText(code);
177 } else {
178 mFeedbackText.setText(R.string.file_migration_ok_finished);
179 mFinishButton.setOnClickListener(new View.OnClickListener() {
180 @Override
181 public void onClick(View view) {
182 Intent resultIntent = new Intent();
183 resultIntent.putExtra(KEY_MIGRATION_TARGET_DIR, mStorageTarget);
184 setResult(RESULT_OK, resultIntent);
185 finish();
186 }
187 });
188 }
189 }
190
191 void checkDestinationAvailability() throws MigrationException {
192 File srcFile = new File(mStorageSource);
193 File dstFile = new File(mStorageTarget);
194
195 if (!dstFile.canRead() || !srcFile.canRead())
196 throw new MigrationException(R.string.file_migration_failed_not_readable);
197
198 if (!dstFile.canWrite() || !srcFile.canWrite())
199 throw new MigrationException(R.string.file_migration_failed_not_writable);
200
201 if (new File(dstFile, MainApp.getDataFolder()).exists())
202 throw new MigrationException(R.string.file_migration_failed_dir_already_exists);
203
204 if (dstFile.getFreeSpace() < FileStorageUtils.getFolderSize(new File(srcFile, MainApp.getDataFolder())))
205 throw new MigrationException(R.string.file_migration_failed_not_enough_space);
206 }
207
208 void copyFiles() throws MigrationException {
209 File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
210 File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
211
212 copyDirs(srcFile, dstFile);
213 mProgress = Math.max(mProgress, mProgressCopyUpperBound);
214 publishProgress(mProgress);
215 }
216
217 void copyDirs(File src, File dst) throws MigrationException {
218 if (!dst.mkdirs())
219 throw new MigrationException(R.string.file_migration_failed_while_coping);
220
221 for (File f : src.listFiles()) {
222
223 mProgress = Math.min(mProgress+1, mProgressCopyUpperBound);
224 publishProgress(mProgress);
225
226 if (f.isDirectory())
227 copyDirs(f, new File(dst, f.getName()));
228 else if (!FileStorageUtils.copyFile(f, new File(dst, f.getName())))
229 throw new MigrationException(R.string.file_migration_failed_while_coping);
230 }
231
232 }
233
234 void updateIndex(Context context) throws MigrationException {
235 FileDataStorageManager manager = new FileDataStorageManager(null, context.getContentResolver());
236
237 try {
238 manager.migrateStoredFiles(mStorageSource, mStorageTarget);
239 } catch (Exception e) {
240 throw new MigrationException(R.string.file_migration_failed_while_updating_index);
241 }
242 }
243
244 void cleanup() throws MigrationCleanupException {
245 File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
246 if (!srcFile.delete())
247 throw new MigrationCleanupException();
248 }
249
250 void rollback() {
251 File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
252 if (dstFile.exists())
253 if (!dstFile.delete())
254 Log_OC.w(TAG, "Rollback step failed");
255 }
256
257 void saveAccountsSyncStatus(String authority, Account accounts[], boolean syncs[]) {
258 for (int i = 0; i < accounts.length; ++i)
259 syncs[i] = ContentResolver.getSyncAutomatically(accounts[i], authority);
260 }
261
262 void stopAccountsSyncing(String authority, Account accounts[]) {
263 for (int i = 0; i < accounts.length; ++i)
264 ContentResolver.setSyncAutomatically(accounts[i], authority, false);
265 }
266
267 void waitForUnfinishedSynchronizations(String authority, Account accounts[]) {
268 for (int i = 0; i < accounts.length; ++i)
269 while (ContentResolver.isSyncActive(accounts[i], authority))
270 try {
271 Thread.sleep(1000);
272 } catch (InterruptedException e) {
273 Log_OC.w(TAG, "Thread interrupted while waiting for account to end syncing");
274 Thread.currentThread().interrupt();
275 }
276 }
277
278 void restoreAccountsSyncStatus(String authority, Account accounts[], boolean oldSync[]) {
279 for (int i = 0; i < accounts.length; ++i)
280 ContentResolver.setSyncAutomatically(accounts[i], authority, oldSync[i]);
281 }
282
283 }
284 }