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
;
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
;
49 * Created by Bartosz Przybylski on 07.11.2015.
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";
56 private ProgressBar mProgressBar
;
57 private Button mFinishButton
;
58 private TextView mFeedbackText
;
61 public void onCreate(Bundle savedInstanceState
) {
62 super.onCreate(savedInstanceState
);
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
);
69 mProgressBar
.setProgress(0);
70 mFinishButton
.setVisibility(View
.INVISIBLE
);
71 mFeedbackText
.setText(R
.string
.file_migration_preparing
);
73 mFinishButton
.setOnClickListener(new View
.OnClickListener() {
75 public void onClick(View view
) {
76 setResult(RESULT_CANCELED
);
81 String source
= getIntent().getStringExtra(KEY_MIGRATION_SOURCE_DIR
);
82 String destination
= getIntent().getStringExtra(KEY_MIGRATION_TARGET_DIR
);
84 if (source
== null
|| destination
== null
) {
85 Log_OC
.e(TAG
, "source or destination is null");
89 new FileMigrationTask().execute(source
, destination
);
92 private class FileMigrationTask
extends AsyncTask
<String
, Integer
, Integer
> {
94 private String mStorageTarget
;
95 private String mStorageSource
;
96 private int mProgress
;
98 private static final int mProgressCopyUpperBound
= 98;
100 private class MigrationException
extends Exception
{
103 * @param resId resource identifier to use for displaying error
105 MigrationException(int resId
) {
110 int getResId() { return mResId
; }
113 private class MigrationCleanupException
extends Exception
{
114 MigrationCleanupException() {}
118 protected Integer
doInBackground(String
... args
) {
120 mStorageSource
= args
[0];
121 mStorageTarget
= args
[1];
124 publishProgress(mProgress
++, R
.string
.file_migration_preparing
);
126 Context context
= StorageMigrationActivity
.this;
127 String ocAuthority
= context
.getString(R
.string
.authority
);
129 Account
[] ocAccounts
= AccountManager
.get(context
).getAccountsByType(MainApp
.getAccountType());
130 boolean[] oldAutoSync
= new boolean[ocAccounts
.length
];
133 publishProgress(mProgress
++, R
.string
.file_migration_checking_destination
);
135 checkDestinationAvailability();
137 publishProgress(mProgress
++, R
.string
.file_migration_saving_accounts_configuration
);
138 saveAccountsSyncStatus(ocAuthority
, ocAccounts
, oldAutoSync
);
140 publishProgress(mProgress
++, R
.string
.file_migration_waiting_for_unfinished_sync
);
141 stopAccountsSyncing(ocAuthority
, ocAccounts
);
142 waitForUnfinishedSynchronizations(ocAuthority
, ocAccounts
);
144 publishProgress(mProgress
++, R
.string
.file_migration_migrating
);
147 publishProgress(mProgress
++, R
.string
.file_migration_updating_index
);
148 updateIndex(context
);
150 publishProgress(mProgress
++, R
.string
.file_migration_cleaning
);
153 } catch (MigrationException e
) {
156 } catch (MigrationCleanupException e
) {
157 Log_OC
.w(TAG
, "Migration mleanup step failed");
160 publishProgress(mProgress
++, R
.string
.file_migration_restoring_accounts_configuration
);
161 restoreAccountsSyncStatus(ocAuthority
, ocAccounts
, oldAutoSync
);
164 publishProgress(mProgress
++, R
.string
.file_migration_ok_finished
);
170 protected void onProgressUpdate(Integer
... progress
) {
171 mProgressBar
.setProgress(progress
[0]);
172 if (progress
.length
> 1)
173 mFeedbackText
.setText(progress
[1]);
177 protected void onPostExecute(Integer code
) {
178 mFinishButton
.setVisibility(View
.VISIBLE
);
180 mFeedbackText
.setText(code
);
182 mFeedbackText
.setText(R
.string
.file_migration_ok_finished
);
183 mFinishButton
.setOnClickListener(new View
.OnClickListener() {
185 public void onClick(View view
) {
186 Intent resultIntent
= new Intent();
187 resultIntent
.putExtra(KEY_MIGRATION_TARGET_DIR
, mStorageTarget
);
188 setResult(RESULT_OK
, resultIntent
);
195 void checkDestinationAvailability() throws MigrationException
{
196 File srcFile
= new File(mStorageSource
);
197 File dstFile
= new File(mStorageTarget
);
199 if (!dstFile
.canRead() || !srcFile
.canRead())
200 throw new MigrationException(R
.string
.file_migration_failed_not_readable
);
202 if (!dstFile
.canWrite() || !srcFile
.canWrite())
203 throw new MigrationException(R
.string
.file_migration_failed_not_writable
);
205 if (new File(dstFile
, MainApp
.getDataFolder()).exists())
206 throw new MigrationException(R
.string
.file_migration_failed_dir_already_exists
);
208 if (dstFile
.getFreeSpace() < calculateUsedSpace(new File(srcFile
, MainApp
.getDataFolder())))
209 throw new MigrationException(R
.string
.file_migration_failed_not_enough_space
);
212 void copyFiles() throws MigrationException
{
213 File srcFile
= new File(mStorageSource
+ File
.separator
+ MainApp
.getDataFolder());
214 File dstFile
= new File(mStorageTarget
+ File
.separator
+ MainApp
.getDataFolder());
216 copyDirs(srcFile
, dstFile
);
217 mProgress
= Math
.max(mProgress
, mProgressCopyUpperBound
);
218 publishProgress(mProgress
);
221 private boolean copyFile(File src
, File target
) {
224 InputStream
in = null
;
225 OutputStream out
= null
;
228 in = new FileInputStream(src
);
229 out
= new FileOutputStream(target
);
230 byte[] buf
= new byte[1024];
232 while ((len
= in.read(buf
)) > 0) {
233 out
.write(buf
, 0, len
);
235 } catch (IOException ex
) {
238 if (in != null
) try {
240 } catch (IOException e
) {
241 e
.printStackTrace(System
.err
);
243 if (out
!= null
) try {
245 } catch (IOException e
) {
246 e
.printStackTrace(System
.err
);
253 void copyDirs(File src
, File dst
) throws MigrationException
{
255 throw new MigrationException(R
.string
.file_migration_failed_while_coping
);
257 for (File f
: src
.listFiles()) {
259 mProgress
= Math
.min(mProgress
+1, mProgressCopyUpperBound
);
260 publishProgress(mProgress
);
263 copyDirs(f
, new File(dst
, f
.getName()));
264 else if (!copyFile(f
, new File(dst
, f
.getName())))
265 throw new MigrationException(R
.string
.file_migration_failed_while_coping
);
270 void updateIndex(Context context
) throws MigrationException
{
271 FileDataStorageManager manager
= new FileDataStorageManager(null
, context
.getContentResolver());
274 manager
.migrateStoredFiles(mStorageSource
, mStorageTarget
);
275 } catch (Exception e
) {
276 throw new MigrationException(R
.string
.file_migration_failed_while_updating_index
);
280 void cleanup() throws MigrationCleanupException
{
281 File srcFile
= new File(mStorageSource
+ File
.separator
+ MainApp
.getDataFolder());
282 if (!srcFile
.delete())
283 throw new MigrationCleanupException();
287 File dstFile
= new File(mStorageTarget
+ File
.separator
+ MainApp
.getDataFolder());
288 if (dstFile
.exists())
289 if (!dstFile
.delete())
290 Log_OC
.w(TAG
, "Rollback step failed");
293 long calculateUsedSpace(File dir
) {
296 for (File f
: dir
.listFiles()) {
298 result
+= calculateUsedSpace(f
);
300 result
+= f
.length();
306 void saveAccountsSyncStatus(String authority
, Account accounts
[], boolean syncs
[]) {
307 for (int i
= 0; i
< accounts
.length
; ++i
)
308 syncs
[i
] = ContentResolver
.getSyncAutomatically(accounts
[i
], authority
);
311 void stopAccountsSyncing(String authority
, Account accounts
[]) {
312 for (int i
= 0; i
< accounts
.length
; ++i
)
313 ContentResolver
.setSyncAutomatically(accounts
[i
], authority
, false
);
316 void waitForUnfinishedSynchronizations(String authority
, Account accounts
[]) {
317 for (int i
= 0; i
< accounts
.length
; ++i
)
318 while (ContentResolver
.isSyncActive(accounts
[i
], authority
))
321 } catch (InterruptedException e
) {
322 Log_OC
.w(TAG
, "Thread interrupted while waiting for account to end syncing");
323 Thread
.currentThread().interrupt();
327 void restoreAccountsSyncStatus(String authority
, Account accounts
[], boolean oldSync
[]) {
328 for (int i
= 0; i
< accounts
.length
; ++i
)
329 ContentResolver
.setSyncAutomatically(accounts
[i
], authority
, oldSync
[i
]);