Merge remote-tracking branch 'remotes/upstream/externalSD2' into beta
authortobiasKaminsky <tobias@kaminsky.me>
Fri, 20 Nov 2015 17:32:55 +0000 (18:32 +0100)
committertobiasKaminsky <tobias@kaminsky.me>
Fri, 20 Nov 2015 17:32:55 +0000 (18:32 +0100)
14 files changed:
AndroidManifest.xml
owncloud-android-library
res/layout/migration_layout.xml [new file with mode: 0644]
res/values-hu-rHU/strings.xml
res/values/strings.xml
res/xml/preferences.xml
src/com/owncloud/android/MainApp.java
src/com/owncloud/android/datamodel/FileDataStorageManager.java
src/com/owncloud/android/ui/activity/LocalDirectorySelectorActivity.java [new file with mode: 0644]
src/com/owncloud/android/ui/activity/Preferences.java
src/com/owncloud/android/ui/activity/StorageMigrationActivity.java [new file with mode: 0644]
src/com/owncloud/android/ui/activity/UploadFilesActivity.java
src/com/owncloud/android/utils/FileStorageUtils.java
tests/project.properties

index ad5dcfd..12bd3f1 100644 (file)
@@ -58,6 +58,8 @@
             </intent-filter>
         </activity>
         <activity android:name=".ui.activity.UploadFilesActivity" />
+        <activity android:name=".ui.activity.LocalDirectorySelectorActivity" />
+        <activity android:name=".ui.activity.StorageMigrationActivity" />
         <activity android:name=".ui.activity.Uploader"
             android:label="@string/uploader_top_message"
             android:theme="@style/Theme.ownCloud">
index 2e0f2a7..4f9a752 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 2e0f2a79224383145d61cc15ca42c6bcc59902d5
+Subproject commit 4f9a7528cab0563cb234a8b817e8ee371dd6cc25
diff --git a/res/layout/migration_layout.xml b/res/layout/migration_layout.xml
new file mode 100644 (file)
index 0000000..84b5c22
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:gravity="center_vertical">
+
+    <ProgressBar
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/migrationProgress"
+        android:layout_gravity="center_horizontal"
+        android:progress="50"
+        android:paddingLeft="30dp"
+        android:paddingRight="30dp"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text=""
+        android:id="@+id/migrationText"
+        android:layout_gravity="center_horizontal"/>
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/drawer_close"
+        android:id="@+id/finishButton"
+        android:layout_gravity="center_horizontal"/>
+</LinearLayout>
\ No newline at end of file
index aea6f62..25967de 100644 (file)
   <string name="sync_file_nothing_to_do_msg">Az állományok már szinkonizálva vannak</string>
   <string name="create_dir_fail_msg">A könyvtárt nem lehet létrehozni</string>
   <string name="filename_forbidden_characters">Nem megendedett karakterek: / \\ &lt; &gt; : \" | ? *</string>
+  <string name="filename_forbidden_charaters_from_server">A fájlnév legalább egy érvénytelen karaktert tartalmaz!</string>
   <string name="filename_empty">A fájl név nem lehet üres</string>
   <string name="wait_a_moment">Egy pillanat...</string>
   <string name="filedisplay_unexpected_bad_get_content">Váratlan hiba; válassza ki a fájlt más programból</string>
index 4e8f180..3750f61 100644 (file)
     <string name="uploader_upload_forbidden_permissions">to upload in this folder</string>
     <string name="downloader_download_file_not_found">The file is no longer available on the server</string>
 
+    <string name="file_migration_finish_button">Finish</string>
+    <string name="file_migration_preparing">Preparing for migration...</string>
+    <string name="file_migration_checking_destination">Checking destination...</string>
+    <string name="file_migration_saving_accounts_configuration">Saving accounts configuration...</string>
+    <string name="file_migration_waiting_for_unfinished_sync">Waiting for unfinished synchronizations...</string>
+    <string name="file_migration_migrating">Moving data...</string>
+    <string name="file_migration_updating_index">Updating index...</string>
+    <string name="file_migration_cleaning">Cleaning...</string>
+    <string name="file_migration_restoring_accounts_configuration">Restoring accounts configuration...</string>
+    <string name="file_migration_ok_finished">Finished</string>
+    <string name="file_migration_failed_not_enough_space">ERROR: Not enough space</string>
+    <string name="file_migration_failed_not_writable">ERROR: File is not writable</string>
+    <string name="file_migration_failed_not_readable">ERROR: File is not readable</string>
+    <string name="file_migration_failed_dir_already_exists">ERROR: owncloud directory already exists</string>
+    <string name="file_migration_failed_while_coping">ERROR: While migrating</string>
+    <string name="file_migration_failed_while_updating_index">ERROR: While updating index</string>
+
     <string name="prefs_category_accounts">Accounts</string>
     <string name="prefs_add_account">Add account</string>
     <string name="auth_redirect_non_secure_connection_title">Secure connection is redirected through an unsecured route.</string>
     <string name="prefs_instant_behaviour_title">Behaviour</string>
     <string name="upload_copy_files">Copy file</string>
     <string name="upload_move_files">Move file</string>
