Stop and start logging on migration
[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 Log_OC.stopLogging();
129
130 try {
131 publishProgress(mProgress++, R.string.file_migration_checking_destination);
132
133 checkDestinationAvailability();
134
135 publishProgress(mProgress++, R.string.file_migration_saving_accounts_configuration);
136 saveAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
137
138 publishProgress(mProgress++, R.string.file_migration_waiting_for_unfinished_sync);
139 stopAccountsSyncing(ocAuthority, ocAccounts);
140 waitForUnfinishedSynchronizations(ocAuthority, ocAccounts);
141
142 publishProgress(mProgress++, R.string.file_migration_migrating);
143 copyFiles();
144
145 publishProgress(mProgress++, R.string.file_migration_updating_index);
146 updateIndex(context);
147
148 publishProgress(mProgress++, R.string.file_migration_cleaning);
149 cleanup();
150
151 } catch (MigrationException e) {
152 rollback();
153 Log_OC.startLogging(mStorageSource);
154 return e.getResId();
155 } catch (MigrationCleanupException e) {
156 Log_OC.w(TAG, "Migration cleanup step failed");
157 Log_OC.startLogging(mStorageSource);
158 return 0;
159 } finally {
160 publishProgress(mProgress++, R.string.file_migration_restoring_accounts_configuration);
161 restoreAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
162 }
163
164 Log_OC.startLogging(mStorageTarget);
165 publishProgress(mProgress++, R.string.file_migration_ok_finished);
166
167 return 0;
168 }
169
170 @Override
171 protected void onProgressUpdate(Integer... progress) {
172 mProgressBar.setProgress(progress[0]);
173 if (progress.length > 1)
174 mFeedbackText.setText(progress[1]);
175 }
176
177 @Override
178 protected void onPostExecute(Integer code) {
179 mFinishButton.setVisibility(View.VISIBLE);
180 if (code != 0) {
181 mFeedbackText.setText(code);
182 } else {
183 mFeedbackText.setText(R.string.file_migration_ok_finished);
184 mFinishButton.setOnClickListener(new View.OnClickListener() {
185 @Override
186 public void onClick(View view) {
187 Intent resultIntent = new Intent();
188 resultIntent.putExtra(KEY_MIGRATION_TARGET_DIR, mStorageTarget);
189 setResult(RESULT_OK, resultIntent);
190 finish();
191 }
192 });
193 }
194 }
195
196 void checkDestinationAvailability() throws MigrationException {
197 File srcFile = new File(mStorageSource);
198 File dstFile = new File(mStorageTarget);
199
200 if (!dstFile.canRead() || !srcFile.canRead())
201 throw new MigrationException(R.string.file_migration_failed_not_readable);
202
203 if (!dstFile.canWrite() || !srcFile.canWrite())
204 throw new MigrationException(R.string.file_migration_failed_not_writable);
205
206 if (new File(dstFile, MainApp.getDataFolder()).exists())
207 throw new MigrationException(R.string.file_migration_failed_dir_already_exists);
208
209 if (dstFile.getFreeSpace() < FileStorageUtils.getFolderSize(new File(srcFile, MainApp.getDataFolder())))
210 throw new MigrationException(R.string.file_migration_failed_not_enough_space);
211 }
212
213 void copyFiles() throws MigrationException {
214 File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
215 File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
216
217 copyDirs(srcFile, dstFile);
218 mProgress = Math.max(mProgress, mProgressCopyUpperBound);
219 publishProgress(mProgress);
220 }
221
222 void copyDirs(File src, File dst) throws MigrationException {
223 if (!dst.mkdirs())
224 throw new MigrationException(R.string.file_migration_failed_while_coping);
225
226 for (File f : src.listFiles()) {
227
228 mProgress = Math.min(mProgress+1, mProgressCopyUpperBound);
229 publishProgress(mProgress);
230
231 if (f.isDirectory())
232 copyDirs(f, new File(dst, f.getName()));
233 else if (!FileStorageUtils.copyFile(f, new File(dst, f.getName())))
234 throw new MigrationException(R.string.file_migration_failed_while_coping);
235 }
236
237 }
238
239 void updateIndex(Context context) throws MigrationException {
240 FileDataStorageManager manager = new FileDataStorageManager(null, context.getContentResolver());
241
242 try {
243 manager.migrateStoredFiles(mStorageSource, mStorageTarget);
244 } catch (Exception e) {
245 throw new MigrationException(R.string.file_migration_failed_while_updating_index);
246 }
247 }
248
249 void cleanup() throws MigrationCleanupException {
250 File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
251 if (!srcFile.delete())
252 throw new MigrationCleanupException();
253 }
254
255 void rollback() {
256 File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
257 if (dstFile.exists())
258 if (!dstFile.delete())
259 Log_OC.w(TAG, "Rollback step failed");
260 }
261
262 void saveAccountsSyncStatus(String authority, Account accounts[], boolean syncs[]) {
263 for (int i = 0; i < accounts.length; ++i)
264 syncs[i] = ContentResolver.getSyncAutomatically(accounts[i], authority);
265 }
266
267 void stopAccountsSyncing(String authority, Account accounts[]) {
268 for (int i = 0; i < accounts.length; ++i)
269 ContentResolver.setSyncAutomatically(accounts[i], authority, false);
270 }
271
272 void waitForUnfinishedSynchronizations(String authority, Account accounts[]) {
273 for (int i = 0; i < accounts.length; ++i)
274 while (ContentResolver.isSyncActive(accounts[i], authority))
275 try {
276 Thread.sleep(1000);
277 } catch (InterruptedException e) {
278 Log_OC.w(TAG, "Thread interrupted while waiting for account to end syncing");
279 Thread.currentThread().interrupt();
280 }
281 }
282
283 void restoreAccountsSyncStatus(String authority, Account accounts[], boolean oldSync[]) {
284 for (int i = 0; i < accounts.length; ++i)
285 ContentResolver.setSyncAutomatically(accounts[i], authority, oldSync[i]);
286 }
287
288 }
289 }