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