+    <string name="prefs_storage_path">Storage path</string>
+    <string name="prefs_common">Common</string>
 
     <string name="pref_behaviour_entries_do_nothing">do nothing</string>
     <string name="pref_behaviour_entries_copy">copy file to OC folder</string>
index b4f46f9..3972e9e 100644 (file)
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -->
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
-    <PreferenceCategory
-               android:title="@string/prefs_category_accounts"
-               android:key="accounts_category">
+       <PreferenceCategory android:title="@string/prefs_category_general">
+               <com.owncloud.android.ui.PreferenceWithLongSummary
+                       android:title="@string/prefs_storage_path"
+                       android:key="storage_path" />
+       </PreferenceCategory>
+    <PreferenceCategory android:title="@string/prefs_category_accounts" android:key="accounts_category">
     </PreferenceCategory>
 
        <PreferenceCategory android:title="@string/prefs_category_security">
index 6bf385c..401f607 100644 (file)
@@ -24,10 +24,13 @@ import android.app.Activity;
 import android.app.Application;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Environment;
+import android.preference.PreferenceManager;
 
 import com.owncloud.android.authentication.PassCodeManager;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
@@ -56,6 +59,8 @@ public class MainApp extends Application {
 
     private static Context mContext;
 
+    private static String storagePath;
+
     private static boolean mOnlyOnDevice = false;
 
     
@@ -66,6 +71,12 @@ public class MainApp extends Application {
         // Setup handler for uncaught exceptions.
         Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
         
+
+        SharedPreferences appPrefs =
+                PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+        MainApp.storagePath = appPrefs.getString("storage_path", Environment.
+                              getExternalStorageDirectory().getAbsolutePath());
+
         boolean isSamlAuth = AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso));
 
         OwnCloudClientManagerFactory.setUserAgent(getUserAgent());
@@ -85,7 +96,7 @@ public class MainApp extends Application {
             // Set folder for store logs
             Log_OC.setLogDataFolder(dataFolder);
 
-            Log_OC.startLogging();
+            Log_OC.startLogging(MainApp.storagePath);
             Log_OC.d("Debug", "start logging");
         }
 
@@ -138,6 +149,14 @@ public class MainApp extends Application {
         return MainApp.mContext;
     }
 
+    public static String getStoragePath(){
+        return MainApp.storagePath;
+    }
+
+    public static void setStoragePath(String path){
+        MainApp.storagePath = path;
+    }
+
     // Methods to obtain Strings referring app_name 
     //   From AccountAuthenticator 
     //   public static final String ACCOUNT_TYPE = "owncloud";    
index 1ba669e..cef10bc 100644 (file)
@@ -47,19 +47,12 @@ import android.provider.MediaStore;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.files.FileUtils;
 import com.owncloud.android.lib.resources.shares.OCShare;
 import com.owncloud.android.lib.resources.shares.ShareType;
 import com.owncloud.android.lib.resources.status.CapabilityBooleanType;
 import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.utils.FileStorageUtils;
 
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
 public class FileDataStorageManager {
 
     public static final int ROOT_PARENT_ID = 0;
@@ -711,42 +704,74 @@ public class FileDataStorageManager {
                 if (!targetFolder.exists()) {
                     targetFolder.mkdirs();
                 }
-                copied = copyFile(localFile, targetFile);
+                copied = FileStorageUtils.copyFile(localFile, targetFile);
             }
             Log_OC.d(TAG, "Local file COPIED : " + copied);
         }
     }
 
