Skip to content

Commit 9f8fdae

Browse files
Merge pull request #92 from nextcloud/externalSD2
External SD support
2 parents 226abe2 + b09126c commit 9f8fdae

22 files changed

+1483
-103
lines changed

AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
5757
<uses-permission android:name="android.permission.WAKE_LOCK" />
5858

59+
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
60+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
61+
5962
<application
6063
android:name=".MainApp"
6164
android:icon="@mipmap/ic_launcher"
@@ -79,6 +82,8 @@
7982
android:taskAffinity=""
8083
android:excludeFromRecents="true"
8184
android:theme="@style/Theme.ownCloud.NoActionBar">
85+
<activity android:name=".ui.activity.LocalDirectorySelectorActivity" />
86+
<activity android:name=".ui.activity.StorageMigrationActivity" />
8287
<intent-filter>
8388
<action android:name="android.intent.action.SEND" />
8489

res/layout/migration_layout.xml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Nextcloud Android client application
4+
5+
Copyright (C) 2016 Bartosz Przybylski
6+
Copyright (C) 2016 Nextcloud
7+
8+
This program is free software; you can redistribute it and/or
9+
modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
10+
License as published by the Free Software Foundation; either
11+
version 3 of the License, or any later version.
12+
13+
This program is distributed in the hope that it will be useful,
14+
but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
GNU AFFERO GENERAL PUBLIC LICENSE for more details.
17+
18+
You should have received a copy of the GNU Affero General Public
19+
License along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
-->
21+
22+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
23+
android:layout_width="match_parent"
24+
android:layout_height="match_parent"
25+
android:gravity="center_vertical"
26+
android:orientation="vertical">
27+
28+
<ProgressBar
29+
android:id="@+id/migrationProgress"
30+
style="?android:attr/progressBarStyleHorizontal"
31+
android:layout_width="match_parent"
32+
android:layout_height="wrap_content"
33+
android:layout_gravity="center_horizontal"
34+
android:paddingLeft="30dp"
35+
android:paddingRight="30dp"
36+
android:progress="50"/>
37+
38+
<TextView
39+
android:id="@+id/migrationText"
40+
android:layout_width="wrap_content"
41+
android:layout_height="wrap_content"
42+
android:layout_gravity="center_horizontal"
43+
android:text=""
44+
android:textAppearance="?android:attr/textAppearanceMedium"/>
45+
46+
<Button
47+
android:id="@+id/finishButton"
48+
android:layout_width="wrap_content"
49+
android:layout_height="wrap_content"
50+
android:layout_gravity="center_horizontal"
51+
android:text="@string/drawer_close"/>
52+
</LinearLayout>

res/values/strings.xml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,28 @@
356356
<string name="uploader_upload_forbidden_permissions">to upload in this folder</string>
357357
<string name="downloader_download_file_not_found">The file is no longer available on the server</string>
358358

359+
<string name="file_migration_dialog_title">Updating storage path</string>
360+
<string name="file_migration_finish_button">Finish</string>
361+
<string name="file_migration_preparing">Preparing for migration&#8230;</string>
362+
<string name="file_migration_checking_destination">Checking destination&#8230;</string>
363+
<string name="file_migration_saving_accounts_configuration">Saving accounts configuration&#8230;</string>
364+
<string name="file_migration_waiting_for_unfinished_sync">Waiting for unfinished synchronizations&#8230;</string>
365+
<string name="file_migration_migrating">Moving data&#8230;</string>
366+
<string name="file_migration_updating_index">Updating index&#8230;</string>
367+
<string name="file_migration_cleaning">Cleaning&#8230;</string>
368+
<string name="file_migration_restoring_accounts_configuration">Restoring accounts configuration&#8230;</string>
369+
<string name="file_migration_ok_finished">Finished</string>
370+
<string name="file_migration_failed_not_enough_space">ERROR: Not enough space</string>
371+
<string name="file_migration_failed_not_writable">ERROR: File is not writable</string>
372+
<string name="file_migration_failed_not_readable">ERROR: File is not readable</string>
373+
<string name="file_migration_failed_dir_already_exists">ERROR: Nextcloud directory already exists</string>
374+
<string name="file_migration_failed_while_coping">ERROR: While migrating</string>
375+
<string name="file_migration_failed_while_updating_index">ERROR: While updating index</string>
376+
377+
<string name="file_migration_directory_already_exists">Data folder already exists, what to do?</string>
378+
<string name="file_migration_override_data_folder">Override</string>
379+
<string name="file_migration_use_data_folder">Use existing</string>
380+
359381
<string name="prefs_category_accounts">Accounts</string>
360382
<string name="prefs_add_account">Add account</string>
361383
<string name="drawer_manage_accounts">Manage accounts</string>
@@ -422,6 +444,8 @@
422444
<string name="pref_behaviour_entries_keep_file">kept in original folder</string>
423445
<string name="pref_behaviour_entries_move">moved to app folder</string>
424446
<string name="pref_behaviour_entries_delete_file">deleted</string>
447+
<string name="prefs_storage_path">Storage path</string>
448+
<string name="prefs_common">Common</string>
425449

