2 * ownCloud Android client application
4 * @author Bartosz Przybylski
5 * Copyright (C) 2015 ownCloud Inc.
6 * Copyright (C) 2015 Bartosz Przybylski
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.
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.
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/>.
21 package com
.owncloud
.android
.ui
.activity
;
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
;
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
;
45 * Created by Bartosz Przybylski on 07.11.2015.
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";
52 private ProgressBar mProgressBar
;
53 private Button mFinishButton
;
54 private TextView mFeedbackText
;
57 public void onCreate(Bundle savedInstanceState
) {
58 super.onCreate(savedInstanceState
);
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
);
65 mProgressBar
.setProgress(0);
66 mFinishButton
.setVisibility(View
.INVISIBLE
);
67 mFeedbackText
.setText(R
.string
.file_migration_preparing
);
69 mFinishButton
.setOnClickListener(new View
.OnClickListener() {
71 public void onClick(View view
) {
72 setResult(RESULT_CANCELED
);
77 String source
= getIntent().getStringExtra(KEY_MIGRATION_SOURCE_DIR
);
78 String destination
= getIntent().getStringExtra(KEY_MIGRATION_TARGET_DIR
);
80 if (source
== null
|| destination
== null
) {
81 Log_OC
.e(TAG
, "source or destination is null");
85 new FileMigrationTask().execute(source
, destination
);
88 private class FileMigrationTask
extends AsyncTask
<String
, Integer
, Integer
> {
90 private String mStorageTarget
;
91 private String mStorageSource
;
92 private int mProgress
;
94 private static final int mProgressCopyUpperBound
= 98;
96 private class MigrationException
extends Exception
{
99 * @param resId resource identifier to use for displaying error
101 MigrationException(int resId
) {
106 int getResId() { return mResId
; }
109 private class MigrationCleanupException
extends Exception
{
110 MigrationCleanupException() {}
114 protected Integer
doInBackground(String
... args
) {
116 mStorageSource
= args
[0];
117 mStorageTarget
= args
[1];
120 publishProgress(mProgress
++, R
.string
.file_migration_preparing
);
122 Context context
= StorageMigrationActivity
.this;
123 String ocAuthority
= context
.getString(R
.string
.authority
);
125 Account
[] ocAccounts
= AccountManager
.get(context
).getAccountsByType(MainApp
.getAccountType());
126 boolean[] oldAutoSync
= new boolean[ocAccounts
.length
];
128 Log_OC
.stopLogging();
131 publishProgress(mProgress
++, R
.string
.file_migration_checking_destination
);
133 checkDestinationAvailability();
135 publishProgress(mProgress
++, R
.string
.file_migration_saving_accounts_configuration
);
136 saveAccountsSyncStatus(ocAuthority
, ocAccounts
, oldAutoSync
);
138 publishProgress(mProgress
++, R
.string
.file_migration_waiting_for_unfinished_sync
);
139 stopAccountsSyncing(ocAuthority
, ocAccounts
);
140 waitForUnfinishedSynchronizations(ocAuthority
, ocAccounts
);
142 publishProgress(mProgress
++, R
.string
.file_migration_migrating
);
145 publishProgress(mProgress
++, R
.string
.file_migration_updating_index
);
146 updateIndex(context
);
148 publishProgress(mProgress
++, R
.string
.file_migration_cleaning
);
151 } catch (MigrationException e
) {
153 Log_OC
.startLogging(mStorageSource
);
155 } catch (MigrationCleanupException e
) {
156 Log_OC
.w(TAG
, "Migration cleanup step failed");
157 Log_OC
.startLogging(mStorageSource
);
160 publishProgress(mProgress
++, R
.string
.file_migration_restoring_accounts_configuration
);
161 restoreAccountsSyncStatus(ocAuthority
, ocAccounts
, oldAutoSync
);
164 Log_OC
.startLogging(mStorageTarget
);
165 publishProgress(mProgress
++, R
.string
.file_migration_ok_finished
);
171 protected void onProgressUpdate(Integer
... progress
) {
172 mProgressBar
.setProgress(progress
[0]);
173 if (progress
.length
> 1)
174 mFeedbackText
.setText(progress
[1]);
178 protected void onPostExecute(Integer code
) {
179 mFinishButton
.setVisibility(View
.VISIBLE
);
181 mFeedbackText
.setText(code
);
183 mFeedbackText
.setText(R
.string
.file_migration_ok_finished
);
184 mFinishButton
.setOnClickListener(new View
.OnClickListener() {
186 public void onClick(View view
) {
187 Intent resultIntent
= new Intent();
188 resultIntent
.putExtra(KEY_MIGRATION_TARGET_DIR
, mStorageTarget
);
189 setResult(RESULT_OK
, resultIntent
);
196 void checkDestinationAvailability() throws MigrationException
{
197 File srcFile
= new File(mStorageSource
);
198 File dstFile
= new File(mStorageTarget
);
200 if (!dstFile
.canRead() || !srcFile
.canRead())
201 throw new MigrationException(R
.string
.file_migration_failed_not_readable
);
203 if (!dstFile
.canWrite() || !srcFile
.canWrite())
204 throw new MigrationException(R
.string
.file_migration_failed_not_writable
);
206 if (new File(dstFile
, MainApp
.getDataFolder()).exists())
207 throw new MigrationException(R
.string
.file_migration_failed_dir_already_exists
);
209 if (dstFile
.getFreeSpace() < FileStorageUtils
.getFolderSize(new File(srcFile
, MainApp
.getDataFolder())))
210 throw new MigrationException(R
.string
.file_migration_failed_not_enough_space
);
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());
217 copyDirs(srcFile
, dstFile
);
218 mProgress
= Math
.max(mProgress
, mProgressCopyUpperBound
);
219 publishProgress(mProgress
);
222 void copyDirs(File src
, File dst
) throws MigrationException
{
224 throw new MigrationException(R
.string
.file_migration_failed_while_coping
);
226 for (File f
: src
.listFiles()) {
228 mProgress
= Math
.min(mProgress
+1, mProgressCopyUpperBound
);
229 publishProgress(mProgress
);
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
);
239 void updateIndex(Context context
) throws MigrationException
{
240 FileDataStorageManager manager
= new FileDataStorageManager(null
, context
.getContentResolver());
243 manager
.migrateStoredFiles(mStorageSource
, mStorageTarget
);
244 } catch (Exception e
) {
245 throw new MigrationException(R
.string
.file_migration_failed_while_updating_index
);
249 void cleanup() throws MigrationCleanupException
{
250 File srcFile
= new File(mStorageSource
+ File
.separator
+ MainApp
.getDataFolder());
251 if (!srcFile
.delete())
252 throw new MigrationCleanupException();
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");
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
);
267 void stopAccountsSyncing(String authority
, Account accounts
[]) {
268 for (int i
= 0; i
< accounts
.length
; ++i
)
269 ContentResolver
.setSyncAutomatically(accounts
[i
], authority
, false
);
272 void waitForUnfinishedSynchronizations(String authority
, Account accounts
[]) {
273 for (int i
= 0; i
< accounts
.length
; ++i
)
274 while (ContentResolver
.isSyncActive(accounts
[i
], authority
))
277 } catch (InterruptedException e
) {
278 Log_OC
.w(TAG
, "Thread interrupted while waiting for account to end syncing");
279 Thread
.currentThread().interrupt();
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
]);