-    private boolean copyFile(File src, File target) {
-        boolean ret = true;
+    public void migrateStoredFiles(String srcPath, String dstPath) throws Exception {
+        Cursor c = null;
+        if (getContentResolver() != null) {
+            c = getContentResolver().query(ProviderTableMeta.CONTENT_URI_FILE,
+                    null,
+                    ProviderTableMeta.FILE_STORAGE_PATH  + " IS NOT NULL",
+                    null,
+                    null);
+
+        } else {
+            try {
+                c = getContentProviderClient().query(ProviderTableMeta.CONTENT_URI_FILE,
+                        new String[]{ProviderTableMeta._ID, ProviderTableMeta.FILE_STORAGE_PATH},
+                        ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL",
+                        null,
+                        null);
+            } catch (RemoteException e) {
+                Log_OC.e(TAG, e.getMessage());
+                throw e;
+            }
+        }
+
+        ArrayList<ContentProviderOperation> operations =
+                new ArrayList<ContentProviderOperation>(c.getCount());
+        if (c.moveToFirst()) {
+            do {
+                ContentValues cv = new ContentValues();
+                long fileId = c.getLong(c.getColumnIndex(ProviderTableMeta._ID));
+                String oldFileStoragePath = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH));
 
-        InputStream in = null;
-        OutputStream out = null;
+                if (oldFileStoragePath.startsWith(srcPath)) {
+
+                    cv.put(
+                            ProviderTableMeta.FILE_STORAGE_PATH,
+                            oldFileStoragePath.replaceFirst(srcPath, dstPath));
 
+                    operations.add(
+                            ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
+                                    withValues(cv).
+                                    withSelection(
+                                            ProviderTableMeta._ID + "=?",
+                                            new String[]{String.valueOf(fileId)}
+                                    )
+                                    .build());
+                }
+
+            } while (c.moveToNext());
+        }
+        c.close();
+
+        /// 3. apply updates in batch
         try {
-            in = new FileInputStream(src);
-            out = new FileOutputStream(target);
-            byte[] buf = new byte[1024];
-            int len;
-            while ((len = in.read(buf)) > 0) {
-                out.write(buf, 0, len);
-            }
-        } catch (IOException ex) {
-            ret = false;
-        } finally {
-            if (in != null) try {
-                in.close();
-            } catch (IOException e) {
-                e.printStackTrace(System.err);
-            }
-            if (out != null) try {
-                out.close();
-            } catch (IOException e) {
-                e.printStackTrace(System.err);
+            if (getContentResolver() != null) {
+                getContentResolver().applyBatch(MainApp.getAuthority(), operations);
+
+            } else {
+                getContentProviderClient().applyBatch(operations);
             }
-        }
 
-        return ret;
+        } catch (Exception e) {
+            throw e;
+        }
     }
 
     
diff --git a/src/com/owncloud/android/ui/activity/LocalDirectorySelectorActivity.java b/src/com/owncloud/android/ui/activity/LocalDirectorySelectorActivity.java
new file mode 100644 (file)
index 0000000..83b44c5
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author Bartosz Przybylski
+ *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2015 Bartosz Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.activity;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+import com.owncloud.android.R;
+
+/**
+ * Created by Bartosz Przybylski on 07.11.2015.
+ */
+public class LocalDirectorySelectorActivity extends UploadFilesActivity {
+
+       @Override
+       public void onCreate(Bundle savedInstanceState) {
+               super.onCreate(savedInstanceState);
+               mUploadBtn.setText(R.string.folder_picker_choose_button_text);
+       }
+
+       @Override
+       public void onClick(View v) {
+               if (v.getId() == R.id.upload_files_btn_cancel) {
+                       setResult(RESULT_CANCELED);
+                       finish();
+
+               } else if (v.getId() == R.id.upload_files_btn_upload) {
+                       Intent resultIntent = new Intent();
+                       resultIntent.putExtra(EXTRA_CHOSEN_FILES, getInitialDirectory().getAbsolutePath());
+                       setResult(RESULT_OK, resultIntent);
+                       finish();
+               }
+       }
+}
index 3f242c2..6d677cd 100644 (file)
@@ -36,6 +36,7 @@ import android.content.res.Configuration;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
 import android.preference.CheckBoxPreference;
@@ -79,6 +80,7 @@ import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.services.OperationsService;
+import com.owncloud.android.ui.PreferenceWithLongSummary;
 import com.owncloud.android.ui.RadioButtonPreference;
 import com.owncloud.android.utils.DisplayUtils;
 