426450
<string name="share_dialog_title">Sharing</string>
427451
<string name="share_file">Share %1$s</string>
@@ -506,4 +530,8 @@
506530
<item quantity="other">%d selected</item>
507531
</plurals>
508532

533+
<string name="storage_description_default">Default</string>
534+
<string name="storage_description_sd_no">SD card %1$d</string>
535+
<string name="storage_description_unknown">Unknown</string>
536+
509537
</resources>

res/xml/preferences.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
-->
2020
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
21+
<PreferenceCategory android:title="@string/prefs_category_general">
22+
<ListPreference
23+
android:title="@string/prefs_storage_path"
24+
android:key="storage_path" />
25+
</PreferenceCategory>
2126

2227
<PreferenceCategory android:title="@string/prefs_category_instant_uploading" android:key="instant_uploading_category">
2328
<com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:key="instant_uploading"

src/com/owncloud/android/MainApp.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,19 @@
2323
import android.app.Activity;
2424
import android.app.Application;
2525
import android.content.Context;
26+
import android.content.SharedPreferences;
2627
import android.content.pm.PackageInfo;
2728
import android.content.pm.PackageManager;
2829
import android.os.Bundle;
2930
import android.os.Environment;
31+
import android.preference.PreferenceManager;
3032

3133
import com.owncloud.android.authentication.PassCodeManager;
3234
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
3335
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
3436
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory.Policy;
3537
import com.owncloud.android.lib.common.utils.Log_OC;
38+
import com.owncloud.android.ui.activity.Preferences;
3639

3740