@@ -88,6 +90,7 @@ import java.io.InputStreamReader;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.concurrent.ExecutionException;
+import java.io.File;
 
 
 /**
@@ -103,6 +106,8 @@ public class Preferences extends PreferenceActivity
 
     private static final int ACTION_SELECT_UPLOAD_PATH = 1;
     private static final int ACTION_SELECT_UPLOAD_VIDEO_PATH = 2;
+    private static final int ACTION_SELECT_STORAGE_PATH = 3;
+    private static final int ACTION_PERFORM_MIGRATION = 4;
 
     private DbHandler mDbHandler;
     private CheckBoxPreference pCode;
@@ -126,6 +131,8 @@ public class Preferences extends PreferenceActivity
     protected FileDownloader.FileDownloaderBinder mDownloaderBinder = null;
     protected FileUploader.FileUploaderBinder mUploaderBinder = null;
     private ServiceConnection mDownloadServiceConnection, mUploadServiceConnection = null;
+    private PreferenceWithLongSummary mPrefStoragePath;
+    private String mStoragePath;
 
     @SuppressWarnings("deprecation")
     @Override
@@ -271,7 +278,7 @@ public class Preferences extends PreferenceActivity
         PreferenceCategory preferenceCategory = (PreferenceCategory) findPreference("more");
         
         boolean helpEnabled = getResources().getBoolean(R.bool.help_enabled);
-        Preference pHelp =  findPreference("help");
+        Preference pHelp = findPreference("help");
         if (pHelp != null ){
             if (helpEnabled) {
                 pHelp.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@@ -293,7 +300,7 @@ public class Preferences extends PreferenceActivity
         }
 
         if (BuildConfig.DEBUG) {
-            Preference pLog =  findPreference("log");
+            Preference pLog = findPreference("log");
             if (pLog != null ){
                 pLog.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                     @Override
@@ -336,7 +343,7 @@ public class Preferences extends PreferenceActivity
                         intent.putExtra(Intent.EXTRA_TEXT, recommendText);
                         startActivity(intent);
 
-                        return(true);
+                        return true;
 
                     }
                 });
@@ -353,9 +360,10 @@ public class Preferences extends PreferenceActivity
                 pFeedback.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                     @Override
                     public boolean onPreferenceClick(Preference preference) {
-                        String feedbackMail   =(String) getText(R.string.mail_feedback);
-                        String feedback   =(String) getText(R.string.prefs_feedback) + " - android v" + appVersion;
-                        Intent intent = new Intent(Intent.ACTION_SENDTO); 
+                        String feedbackMail = (String) getText(R.string.mail_feedback);
+                        String feedback     = String.format("%s - android v%s", getText(R.string.prefs_feedback),  appVersion);
+                        Intent intent       = new Intent(Intent.ACTION_SENDTO);
+
                         intent.setType("text/plain");
                         intent.putExtra(Intent.EXTRA_SUBJECT, feedback);
                         
@@ -394,7 +402,29 @@ public class Preferences extends PreferenceActivity
             }
         }
 
-        mPrefInstantUploadPath =  findPreference("instant_upload_path");
+        mPrefStoragePath =  (PreferenceWithLongSummary)findPreference("storage_path");
+        if (mPrefStoragePath != null) {
+
+            mPrefStoragePath.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    Intent intent = new Intent(Preferences.this, LocalDirectorySelectorActivity.class);
+                    intent.putExtra(UploadFilesActivity.KEY_DIRECTORY_PATH, mStoragePath);
+                    startActivityForResult(intent, ACTION_SELECT_STORAGE_PATH);
+                    return true;
+                }
+            });
+
+            mPrefStoragePath.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+                    @Override
+                    public boolean onPreferenceChange(Preference preference, Object newValue) {
+                        MainApp.setStoragePath((String) newValue);
+                        return true;
+                    }
+                });
+        }
+
+        mPrefInstantUploadPath = (PreferenceWithLongSummary)findPreference("instant_upload_path");
         if (mPrefInstantUploadPath != null){
 
             mPrefInstantUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@@ -414,7 +444,7 @@ public class Preferences extends PreferenceActivity
         mPrefInstantUploadCategory =
                 (PreferenceCategory) findPreference("instant_uploading_category");
         
-        mPrefInstantUploadPathWiFi =  findPreference("instant_upload_on_wifi");
+        mPrefInstantUploadPathWiFi = findPreference("instant_upload_on_wifi");
         mPrefInstantUpload = findPreference("instant_uploading");
         
         toggleInstantPictureOptions(((CheckBoxPreference) mPrefInstantUpload).isChecked());
@@ -460,7 +490,7 @@ public class Preferences extends PreferenceActivity
         });
             
         /* About App */
-       pAboutApp = (Preference) findPreference("about_app");
+       pAboutApp = findPreference("about_app");
        if (pAboutApp != null) { 
                pAboutApp.setTitle(String.format(getString(R.string.about_android),
                                                 getString(R.string.app_name)));
@@ -474,6 +504,7 @@ public class Preferences extends PreferenceActivity
        }
 
        loadInstantUploadPath();
+       loadStoragePath();
        loadInstantUploadVideoPath();
 
         /* ComponentsGetter */
@@ -651,8 +682,7 @@ public class Preferences extends PreferenceActivity
 
         if (requestCode == ACTION_SELECT_UPLOAD_PATH && resultCode == RESULT_OK){
 
-            OCFile folderToUpload =
-                    (OCFile) data.getParcelableExtra(UploadPathActivity.EXTRA_FOLDER);
+            OCFile folderToUpload =  data.getParcelableExtra(UploadPathActivity.EXTRA_FOLDER);
 
             mUploadPath = folderToUpload.getRemotePath();
 
@@ -663,10 +693,9 @@ public class Preferences extends PreferenceActivity
 
             saveInstantUploadPathOnPreferences();
 
-        } else if (requestCode == ACTION_SELECT_UPLOAD_VIDEO_PATH && resultCode == RESULT_OK){
+        } else if (requestCode == ACTION_SELECT_UPLOAD_VIDEO_PATH && resultCode == RESULT_OK) {
 
-            OCFile folderToUploadVideo =
-                    (OCFile) data.getParcelableExtra(UploadPathActivity.EXTRA_FOLDER);
+            OCFile folderToUploadVideo = data.getParcelableExtra(UploadPathActivity.EXTRA_FOLDER);
 
             mUploadVideoPath = folderToUploadVideo.getRemotePath();
 
@@ -676,6 +705,21 @@ public class Preferences extends PreferenceActivity
             mPrefInstantVideoUploadPath.setSummary(mUploadVideoPath);
 
             saveInstantUploadVideoPathOnPreferences();
+        } else if (requestCode == ACTION_SELECT_STORAGE_PATH && resultCode == RESULT_OK) {
+            File currentStorageDir = new File(mStoragePath);
+            File upcomingStorageDir = new File(data.getStringExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES));
+
+            if (currentStorageDir != upcomingStorageDir) {
+                Intent migrationIntent = new Intent(this, StorageMigrationActivity.class);
+                migrationIntent.putExtra(StorageMigrationActivity.KEY_MIGRATION_SOURCE_DIR,
+                        currentStorageDir.getAbsolutePath());
+                migrationIntent.putExtra(StorageMigrationActivity.KEY_MIGRATION_TARGET_DIR,
+                        upcomingStorageDir.getAbsolutePath());
+                startActivityForResult(migrationIntent, ACTION_PERFORM_MIGRATION);
+            }
+        } else if (requestCode == ACTION_PERFORM_MIGRATION && resultCode == RESULT_OK) {
+            String resultStorageDir = data.getStringExtra(StorageMigrationActivity.KEY_MIGRATION_TARGET_DIR);
+            saveStoragePath(resultStorageDir);
         }
     }
 