3841
/**
@@ -54,13 +57,20 @@ public class MainApp extends Application {
5457

5558
private static Context mContext;
5659

60+
private static String storagePath;
61+
5762
private static boolean mOnlyOnDevice = false;
5863

5964

6065
public void onCreate(){
6166
super.onCreate();
6267
MainApp.mContext = getApplicationContext();
63-
68+
69+
SharedPreferences appPrefs =
70+
PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
71+
MainApp.storagePath = appPrefs.getString(Preferences.Keys.STORAGE_PATH, Environment.
72+
getExternalStorageDirectory().getAbsolutePath());
73+
6474
boolean isSamlAuth = AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso));
6575

6676
OwnCloudClientManagerFactory.setUserAgent(getUserAgent());
@@ -80,8 +90,7 @@ public void onCreate(){
8090
// Set folder for store logs
8191
Log_OC.setLogDataFolder(dataFolder);
8292

83-
//TODO: to be changed/fixed whenever SD card support gets merged.
84-
Log_OC.startLogging(Environment.getExternalStorageDirectory().getAbsolutePath());
93+
Log_OC.startLogging(MainApp.storagePath);
8594
Log_OC.d("Debug", "start logging");
8695
}
8796

@@ -132,6 +141,14 @@ public static Context getAppContext() {
132141
return MainApp.mContext;
133142
}
134143

144+
public static String getStoragePath(){
145+
return MainApp.storagePath;
146+
}
147+
148+
public static void setStoragePath(String path){
149+
MainApp.storagePath = path;
150+
}
151+
135152
// Methods to obtain Strings referring app_name
136153
// From AccountAuthenticator
137154
// public static final String ACCOUNT_TYPE = "owncloud";

src/com/owncloud/android/datamodel/FileDataStorageManager.java

Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,6 @@
4646
import com.owncloud.android.utils.MimeTypeUtil;
4747

4848
import java.io.File;
49-
import java.io.FileInputStream;
50-
import java.io.FileOutputStream;
51-
import java.io.IOException;
52-
import java.io.InputStream;
53-
import java.io.OutputStream;
5449
import java.util.ArrayList;
5550
import java.util.Collection;
5651
import java.util.Collections;
@@ -727,44 +722,69 @@ public void copyLocalFile(OCFile file, String targetPath) {
727722
if (!targetFolder.exists()) {
728723
targetFolder.mkdirs();
729724
}
730-
copied = copyFile(localFile, targetFile);
725+
copied = FileStorageUtils.copyFile(localFile, targetFile);
731726
}
732727
Log_OC.d(TAG, "Local file COPIED : " + copied);
733728
}
734729
}
735730

736-
private boolean copyFile(File src, File target) {
737-
boolean ret = true;
738-
739-
InputStream in = null;
740-
OutputStream out = null;
731+
public void migrateStoredFiles(String srcPath, String dstPath) throws Exception {
732+
Cursor c = null;
733+
if (getContentResolver() != null) {
734+
c = getContentResolver().query(ProviderTableMeta.CONTENT_URI_FILE,
735+
null,
736+
ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL",
737+
null,
738+
null);
741739

742-
try {
743-
in = new FileInputStream(src);
744-
out = new FileOutputStream(target);
745-
byte[] buf = new byte[1024];
746-
int len;
747-
while ((len = in.read(buf)) > 0) {
748-
out.write(buf, 0, len);
749-
}
750-
} catch (IOException ex) {
751-
ret = false;
752-
} finally {
753-
if (in != null) try {
754-
in.close();
755-
} catch (IOException e) {
756-
e.printStackTrace(System.err);
757-
}
758-
if (out != null) try {
759-
out.close();
760-
} catch (IOException e) {
761-
e.printStackTrace(System.err);
740+
} else {
741+
try {
742+
c = getContentProviderClient().query(ProviderTableMeta.CONTENT_URI_FILE,
743+
new String[]{ProviderTableMeta._ID, ProviderTableMeta.FILE_STORAGE_PATH},
744+
ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL",
745+
null,
746+
null);
747+
} catch (RemoteException e) {
748+
Log_OC.e(TAG, e.getMessage());
749+
throw e;
762750
}
763751
}
764752

765-
return ret;
766-
}
753+
ArrayList<ContentProviderOperation> operations =
754+
new ArrayList<ContentProviderOperation>(c.getCount());
755+
if (c.moveToFirst()) {
756+
do {
757+
ContentValues cv = new ContentValues();
758+
long fileId = c.getLong(c.getColumnIndex(ProviderTableMeta._ID));
759+
String oldFileStoragePath = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH));
767760

761+
if (oldFileStoragePath.startsWith(srcPath)) {
762+
763+
cv.put(
764+
ProviderTableMeta.FILE_STORAGE_PATH,
765+
oldFileStoragePath.replaceFirst(srcPath, dstPath));
766+
767+
operations.add(
768+
ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
769+
withValues(cv).
770+
withSelection(
771+
ProviderTableMeta._ID + "=?",
772+
new String[]{String.valueOf(fileId)}
773+
)
774+
.build());
775+
}
776+
777+
} while (c.moveToNext());
778+
}
779+
c.close();
780+
781+
/// 3. apply updates in batch
782+
if (getContentResolver() != null) {
783+
getContentResolver().applyBatch(MainApp.getAuthority(), operations);
784+
} else {
785+
getContentProviderClient().applyBatch(operations);
786+
}
787+
}
768788

769789
private Vector<OCFile> getFolderContent(long parentId, boolean onlyOnDevice) {
770790

0 commit comments

Comments
 (0)