@@ -886,6 +930,31 @@ public class Preferences extends PreferenceActivity
     }
 
     /**
+     * Save storage path
+     */
+    private void saveStoragePath(String newStoragePath) {
+        SharedPreferences appPrefs =
+                PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+        mStoragePath = newStoragePath;
+        MainApp.setStoragePath(mStoragePath);
+        SharedPreferences.Editor editor = appPrefs.edit();
+        editor.putString("storage_path", mStoragePath);
+        editor.commit();
+        mPrefStoragePath.setSummary(mStoragePath);
+    }
+
+    /**
+     * Load storage path set on preferences
+     */
+    private void loadStoragePath() {
+        SharedPreferences appPrefs =
+                PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+        mStoragePath = appPrefs.getString("storage_path", Environment.getExternalStorageDirectory()
+                                                         .getAbsolutePath());
+        mPrefStoragePath.setSummary(mStoragePath);
+    }
+
+    /**
      * Save the "Instant Upload Path" on preferences
      */
     private void saveInstantUploadPathOnPreferences() {
@@ -900,10 +969,7 @@ public class Preferences extends PreferenceActivity
      * Load upload video path set on preferences
      */
     private void loadInstantUploadVideoPath() {
-        SharedPreferences appPrefs =
-                PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
-        mUploadVideoPath = appPrefs.getString("instant_video_upload_path", getString(R.string.instant_upload_path));
-        mPrefInstantVideoUploadPath.setSummary(mUploadVideoPath);
+        mPrefInstantVideoUploadPath.setSummary(MainApp.getStoragePath());
     }
 
     /**
@@ -917,7 +983,7 @@ public class Preferences extends PreferenceActivity
         editor.commit();
     }
 
-    // Methods for ComponetsGetter
+    // Methods for ComponentsGetter
     @Override
     public FileDownloader.FileDownloaderBinder getFileDownloaderBinder() {
         return mDownloaderBinder;
@@ -956,14 +1022,10 @@ public class Preferences extends PreferenceActivity
 
             if (component.equals(new ComponentName(Preferences.this, FileDownloader.class))) {
                 mDownloaderBinder = (FileDownloader.FileDownloaderBinder) service;
-
             } else if (component.equals(new ComponentName(Preferences.this, FileUploader.class))) {
                 Log_OC.d(TAG, "Upload service connected");
                 mUploaderBinder = (FileUploader.FileUploaderBinder) service;
-            } else {
-                return;
             }
-
         }
 
         @Override
diff --git a/src/com/owncloud/android/ui/activity/StorageMigrationActivity.java b/src/com/owncloud/android/ui/activity/StorageMigrationActivity.java
new file mode 100644 (file)
index 0000000..c0b4522
--- /dev/null
@@ -0,0 +1,281 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author Bartosz Przybylski
+ *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2015 Bartosz Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.activity;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.owncloud.android.MainApp;
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.utils.FileStorageUtils;
+
+import java.io.File;
+
+/**
+ * Created by Bartosz Przybylski on 07.11.2015.
+ */
+public class StorageMigrationActivity extends AppCompatActivity {
+       private static final String TAG = StorageMigrationActivity.class.getName();
+       public static final String KEY_MIGRATION_TARGET_DIR = "MIGRATION_TARGET";
+       public static final String KEY_MIGRATION_SOURCE_DIR = "MIGRATION_SOURCE";
+
+       private ProgressBar mProgressBar;
+       private Button mFinishButton;
+       private TextView mFeedbackText;
+
+       @Override
+       public void onCreate(Bundle savedInstanceState) {
+               super.onCreate(savedInstanceState);
+
+               setContentView(R.layout.migration_layout);
+               mProgressBar = (ProgressBar)findViewById(R.id.migrationProgress);
+               mFinishButton = (Button)findViewById(R.id.finishButton);
+               mFeedbackText = (TextView)findViewById(R.id.migrationText);
+
+               mProgressBar.setProgress(0);
+               mFinishButton.setVisibility(View.INVISIBLE);
+               mFeedbackText.setText(R.string.file_migration_preparing);
+
+               mFinishButton.setOnClickListener(new View.OnClickListener() {
+                       @Override
+                       public void onClick(View view) {
+                               setResult(RESULT_CANCELED);
+                               finish();
+                       }
+               });
+
+               String source = getIntent().getStringExtra(KEY_MIGRATION_SOURCE_DIR);
+               String destination = getIntent().getStringExtra(KEY_MIGRATION_TARGET_DIR);
+
+               if (source == null || destination == null) {
+                       Log_OC.e(TAG, "source or destination is null");
+                       finish();
+               }
+
+               new FileMigrationTask().execute(source, destination);
+       }
+
+       private class FileMigrationTask extends AsyncTask<String, Integer, Integer> {
+
+               private String mStorageTarget;
+               private String mStorageSource;
+               private int mProgress;
+
+               private static final int mProgressCopyUpperBound = 98;
+
+               private class MigrationException extends Exception {
+                       private int mResId;
+                       /*
+                        * @param resId resource identifier to use for displaying error
+                        */
+                       MigrationException(int resId) {
+                               super();
+                               this.mResId = resId;
+                       }
+
+                       int getResId() { return mResId; }
+               }
+
+               @Override
+               protected Integer doInBackground(String... args) {
+
+                       mStorageSource = args[0];
+                       mStorageTarget = args[1];
+                       mProgress = 0;
+
+                       publishProgress(mProgress++, R.string.file_migration_preparing);
+
+                       Context context = StorageMigrationActivity.this;
+                       String ocAuthority = context.getString(R.string.authority);
+
+                       Account[] ocAccounts = AccountManager.get(context).getAccountsByType(MainApp.getAccountType());
+                       boolean[] oldAutoSync = new boolean[ocAccounts.length];
+
+                       Log_OC.stopLogging();
+
+                       try {
+                               publishProgress(mProgress++, R.string.file_migration_checking_destination);
+
+                               checkDestinationAvailability();
+
+                               publishProgress(mProgress++, R.string.file_migration_saving_accounts_configuration);
+                               saveAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
+
+                               publishProgress(mProgress++, R.string.file_migration_waiting_for_unfinished_sync);
+                               stopAccountsSyncing(ocAuthority, ocAccounts);
+                               waitForUnfinishedSynchronizations(ocAuthority, ocAccounts);
+
+                               publishProgress(mProgress++, R.string.file_migration_migrating);
+                               copyFiles();
+
+                               publishProgress(mProgress++, R.string.file_migration_updating_index);
+                               updateIndex(context);
+
+                               publishProgress(mProgress++, R.string.file_migration_cleaning);
+                               cleanup();
+
+                       } catch (MigrationException e) {
+                               rollback();
+                               Log_OC.startLogging(mStorageSource);
+                               return e.getResId();
+                       } finally {
+                               publishProgress(mProgress++, R.string.file_migration_restoring_accounts_configuration);
+                               restoreAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
+                       }
+
+                       Log_OC.startLogging(mStorageTarget);
+                       publishProgress(mProgress++, R.string.file_migration_ok_finished);
+
+                       return 0;
+               }
+
+               @Override
+               protected void onProgressUpdate(Integer... progress) {
+                       mProgressBar.setProgress(progress[0]);
+                       if (progress.length > 1)
+                               mFeedbackText.setText(progress[1]);
+               }
+
+               @Override
+               protected void onPostExecute(Integer code) {
+                       mFinishButton.setVisibility(View.VISIBLE);
+                       if (code != 0) {
+                               mFeedbackText.setText(code);
+                       } else {
+                               mFeedbackText.setText(R.string.file_migration_ok_finished);
+                               mFinishButton.setOnClickListener(new View.OnClickListener() {
+                                       @Override
+                                       public void onClick(View view) {
+                                               Intent resultIntent = new Intent();
+                                               resultIntent.putExtra(KEY_MIGRATION_TARGET_DIR, mStorageTarget);
+                                               setResult(RESULT_OK, resultIntent);
+                                               finish();
+                                       }
+                               });
+                       }
+               }
+
+               void checkDestinationAvailability() throws MigrationException {
+                       File srcFile = new File(mStorageSource);
+                       File dstFile = new File(mStorageTarget);
+
+                       if (!dstFile.canRead() || !srcFile.canRead())
+                               throw new MigrationException(R.string.file_migration_failed_not_readable);
+
+                       if (!dstFile.canWrite() || !srcFile.canWrite())
+                               throw new MigrationException(R.string.file_migration_failed_not_writable);
+
+                       if (new File(dstFile, MainApp.getDataFolder()).exists())
+                               throw new MigrationException(R.string.file_migration_failed_dir_already_exists);
+
+                       if (dstFile.getFreeSpace() < FileStorageUtils.getFolderSize(new File(srcFile, MainApp.getDataFolder())))
+                               throw new MigrationException(R.string.file_migration_failed_not_enough_space);
+               }
+
+               void copyFiles() throws MigrationException {
+                       File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
+                       File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
+
+                       copyDirs(srcFile, dstFile);
+                       mProgress = Math.max(mProgress, mProgressCopyUpperBound);
+                       publishProgress(mProgress);
+               }
+
+               void copyDirs(File src, File dst) throws MigrationException {
+                       if (!dst.mkdirs())
+                               throw new MigrationException(R.string.file_migration_failed_while_coping);
+
+                       for (File f : src.listFiles()) {
+
+                               mProgress = Math.min(mProgress+1, mProgressCopyUpperBound);
+                               publishProgress(mProgress);
+
+                               if (f.isDirectory())
+                                       copyDirs(f, new File(dst, f.getName()));
+                               else if (!FileStorageUtils.copyFile(f, new File(dst, f.getName())))
+                                       throw new MigrationException(R.string.file_migration_failed_while_coping);
+                       }
+
+               }
+
+               void updateIndex(Context context) throws MigrationException {
+                       FileDataStorageManager manager = new FileDataStorageManager(null, context.getContentResolver());
+
+                       try {
+                               manager.migrateStoredFiles(mStorageSource, mStorageTarget);
+                       } catch (Exception e) {
+                               throw new MigrationException(R.string.file_migration_failed_while_updating_index);
+                       }
+               }
+
+               void cleanup() {
+                       File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
+                       if (!srcFile.delete())
+                               Log_OC.w(TAG, "Migration cleanup step failed");
+               }
+
+               void rollback() {
+                       File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
+                       if (dstFile.exists())
+                               if (!dstFile.delete())
+                                       Log_OC.w(TAG, "Rollback step failed");
+               }
+
+               void saveAccountsSyncStatus(String authority, Account accounts[], boolean syncs[]) {
+                       for (int i = 0; i < accounts.length; ++i)
+                               syncs[i] = ContentResolver.getSyncAutomatically(accounts[i], authority);
+               }
+
+               void stopAccountsSyncing(String authority, Account accounts[]) {
+                       for (int i = 0; i < accounts.length; ++i)
+                               ContentResolver.setSyncAutomatically(accounts[i], authority, false);
+               }
+
+               void waitForUnfinishedSynchronizations(String authority, Account accounts[]) {
+                       for (int i = 0; i < accounts.length; ++i)
+                               while (ContentResolver.isSyncActive(accounts[i], authority))
+                                       try {
+                                               Thread.sleep(1000);
+                                       } catch (InterruptedException e) {
+                                               Log_OC.w(TAG, "Thread interrupted while waiting for account to end syncing");
+                                               Thread.currentThread().interrupt();
+                                       }
+               }
+
+               void restoreAccountsSyncStatus(String authority, Account accounts[], boolean oldSync[]) {
+                       for (int i = 0; i < accounts.length; ++i)
+                               ContentResolver.setSyncAutomatically(accounts[i], authority, oldSync[i]);
+               }
+
+       }
+}
index f93af1f..d6665e2 100644 (file)
@@ -65,18 +65,18 @@ public class UploadFilesActivity extends FileActivity implements
     
     private ArrayAdapter<String> mDirectories;
     private File mCurrentDir = null;
-    private LocalFileListFragment mFileListFragment;
-    private Button mCancelBtn;
-    private Button mUploadBtn;
-    private Account mAccountOnCreation;
-    private DialogFragment mCurrentDialog;
+    protected LocalFileListFragment mFileListFragment;
+    protected Button mCancelBtn;
+    protected Button mUploadBtn;
+    protected Account mAccountOnCreation;
+    protected DialogFragment mCurrentDialog;
     
     public static final String EXTRA_CHOSEN_FILES =
             UploadFilesActivity.class.getCanonicalName() + ".EXTRA_CHOSEN_FILES";
 
     public static final int RESULT_OK_AND_MOVE = RESULT_FIRST_USER; 
     
-    private static final String KEY_DIRECTORY_PATH =
+    public static final String KEY_DIRECTORY_PATH =
             UploadFilesActivity.class.getCanonicalName() + ".KEY_DIRECTORY_PATH";
     private static final String TAG = "UploadFilesActivity";
     private static final String WAIT_DIALOG_TAG = "WAIT";
@@ -91,8 +91,9 @@ public class UploadFilesActivity extends FileActivity implements
         super.onCreate(savedInstanceState);
 
         if(savedInstanceState != null) {
-            mCurrentDir = new File(savedInstanceState.getString(
-                    UploadFilesActivity.KEY_DIRECTORY_PATH));
+            mCurrentDir = new File(savedInstanceState.getString(KEY_DIRECTORY_PATH));
+        } else if (getIntent() != null && getIntent().hasExtra(KEY_DIRECTORY_PATH)) {
+            mCurrentDir = new File(getIntent().getStringExtra(KEY_DIRECTORY_PATH));
         } else {
             mCurrentDir = Environment.getExternalStorageDirectory();
         }
index 47a46d2..e300a5e 100644 (file)
@@ -23,6 +23,11 @@ package com.owncloud.android.utils;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -41,7 +46,6 @@ import android.content.Context;
 import android.content.SharedPreferences;
 import android.preference.PreferenceManager;
 import android.net.Uri;
-import android.os.Environment;
 import android.os.StatFs;
 import android.webkit.MimeTypeMap;
 
@@ -58,8 +62,9 @@ public class FileStorageUtils {
 
     
     public static final String getSavePath(String accountName) {
-        File sdCard = Environment.getExternalStorageDirectory();
-        return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/" + Uri.encode(accountName, "@");
+//        File sdCard = Environment.getExternalStorageDirectory();
+
+        return MainApp.getStoragePath() + File.separator + MainApp.getDataFolder() + File.separator + Uri.encode(accountName, "@");
         // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
     }
 
@@ -68,14 +73,13 @@ public class FileStorageUtils {
     }
 
     public static final String getTemporalPath(String accountName) {
-        File sdCard = Environment.getExternalStorageDirectory();
-        return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/tmp/" + Uri.encode(accountName, "@");
+        return MainApp.getStoragePath() + File.separator + MainApp.getDataFolder() + File.separator + "tmp" + File.separator + Uri.encode(accountName, "@");
             // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
     }
 
     @SuppressLint("NewApi")
     public static final long getUsableSpace(String accountName) {
-        File savePath = Environment.getExternalStorageDirectory();
+        File savePath = new File(MainApp.getStoragePath());
         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) {
             return savePath.getUsableSpace();
 
@@ -87,7 +91,7 @@ public class FileStorageUtils {
     }
     
     public static final String getLogPath()  {
-        return Environment.getExternalStorageDirectory() + File.separator + MainApp.getDataFolder() + File.separator + "log";
+        return MainApp.getStoragePath() + File.separator + MainApp.getDataFolder() + File.separator + "log";
     }
 
     public static String getInstantUploadFilePath(Context context, String fileName) {
@@ -365,13 +369,11 @@ public class FileStorageUtils {
     public static long getFolderSize(File dir) {
         if (dir.exists()) {
             long result = 0;
-            File[] fileList = dir.listFiles();
-            for(int i = 0; i < fileList.length; i++) {
-                if(fileList[i].isDirectory()) {
-                    result += getFolderSize(fileList[i]);
-                } else {
-                    result += fileList[i].length();
-                }
+            for (File f : dir.listFiles()) {
+                if (f.isDirectory())
+                    result += getFolderSize(f);
+                else
+                    result += f.length();
             }
             return result;
         }
@@ -421,4 +423,36 @@ public class FileStorageUtils {
         }
     }
 
+    public static boolean copyFile(File src, File target) {
+        boolean ret = true;
+
+        InputStream in = null;
+        OutputStream out = null;
+
+        try {
+            in = new FileInputStream(src);
+            out = new FileOutputStream(target);
+            byte[] buf = new byte[1024];
+            int len;
+            while ((len = in.read(buf)) > 0) {
+                out.write(buf, 0, len);
+            }
+        } catch (IOException ex) {
+            ret = false;
+        } finally {
+            if (in != null) try {
+                in.close();
+            } catch (IOException e) {
+                e.printStackTrace(System.err);
+            }
+            if (out != null) try {
+                out.close();
+            } catch (IOException e) {
+                e.printStackTrace(System.err);
+            }
+        }
+
+        return ret;
+    }
+
 }
index 916037e..00cf62b 100644 (file)
@@ -11,4 +11,4 @@
 #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 
 # Project target.
-target=android-23
+target=android-22