diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index feade6e2..59e00ed7 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -10,25 +10,26 @@ on:
jobs:
build:
-
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- - name: set up JDK 1.8
+ - name: set up JDK
uses: actions/setup-java@v1
with:
java-version: 1.8
- - name: Set up Go 1.14
+ - name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.14
id: go
- # https://github.com/actions/virtual-environments/issues/578
- - name: Fix missing NDK dependency
- run: echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;20.0.5594570"
- - name: Build rclone
- run: ./gradlew buildNative -p rclone
+ - name: Configure Android SDK
+ run: |
+ BUILD_TOOLS_VERSION="$(grep -E "^io\.github\.x0b\.rcx\.buildToolsVersion=" gradle.properties | cut -d'=' -f2)"
+ NDK_VERSION="$(grep -E "^io\.github\.x0b\.rcx\.ndkVersion=" gradle.properties | cut -d'=' -f2)"
+
+ yes | sudo "${ANDROID_HOME}/tools/bin/sdkmanager" --licenses
+ sudo "${ANDROID_HOME}/tools/bin/sdkmanager" "build-tools;${BUILD_TOOLS_VERSION}" "ndk;${NDK_VERSION}"
- name: Build app
run: ./gradlew assembleOssDebug
- name: Upload APK
diff --git a/.gitignore b/.gitignore
index 12538b7c..5a635f59 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,9 @@
*.apk
*.ap_
+# Built libraries
+*.so
+
# Files for the ART/Dalvik VM
*.dex
diff --git a/app/build.gradle b/app/build.gradle
index d412e7e3..84e111c8 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,10 +1,27 @@
apply plugin: 'com.android.application'
-if (!getGradle().getStartParameter().getTaskRequests().toString().toLowerCase().contains("oss")) {
- apply plugin: 'com.google.gms.google-services'
- apply plugin: 'com.google.firebase.crashlytics'
+
+for (String taskName : getGradle().getStartParameter().getTaskNames()) {
+ if (taskName.endsWith('RcxDebug') || taskName.endsWith('RcxRelease')) {
+ apply plugin: 'com.google.gms.google-services'
+ apply plugin: 'com.google.firebase.crashlytics'
+ break
+ }
+}
+
+tasks.whenTaskAdded { task ->
+ // We defer the build of rclone just before the assemble stage of the
+ // main app, so that Android Studio displays a convenient clickable
+ // error message that allows to install the proper NDK version
+ // directly from the SDK manager.
+ if (task.name.startsWith('assemble')) {
+ task.dependsOn(':rclone:buildNative')
+ }
}
android {
+ buildToolsVersion project.properties['io.github.x0b.rcx.buildToolsVersion']
+ ndkVersion project.properties['io.github.x0b.rcx.ndkVersion']
+
signingConfigs {
github_x0b {
keyAlias 'github_x0b'
@@ -18,6 +35,10 @@ android {
versionCode 170 // last digit is reserved for ABI, only ever end on 0!
versionName '1.11.4'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ def documentsAuthority = applicationId + '.documents'
+ manifestPlaceholders = [documentsAuthority: documentsAuthority]
+ buildConfigField "String", "DOCUMENTS_AUTHORITY", "\"${documentsAuthority}\""
}
buildTypes {
@@ -63,10 +84,11 @@ android {
}
project.ext.versionCodes = [
- 'armeabi-v7a': 6,
- 'arm64-v8a': 7,
- 'x86': 8,
- 'x86_64': 9]
+ 'armeabi-v7a': 6,
+ 'arm64-v8a': 7,
+ 'x86': 8,
+ 'x86_64': 9
+ ]
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1fe34ab5..b7cc7720 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -63,6 +63,17 @@
android:resource="@xml/file_provider_paths" />
+
+
+
+
+
+
remote
Set shortcutSet = new HashSet<>();
List shortcutInfoList = new ArrayList<>();
- RemoteItem.prepareDisplay(context, remotes);
for (RemoteItem remoteItem : remotes) {
String id = getUniqueIdFromString(remoteItem.getName());
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Dialogs/RemoteDestinationDialog.java b/app/src/main/java/ca/pkay/rcloneexplorer/Dialogs/RemoteDestinationDialog.java
index 83539fdd..79b156af 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Dialogs/RemoteDestinationDialog.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Dialogs/RemoteDestinationDialog.java
@@ -452,7 +452,7 @@ protected void onPreExecute() {
@Override
protected List doInBackground(Void... voids) {
List fileItemList;
- fileItemList = rclone.getDirectoryContent(remote, directoryObject.getCurrentPath(), startAtRoot);
+ fileItemList = rclone.ls(remote, directoryObject.getCurrentPath(), startAtRoot);
return fileItemList;
}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/FileExplorerFragment.java b/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/FileExplorerFragment.java
index 5e4de02b..a4c948ff 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/FileExplorerFragment.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/FileExplorerFragment.java
@@ -6,7 +6,6 @@
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
@@ -86,7 +85,6 @@
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
-import java.net.URL;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
@@ -1659,7 +1657,7 @@ protected void onPreExecute() {
@Override
protected List doInBackground(Void... voids) {
List fileItemList;
- fileItemList = rclone.getDirectoryContent(remote, directoryObject.getCurrentPath(), startAtRoot);
+ fileItemList = rclone.ls(remote, directoryObject.getCurrentPath(), startAtRoot);
return fileItemList;
}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/RemotesFragment.java b/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/RemotesFragment.java
index 6e35274b..f2cf02d8 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/RemotesFragment.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/RemotesFragment.java
@@ -7,6 +7,7 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.provider.DocumentsContract;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -25,6 +26,7 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import ca.pkay.rcloneexplorer.AppShortcutsHelper;
+import ca.pkay.rcloneexplorer.BuildConfig;
import ca.pkay.rcloneexplorer.Dialogs.RemotePropertiesDialog;
import ca.pkay.rcloneexplorer.Items.RemoteItem;
import ca.pkay.rcloneexplorer.MainActivity;
@@ -33,7 +35,6 @@
import ca.pkay.rcloneexplorer.RecyclerViewAdapters.RemotesRecyclerViewAdapter;
import ca.pkay.rcloneexplorer.RemoteConfig.RemoteConfig;
import com.leinardi.android.speeddial.SpeedDialView;
-import java9.util.stream.StreamSupport;
import jp.wasabeef.recyclerview.animators.LandingAnimator;
import java.util.ArrayList;
@@ -298,12 +299,17 @@ private void refreshRemotes() {
if (null != recyclerViewAdapter) {
recyclerViewAdapter.newData(remotes);
}
+ refreshSAFRoots();
+ }
+
+ private void refreshSAFRoots() {
+ Uri rootsUri = DocumentsContract.buildRootsUri(BuildConfig.DOCUMENTS_AUTHORITY);
+ context.getContentResolver().notifyChange(rootsUri, null);
}
private List filterRemotes() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
Set hiddenRemotes = sharedPreferences.getStringSet(getString(R.string.shared_preferences_hidden_remotes), new HashSet<>());
- Set renamedRemotes = sharedPreferences.getStringSet(getString(R.string.pref_key_renamed_remotes), new HashSet<>());
remotes = rclone.getRemotes();
if (hiddenRemotes != null && !hiddenRemotes.isEmpty()) {
ArrayList toBeHidden = new ArrayList<>();
@@ -315,13 +321,6 @@ private List filterRemotes() {
remotes.removeAll(toBeHidden);
}
Collections.sort(remotes);
- for(RemoteItem item : remotes) {
- if(renamedRemotes.contains(item.getName())) {
- String displayName = sharedPreferences.getString(
- getString(R.string.pref_key_renamed_remote_prefix, item.getName()), item.getName());
- item.setDisplayName(displayName);
- }
- }
return remotes;
}
@@ -470,21 +469,14 @@ private void renameRemote(final RemoteItem remoteItem) {
builder = new AlertDialog.Builder(context);
}
- final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
final EditText remoteNameEdit = new EditText(context);
String initialText = remoteItem.getDisplayName();
remoteNameEdit.setText(initialText);
builder.setView(remoteNameEdit);
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.select, (dialog, which) -> {
- String displayName = remoteNameEdit.getText().toString();
- Set renamedRemotes = pref.getStringSet(getString(R.string.pref_key_renamed_remotes), new HashSet<>());
- renamedRemotes.add(remoteItem.getName());
- pref.edit()
- .putString(getString(R.string.pref_key_renamed_remote_prefix, remoteItem.getName()), displayName)
- .putStringSet(getString(R.string.pref_key_renamed_remotes), renamedRemotes)
- .apply();
- remoteItem.setDisplayName(displayName);
+ String newName = remoteNameEdit.getText().toString();
+ rclone.renameRemote(remoteItem.getName(), newName);
refreshRemotes();
});
builder.setTitle(R.string.rename_remote);
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/ShareFragment.java b/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/ShareFragment.java
index daecd9d5..8bdfbdf3 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/ShareFragment.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/ShareFragment.java
@@ -157,7 +157,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
breadcrumbView = ((FragmentActivity)context).findViewById(R.id.breadcrumb_view);
breadcrumbView.setOnClickListener(this);
breadcrumbView.setVisibility(View.VISIBLE);
- breadcrumbView.addCrumb(remote.getName(), "//" + remote.getName());
+ breadcrumbView.addCrumb(remote.getDisplayName(), "//" + remote.getName());
final TypedValue accentColorValue = new TypedValue ();
context.getTheme().resolveAttribute (R.attr.colorAccent, accentColorValue, true);
@@ -513,7 +513,7 @@ protected void onPreExecute() {
@Override
protected List doInBackground(Void... voids) {
List fileItemList;
- fileItemList = rclone.getDirectoryContent(remote, directoryObject.getCurrentPath(), startAtRoot);
+ fileItemList = rclone.ls(remote, directoryObject.getCurrentPath(), startAtRoot);
return fileItemList;
}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/ShareRemotesFragment.java b/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/ShareRemotesFragment.java
index 52840edf..b058c765 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/ShareRemotesFragment.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Fragments/ShareRemotesFragment.java
@@ -53,7 +53,6 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
((FragmentActivity) context).setTitle(getString(R.string.remotes_toolbar_title));
Rclone rclone = new Rclone(getContext());
remotes = rclone.getRemotes();
- RemoteItem.prepareDisplay(getContext(), remotes);
Collections.sort(remotes);
}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Items/RemoteItem.java b/app/src/main/java/ca/pkay/rcloneexplorer/Items/RemoteItem.java
index f9d6fe75..693d5d63 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Items/RemoteItem.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Items/RemoteItem.java
@@ -73,8 +73,9 @@ public class RemoteItem implements Comparable, Parcelable {
private boolean isDrawerPinned;
private String displayName;
- public RemoteItem(String name, String type) {
+ public RemoteItem(String name, String displayName, String type) {
this.name = name;
+ this.displayName = displayName;
this.typeReadable = type;
this.type = getTypeFromString(type);
}
@@ -169,6 +170,8 @@ public String getName() {
return name;
}
+ public String getDisplayName() { return displayName; }
+
public int getType() {
return type;
}
@@ -249,27 +252,6 @@ public boolean isRemoteType(int ...remotes) {
return isSameType;
}
- public String getDisplayName() {
- return displayName != null ? displayName : name;
- }
-
- public void setDisplayName(String displayName) {
- this.displayName = displayName;
- }
-
- public static List prepareDisplay(Context context, List items) {
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
- Set renamedRemotes = pref.getStringSet(context.getString(R.string.pref_key_renamed_remotes), new HashSet<>());
- for(RemoteItem item : items) {
- if(renamedRemotes.contains(item.name)) {
- String displayName = pref.getString(
- context.getString(R.string.pref_key_renamed_remote_prefix, item.name), item.name);
- item.displayName = displayName;
- }
- }
- return items;
- }
-
private int getTypeFromString(String type) {
switch (type) {
case SafConstants.SAF_REMOTE_NAME:
@@ -407,7 +389,10 @@ public int compareTo(@NonNull RemoteItem remoteItem) {
} else if (!this.isPinned && remoteItem.isPinned) {
return 1;
}
- return getDisplayName().toLowerCase().compareTo(remoteItem.getDisplayName().toLowerCase());
+
+ String self = getDisplayName().toLowerCase();
+ String other = remoteItem.getDisplayName().toLowerCase();
+ return self.compareTo(other);
}
@Override
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/MainActivity.java b/app/src/main/java/ca/pkay/rcloneexplorer/MainActivity.java
index 76a054d8..895b1305 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/MainActivity.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/MainActivity.java
@@ -11,7 +11,6 @@
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
-import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -176,13 +175,13 @@ protected void onCreate(Bundle savedInstanceState) {
AppShortcutsHelper.populateAppShortcuts(this, rclone.getRemotes());
}
- startRemotesFragment();
-
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putInt(getString(R.string.pref_key_version_code), currentVersionCode);
editor.putString(getString(R.string.pref_key_version_name), currentVersionName);
editor.apply();
- } else if (rclone.isConfigEncrypted()) {
+ }
+
+ if (rclone.isConfigEncrypted()) {
askForConfigPassword();
} else if (savedInstanceState != null) {
fragment = getSupportFragmentManager().findFragmentByTag(FILE_EXPLORER_FRAGMENT_TAG);
@@ -366,15 +365,6 @@ private void pinRemotesToDrawer() {
List remoteItems = rclone.getRemotes();
Collections.sort(remoteItems);
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
- Set renamedRemotes = sharedPreferences.getStringSet(getString(R.string.pref_key_renamed_remotes), new HashSet<>());
- for(RemoteItem item : remoteItems) {
- if(renamedRemotes.contains(item.getName())) {
- String displayName = sharedPreferences.getString(
- getString(R.string.pref_key_renamed_remote_prefix, item.getName()), item.getName());
- item.setDisplayName(displayName);
- }
- }
for (RemoteItem remoteItem : remoteItems) {
if (remoteItem.isDrawerPinned()) {
MenuItem menuItem = subMenu.add(R.id.nav_pinned, availableDrawerPinnedRemoteId, Menu.NONE, remoteItem.getDisplayName());
@@ -735,9 +725,6 @@ protected void onPostExecute(Boolean success) {
}
private class RefreshLocalAliases extends AsyncTask {
-
- private String EMULATED = "5d44cd8d-397c-4107-b79b-17f2b6a071e8";
-
private LoadingDialog loadingDialog;
protected boolean isRequired() {
@@ -756,7 +743,7 @@ protected boolean isRequired() {
FLog.d(TAG, "Storage volumes not changed, no refresh required");
return false;
} else {
- FLog.d(TAG, "Storage volumnes changed, refresh required");
+ FLog.d(TAG, "Storage volumes changed, refresh required");
externalVolumes = current;
persisted = TextUtils.join("|", current);
PreferenceManager.getDefaultSharedPreferences(context).edit()
@@ -790,14 +777,10 @@ protected Boolean doInBackground(Void... aVoid) {
}
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
Set generated = pref.getStringSet(getString(R.string.pref_key_local_alias_remotes), new HashSet<>());
- Set renamed = pref.getStringSet(getString(R.string.pref_key_renamed_remotes), new HashSet<>());
SharedPreferences.Editor editor = pref.edit();
for(String remote : generated) {
rclone.deleteRemote(remote);
- renamed.remove(remote);
- editor.remove(getString(R.string.pref_key_renamed_remote_prefix, remote));
}
- editor.putStringSet(getString(R.string.pref_key_renamed_remotes), renamed);
editor.apply();
File[] dirs = context.getExternalFilesDirs(null);
for(File file : dirs) {
@@ -833,13 +816,13 @@ private File getVolumeRoot(File file) {
private void addLocalRemote(File root) throws IOException {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
String name = root.getCanonicalPath();
- String id = Environment.isExternalStorageEmulated(root) ? EMULATED : UUID.randomUUID().toString();
+ String id = UUID.randomUUID().toString();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
StorageVolume storageVolume = storageManager.getStorageVolume(root);
- name = storageVolume.getDescription(context);
- if (null != storageVolume.getUuid()) {
- id = storageVolume.getUuid();
+ String description = storageVolume != null ? storageVolume.getDescription(context) : null;
+ if (description != null) {
+ name = description;
}
}
@@ -849,7 +832,7 @@ private void addLocalRemote(File root) throws IOException {
options.add("alias");
options.add("remote");
options.add(path);
- FLog.d(TAG, "Adding local remote [%s] remote = %s", id, path);
+ FLog.d(TAG, "Adding local remote [%s] remote = %s", name, path);
Process process = rclone.configCreate(options);
try {
process.waitFor();
@@ -861,15 +844,12 @@ private void addLocalRemote(File root) throws IOException {
FLog.e(TAG, "addLocalRemote: process error", e);
return;
}
- Set renamedRemotes = pref.getStringSet(getString(R.string.pref_key_renamed_remotes), new HashSet<>());
+ rclone.renameRemote(id, name);
Set pinnedRemotes = pref.getStringSet(getString(R.string.shared_preferences_drawer_pinned_remotes), new HashSet<>());
Set generatedRemotes = pref.getStringSet(getString(R.string.pref_key_local_alias_remotes), new HashSet<>());
- renamedRemotes.add(id);
pinnedRemotes.add(id);
generatedRemotes.add(id);
pref.edit()
- .putStringSet(getString(R.string.pref_key_renamed_remotes), renamedRemotes)
- .putString(getString(R.string.pref_key_renamed_remote_prefix, id), name)
.putStringSet(getString(R.string.shared_preferences_drawer_pinned_remotes), pinnedRemotes)
.putStringSet(getString(R.string.pref_key_local_alias_remotes), generatedRemotes)
.apply();
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java b/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java
index fdd1f507..8dad14f1 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java
@@ -5,6 +5,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
+import android.util.Log;
import android.webkit.MimeTypeMap;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -17,6 +18,7 @@
import io.github.x0b.safdav.SafAccessProvider;
import io.github.x0b.safdav.SafDAVServer;
import io.github.x0b.safdav.file.SafConstants;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -46,6 +48,9 @@ public class Rclone {
public static final int SERVE_PROTOCOL_WEBDAV = 2;
public static final int SERVE_PROTOCOL_FTP = 3;
public static final int SERVE_PROTOCOL_DLNA = 4;
+ private static final String[] COMMON_TRANSFER_OPTIONS = new String[] {
+ "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE"
+ };
private static SafDAVServer safDAVServer;
private Context context;
private String rclone;
@@ -178,7 +183,7 @@ public void logErrorOutput(Process process) {
}
@Nullable
- public List getDirectoryContent(RemoteItem remote, String path, boolean startAtRoot) {
+ public List ls(RemoteItem remote, String path, boolean startAtRoot) {
String remoteAndPath = remote.getName() + ":";
if (startAtRoot) {
remoteAndPath += "/";
@@ -254,7 +259,7 @@ public List getDirectoryContent(RemoteItem remote, String path, boolea
FileItem fileItem = new FileItem(remote, filePath, fileName, fileSize, fileModTime, mimeType, fileIsDir);
fileItemList.add(fileItem);
} catch (JSONException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return null;
}
}
@@ -267,8 +272,18 @@ public List getRemotes() {
Process process;
JSONObject remotesJSON;
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
- Set pinnedRemotes = sharedPreferences.getStringSet(context.getString(R.string.shared_preferences_pinned_remotes), new HashSet<>());
- Set favoriteRemotes = sharedPreferences.getStringSet(context.getString(R.string.shared_preferences_drawer_pinned_remotes), new HashSet<>());
+ Set pinnedRemotes = sharedPreferences.getStringSet(
+ context.getString(R.string.shared_preferences_pinned_remotes),
+ new HashSet<>()
+ );
+ Set favoriteRemotes = sharedPreferences.getStringSet(
+ context.getString(R.string.shared_preferences_drawer_pinned_remotes),
+ new HashSet<>()
+ );
+ Set renamedRemotes = sharedPreferences.getStringSet(
+ context.getString(R.string.pref_key_renamed_remotes),
+ new HashSet<>()
+ );
try {
process = Runtime.getRuntime().exec(command);
@@ -288,7 +303,7 @@ public List getRemotes() {
remotesJSON = new JSONObject(output.toString());
} catch (IOException | InterruptedException | JSONException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return new ArrayList<>();
}
@@ -310,7 +325,15 @@ public List getRemotes() {
}
}
- RemoteItem newRemote = new RemoteItem(key, type);
+ String displayName = key;
+ if (renamedRemotes.contains(key)) {
+ displayName = sharedPreferences.getString(
+ context.getString(R.string.pref_key_renamed_remote_prefix, key),
+ key
+ );
+ }
+
+ RemoteItem newRemote = new RemoteItem(key, displayName, type);
if (type.equals("crypt") || type.equals("alias") || type.equals("cache")) {
newRemote = getRemoteType(remotesJSON, newRemote, key, 8);
if (newRemote == null) {
@@ -329,7 +352,7 @@ public List getRemotes() {
remoteItemList.add(newRemote);
} catch (JSONException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
}
}
@@ -387,7 +410,7 @@ private RemoteItem getRemoteType(JSONObject remotesJSON, RemoteItem remoteItem,
remoteItem.setType(type);
return remoteItem;
} catch (JSONException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
}
}
@@ -403,11 +426,10 @@ public Process configCreate(List options) {
System.arraycopy(opt, 0, commandWithOptions, command.length, opt.length);
-
try {
return Runtime.getRuntime().exec(commandWithOptions);
} catch (IOException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return null;
}
}
@@ -426,7 +448,7 @@ public void deleteRemote(String remoteName) {
process = Runtime.getRuntime().exec(command);
process.waitFor();
} catch (IOException | InterruptedException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
}
}
@@ -444,7 +466,7 @@ public String obscure(String pass) {
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
return reader.readLine();
} catch (IOException | InterruptedException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return null;
}
}
@@ -501,7 +523,7 @@ public Process serve(int protocol, int port, boolean allowRemoteAccess, @Nullabl
try {
return Runtime.getRuntime().exec(command, env);
} catch (IOException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return null;
}
}
@@ -516,81 +538,122 @@ public Process sync(RemoteItem remoteItem, String remote, String localPath, int
String localRemotePath = (remoteItem.isRemoteType(RemoteItem.LOCAL)) ? getLocalRemotePathPrefix(remoteItem, context) + "/" : "";
String remotePath = (remote.compareTo("//" + remoteName) == 0) ? remoteName + ":" + localRemotePath : remoteName + ":" + localRemotePath + remote;
+ List opts = new ArrayList<>(Arrays.asList("sync"));
if (syncDirection == 1) {
- command = createCommandWithOptions("sync", localPath, remotePath, "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE");
+ opts.addAll(Arrays.asList(localPath, remotePath));
} else if (syncDirection == 2) {
- command = createCommandWithOptions("sync", remotePath, localPath, "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE");
+ opts.addAll(Arrays.asList(remotePath, localPath));
} else {
return null;
}
+ opts.addAll(Arrays.asList(COMMON_TRANSFER_OPTIONS));
+ command = createCommandWithOptions(opts.toArray(new String[0]));
String[] env = getRcloneEnv();
try {
return Runtime.getRuntime().exec(command, env);
} catch (IOException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return null;
}
}
+ private String buildRemoteFilePath(RemoteItem remote, String path) {
+ String remoteFilePath = remote.getName() + ":";
+ if (remote.isRemoteType(RemoteItem.LOCAL) && !remote.isAlias() && !remote.isCrypt() && !remote.isCache()) {
+ remoteFilePath += getLocalRemotePathPrefix(remote, context) + "/";
+ }
+ remoteFilePath += path;
+ return remoteFilePath;
+ }
+
public Process downloadFile(RemoteItem remote, FileItem downloadItem, String downloadPath) {
String[] command;
- String remoteFilePath;
String localFilePath;
-
- remoteFilePath = remote.getName() + ":";
- if (remote.isRemoteType(RemoteItem.LOCAL) && (!remote.isAlias() && !remote.isCrypt() && !remote.isCache())) {
- remoteFilePath += getLocalRemotePathPrefix(remote, context) + "/";
- }
- remoteFilePath += downloadItem.getPath();
+ String remoteFilePath = buildRemoteFilePath(remote, downloadItem.getPath());
if (downloadItem.isDir()) {
localFilePath = downloadPath + "/" + downloadItem.getName();
} else {
localFilePath = downloadPath;
}
- command = createCommandWithOptions("copy", remoteFilePath, localFilePath, "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE");
+
+ List opts = new ArrayList<>(
+ Arrays.asList("copy", remoteFilePath, localFilePath)
+ );
+ opts.addAll(Arrays.asList(COMMON_TRANSFER_OPTIONS));
+ command = createCommandWithOptions(opts.toArray(new String[0]));
String[] env = getRcloneEnv();
try {
return Runtime.getRuntime().exec(command, env);
} catch (IOException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return null;
}
}
- public Process uploadFile(RemoteItem remote, String uploadPath, String uploadFile) {
- String remoteName = remote.getName();
- String path;
+ public Process catFile(RemoteItem remote, String path) {
String[] command;
- String localRemotePath;
+ String remoteFilePath = buildRemoteFilePath(remote, path);
- if (remote.isRemoteType(RemoteItem.LOCAL) && (!remote.isAlias() && !remote.isCrypt() && !remote.isCache())) {
- localRemotePath = getLocalRemotePathPrefix(remote, context) + "/";
- } else {
- localRemotePath = "";
+ List opts = new ArrayList<>(Arrays.asList("cat", remoteFilePath));
+ opts.addAll(Arrays.asList(COMMON_TRANSFER_OPTIONS));
+ command = createCommandWithOptions(opts.toArray(new String[0]));
+
+ String[] env = getRcloneEnv();
+ try {
+ return Runtime.getRuntime().exec(command, env);
+ } catch (IOException e) {
+ Log.e(TAG, "Runtime error.", e);
+ return null;
}
+ }
+
+ public Process uploadFile(RemoteItem remote, String uploadPath, String fileToUpload) {
+ String remoteName = remote.getName();
+ String[] command;
+ String path = uploadPath.equals("//" + remoteName)
+ ? buildRemoteFilePath(remote, "")
+ : buildRemoteFilePath(remote, uploadPath);
- File file = new File(uploadFile);
+ File file = new File(fileToUpload);
if (file.isDirectory()) {
- int index = uploadFile.lastIndexOf('/');
- String dirName = uploadFile.substring(index + 1);
- path = (uploadPath.compareTo("//" + remoteName) == 0) ? remoteName + ":" + localRemotePath + dirName : remoteName + ":" + localRemotePath + uploadPath + "/" + dirName;
- } else {
- path = (uploadPath.compareTo("//" + remoteName) == 0) ? remoteName + ":" + localRemotePath : remoteName + ":" + localRemotePath + uploadPath;
+ int index = fileToUpload.lastIndexOf('/');
+ String dirName = fileToUpload.substring(index + 1);
+ path += "/" + dirName;
}
- command = createCommandWithOptions("copy", uploadFile, path, "--transfers", "1", "--stats=1s", "--stats-log-level", "NOTICE");
+ List opts = new ArrayList<>(
+ Arrays.asList("copy", fileToUpload, path)
+ );
+ opts.addAll(Arrays.asList(COMMON_TRANSFER_OPTIONS));
+ command = createCommandWithOptions(opts.toArray(new String[0]));
String[] env = getRcloneEnv();
try {
return Runtime.getRuntime().exec(command, env);
} catch (IOException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return null;
}
+ }
+
+ public Process rCatFile(RemoteItem remote, String uploadPath) {
+ String[] command;
+ String remoteFilePath = buildRemoteFilePath(remote, uploadPath);
+
+ List opts = new ArrayList<>(Arrays.asList("rcat", remoteFilePath));
+ opts.addAll(Arrays.asList(COMMON_TRANSFER_OPTIONS));
+ command = createCommandWithOptions(opts.toArray(new String[0]));
+ String[] env = getRcloneEnv();
+ try {
+ return Runtime.getRuntime().exec(command, env);
+ } catch (IOException e) {
+ Log.e(TAG, "Runtime error.", e);
+ return null;
+ }
}
public Process deleteItems(RemoteItem remote, FileItem deleteItem) {
@@ -616,7 +679,7 @@ public Process deleteItems(RemoteItem remote, FileItem deleteItem) {
try {
process = Runtime.getRuntime().exec(command, env);
} catch (IOException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
}
return process;
}
@@ -641,7 +704,7 @@ public Boolean makeDirectory(RemoteItem remote, String path) {
return false;
}
} catch (IOException | InterruptedException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return false;
}
return true;
@@ -668,7 +731,7 @@ public Process moveTo(RemoteItem remote, FileItem moveItem, String newLocation)
try {
process = Runtime.getRuntime().exec(command, env);
} catch (IOException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
}
return process;
@@ -696,7 +759,7 @@ public Boolean moveTo(RemoteItem remote, String oldFile, String newFile) {
return false;
}
} catch (IOException | InterruptedException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return false;
}
return true;
@@ -710,7 +773,7 @@ public boolean emptyTrashCan(String remote) {
process = Runtime.getRuntime().exec(command, env);
process.waitFor();
} catch (IOException | InterruptedException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
}
return process != null && process.exitValue() == 0;
@@ -738,7 +801,7 @@ public String link(RemoteItem remote, String filePath) {
return reader.readLine();
} catch (IOException | InterruptedException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
if (process != null) {
logErrorOutput(process);
}
@@ -775,7 +838,7 @@ public String calculateMD5(RemoteItem remote, FileItem fileItem) {
return split[0];
}
} catch (IOException | InterruptedException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return context.getString(R.string.hash_error);
}
}
@@ -809,7 +872,7 @@ public String calculateSHA1(RemoteItem remote, FileItem fileItem) {
return split[0];
}
} catch (IOException | InterruptedException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return context.getString(R.string.hash_error);
}
}
@@ -831,7 +894,7 @@ public String getRcloneVersion() {
result.add(line);
}
} catch (IOException | InterruptedException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return "-1";
}
@@ -946,7 +1009,7 @@ public Boolean isConfigEncrypted() {
process = Runtime.getRuntime().exec(command);
process.waitFor();
} catch (IOException | InterruptedException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return false;
}
return process.exitValue() != 0;
@@ -960,7 +1023,7 @@ public Boolean decryptConfig(String password) {
try {
process = Runtime.getRuntime().exec(command, environmentalVars);
} catch (IOException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return false;
}
@@ -972,14 +1035,14 @@ public Boolean decryptConfig(String password) {
result.add(line);
}
} catch (IOException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return false;
}
try {
process.waitFor();
} catch (InterruptedException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return false;
}
@@ -1003,7 +1066,7 @@ public Boolean decryptConfig(String password) {
fileOutputStream.flush();
fileOutputStream.close();
} catch (IOException e) {
- e.printStackTrace();
+ Log.e(TAG, "Runtime error.", e);
return false;
}
return true;
@@ -1103,6 +1166,25 @@ public void exportConfigFile(Uri uri) throws IOException {
outputStream.close();
}
+ public void renameRemote(String remoteName, String displayName) {
+ final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
+ final Set renamedRemotes = pref.getStringSet(
+ context.getString(R.string.pref_key_renamed_remotes),
+ new HashSet<>()
+ );
+ renamedRemotes.add(remoteName);
+ pref.edit()
+ .putString(
+ context.getString(R.string.pref_key_renamed_remote_prefix, remoteName),
+ displayName
+ )
+ .putStringSet(
+ context.getString(R.string.pref_key_renamed_remotes),
+ renamedRemotes
+ )
+ .apply();
+ }
+
/**
* Prefixes local remotes with a base path on the primary external storage.
* @param item
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/AliasConfig.java b/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/AliasConfig.java
index 38e9d46e..d471cad8 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/AliasConfig.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/AliasConfig.java
@@ -131,7 +131,6 @@ private void setRemote() {
return;
}
- RemoteItem.prepareDisplay(context, remotes);
Collections.sort(remotes, (a, b) -> a.getDisplayName().compareTo(b.getDisplayName()));
String[] options = new String[remotes.size()];
int i = 0;
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/CacheConfig.java b/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/CacheConfig.java
index 8ea2c7e8..8e95b04d 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/CacheConfig.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/CacheConfig.java
@@ -286,7 +286,6 @@ private void setRemote() {
Toasty.info(context, getString(R.string.no_remotes), Toast.LENGTH_SHORT, true).show();
return;
}
- RemoteItem.prepareDisplay(context, remotes);
Collections.sort(remotes, (a, b) -> a.getDisplayName().compareTo(b.getDisplayName()));
String[] options = new String[remotes.size()];
int i = 0;
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/CryptConfig.java b/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/CryptConfig.java
index 8457938a..bbc6378f 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/CryptConfig.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/CryptConfig.java
@@ -289,7 +289,6 @@ private void setRemote() {
return;
}
- RemoteItem.prepareDisplay(context, remotes);
Collections.sort(remotes, (a, b) -> a.getDisplayName().compareTo(b.getDisplayName()));
String[] options = new String[remotes.size()];
int i = 0;
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/DriveConfig.java b/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/DriveConfig.java
index 1ab42c77..5a5dc8fd 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/DriveConfig.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/DriveConfig.java
@@ -2,10 +2,8 @@
import android.annotation.SuppressLint;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -18,7 +16,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
-import androidx.browser.customtabs.CustomTabsIntent;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import ca.pkay.rcloneexplorer.MainActivity;
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/UnionConfig.java b/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/UnionConfig.java
index fc7ef519..f04e25d2 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/UnionConfig.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/RemoteConfig/UnionConfig.java
@@ -122,7 +122,6 @@ private void addRemote() {
return;
}
- RemoteItem.prepareDisplay(context, configuredRemotes);
Collections.sort(configuredRemotes, (a, b) -> a.getDisplayName().compareTo(b.getDisplayName()));
String[] options = new String[configuredRemotes.size()];
int i = 0;
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/SAFProvider/BufferedTransferThread.java b/app/src/main/java/ca/pkay/rcloneexplorer/SAFProvider/BufferedTransferThread.java
new file mode 100644
index 00000000..4e1dd37d
--- /dev/null
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/SAFProvider/BufferedTransferThread.java
@@ -0,0 +1,66 @@
+package ca.pkay.rcloneexplorer.SAFProvider;
+
+import android.os.CancellationSignal;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+class BufferedTransferThread extends Thread {
+ private static String TAG = "BufferedTransferThread";
+
+ private InputStream is;
+ private OutputStream os;
+ private int bufferSize;
+ private @Nullable CancellationSignal cancellationSignal;
+
+ public BufferedTransferThread(
+ InputStream is,
+ OutputStream os,
+ @Nullable CancellationSignal cancellationSignal
+ ) {
+ this(is, os, 65536, cancellationSignal);
+ }
+
+ public BufferedTransferThread(
+ InputStream is,
+ OutputStream os,
+ int bufferSize,
+ @Nullable CancellationSignal cancellationSignal
+ ) {
+ this.is = is;
+ this.os = os;
+ this.bufferSize = bufferSize;
+ this.cancellationSignal = cancellationSignal;
+ }
+
+ private boolean isCanceled() {
+ return cancellationSignal != null && cancellationSignal.isCanceled();
+ }
+
+ @Override
+ public void run() {
+ byte[] buf = new byte[this.bufferSize];
+ int len;
+
+ try {
+ while (!isCanceled() && (len = is.read(buf)) != -1) {
+ os.write(buf, 0, len);
+ os.flush();
+ }
+ } catch (IOException e) {
+ Log.i(TAG, "Couldn't write file.");
+ } finally {
+ try {
+ is.close();
+ os.close();
+ } catch (IOException e) {
+ Log.i(TAG, "Couldn't close file.");
+ }
+ }
+
+ }
+}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/SAFProvider/RcxUri.java b/app/src/main/java/ca/pkay/rcloneexplorer/SAFProvider/RcxUri.java
new file mode 100644
index 00000000..02a55f98
--- /dev/null
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/SAFProvider/RcxUri.java
@@ -0,0 +1,111 @@
+package ca.pkay.rcloneexplorer.SAFProvider;
+
+import android.net.Uri;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.FileNotFoundException;
+import java.util.List;
+
+import ca.pkay.rcloneexplorer.Items.FileItem;
+import ca.pkay.rcloneexplorer.Items.RemoteItem;
+import ca.pkay.rcloneexplorer.Rclone;
+
+class RcxUri {
+ private static final String RCX_SCHEME = "rcx://";
+
+ private String uri;
+ private Uri parsedUri;
+ private List pathSegments;
+
+ public RcxUri(String uri) {
+ this.uri = uri;
+ this.parsedUri = Uri.parse(uri);
+ this.pathSegments = parsedUri.getPathSegments();
+ }
+
+ public RcxUri(Uri uri) {
+ this(uri.toString());
+ }
+
+ @NotNull
+ @Override
+ public String toString() {
+ return uri;
+ }
+
+ public static RcxUri fromRemoteName(String remoteName) {
+ return new RcxUri(RCX_SCHEME + Uri.encode(remoteName));
+ }
+
+ public String getPathForRClone() {
+ StringBuilder sb = new StringBuilder();
+ for (final String s : pathSegments) {
+ sb.append("/");
+ sb.append(Uri.decode(s));
+ }
+ return sb.toString();
+ }
+
+ public String getRemoteName() {
+ return Uri.decode(parsedUri.getHost());
+ }
+
+ public RemoteItem getRemoteItem(Rclone rclone) {
+ final String remoteName = this.getRemoteName();
+ for (final RemoteItem remote : rclone.getRemotes()) {
+ if (remote.getName().equals(remoteName)) {
+ return remote;
+ }
+ }
+ return null;
+ }
+
+ public RcxUri getParentRcxUri() {
+ if (pathSegments.size() == 0) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder(
+ parsedUri.getScheme() + "://" + Uri.encode(parsedUri.getHost())
+ );
+ for (String segment : pathSegments.subList(0, pathSegments.size() - 1)) {
+ sb.append("/" + segment);
+ }
+ return new RcxUri(sb.toString());
+ }
+
+ public RcxUri getChildRcxUri(String unencodedFilename) {
+ return new RcxUri(
+ Uri.withAppendedPath(parsedUri, Uri.encode(unencodedFilename))
+ );
+ }
+
+ public String getFileName() {
+ return pathSegments.get(pathSegments.size() - 1);
+ }
+
+ public FileItem getFileItem(Rclone rclone) throws FileNotFoundException {
+ // Unfortunately, rclone has no equivalent for ls' "--directory" option
+ // that lists directories and not their content.
+ // As a workaround, we query the parent directory of the requested document
+ // and find the corresponding item within it.
+ RcxUri parentRcxUri = getParentRcxUri();
+
+ final List directoryContent = rclone.ls(
+ getRemoteItem(rclone),
+ parentRcxUri.getPathForRClone(),
+ false
+ );
+ if (directoryContent == null) {
+ throw new FileNotFoundException("Couldn't query document document.");
+ }
+
+ final String fileName = getFileName();
+ for (final FileItem fileItem : directoryContent) {
+ if (fileItem.getName().equals(fileName)) {
+ return fileItem;
+ }
+ }
+ throw new FileNotFoundException("Couldn't find document in remote document.");
+ }
+}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/SAFProvider/SAFProvider.java b/app/src/main/java/ca/pkay/rcloneexplorer/SAFProvider/SAFProvider.java
new file mode 100644
index 00000000..4a9517e0
--- /dev/null
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/SAFProvider/SAFProvider.java
@@ -0,0 +1,317 @@
+package ca.pkay.rcloneexplorer.SAFProvider;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsProvider;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+import ca.pkay.rcloneexplorer.Items.FileItem;
+import ca.pkay.rcloneexplorer.Items.RemoteItem;
+import ca.pkay.rcloneexplorer.Rclone;
+
+public final class SAFProvider extends DocumentsProvider {
+ private static final String TAG = "SAFProvider";
+
+ private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
+ DocumentsContract.Root.COLUMN_ROOT_ID,
+ DocumentsContract.Root.COLUMN_MIME_TYPES,
+ DocumentsContract.Root.COLUMN_FLAGS,
+ DocumentsContract.Root.COLUMN_ICON,
+ DocumentsContract.Root.COLUMN_TITLE,
+ DocumentsContract.Root.COLUMN_SUMMARY,
+ DocumentsContract.Root.COLUMN_DOCUMENT_ID,
+ DocumentsContract.Root.COLUMN_AVAILABLE_BYTES,
+ };
+ private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
+ DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+ DocumentsContract.Document.COLUMN_MIME_TYPE,
+ DocumentsContract.Document.COLUMN_DISPLAY_NAME,
+ DocumentsContract.Document.COLUMN_LAST_MODIFIED,
+ DocumentsContract.Document.COLUMN_FLAGS,
+ DocumentsContract.Document.COLUMN_SIZE,
+ };
+ private static final int SUPPORTED_DOCUMENT_FLAGS =
+ DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE
+ | DocumentsContract.Document.FLAG_SUPPORTS_DELETE
+ | DocumentsContract.Document.FLAG_SUPPORTS_MOVE
+ | DocumentsContract.Document.FLAG_SUPPORTS_RENAME
+ | DocumentsContract.Document.FLAG_SUPPORTS_WRITE;
+
+
+ private Rclone rclone;
+ private Context context;
+
+ @Override
+ public boolean onCreate() {
+ context = this.getContext();
+ if (context == null) {
+ return false;
+ }
+ rclone = new Rclone(context);
+ return true;
+ }
+
+ @Override
+ public Cursor queryRoots(String[] projection) {
+ if (projection == null) {
+ projection = DEFAULT_ROOT_PROJECTION;
+ }
+
+ List remotes = rclone.getRemotes();
+
+ final MatrixCursor result = new MatrixCursor(projection, remotes.size());
+
+ if (remotes.size() == 0) {
+ return result;
+ }
+
+ for (RemoteItem remote : remotes) {
+ if (remote.isRemoteType(RemoteItem.LOCAL)) {
+ continue;
+ }
+
+ RcxUri rcxUri = RcxUri.fromRemoteName(remote.getName());
+ Log.d(TAG, "Adding root " + rcxUri.toString() + ".");
+
+ final MatrixCursor.RowBuilder row = result.newRow();
+ row.add(DocumentsContract.Root.COLUMN_ROOT_ID, rcxUri);
+ row.add(DocumentsContract.Root.COLUMN_SUMMARY, remote.getDisplayName());
+ row.add(
+ DocumentsContract.Root.COLUMN_FLAGS,
+ DocumentsContract.Root.FLAG_SUPPORTS_CREATE
+ );
+ row.add(DocumentsContract.Root.COLUMN_TITLE, "RClone");
+ row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, rcxUri);
+ row.add(DocumentsContract.Root.COLUMN_MIME_TYPES, null);
+ row.add(DocumentsContract.Root.COLUMN_AVAILABLE_BYTES, null);
+ row.add(DocumentsContract.Root.COLUMN_ICON, remote.getRemoteIcon());
+ }
+
+ return result;
+ }
+
+ private void includeFileItem(MatrixCursor result, FileItem file, RcxUri parentRcxUri) {
+ final String fileName = file.getName();
+ final RcxUri rcxUri = parentRcxUri.getChildRcxUri(fileName);
+ Log.d(TAG, "Adding document " + rcxUri.toString());
+
+ final MatrixCursor.RowBuilder row = result.newRow();
+ row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, rcxUri.toString());
+ row.add(
+ DocumentsContract.Document.COLUMN_MIME_TYPE,
+ file.isDir() ? DocumentsContract.Document.MIME_TYPE_DIR : file.getMimeType()
+ );
+ row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, file.getName());
+ row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, file.getModTime());
+ row.add(DocumentsContract.Document.COLUMN_FLAGS, SUPPORTED_DOCUMENT_FLAGS);
+ row.add(DocumentsContract.Document.COLUMN_SIZE, file.isDir() ? null : file.getSize());
+ }
+
+ @Override
+ public Cursor queryChildDocuments(String parentUri, String[] projection, String sortOrder) throws FileNotFoundException {
+ if (projection == null) {
+ projection = DEFAULT_DOCUMENT_PROJECTION;
+ }
+
+ final MatrixCursor result = new MatrixCursor(projection) {
+ @Override
+ public Bundle getExtras() {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(DocumentsContract.EXTRA_LOADING, true);
+ return bundle;
+ }
+ };
+
+ RcxUri parentRcxUri = new RcxUri(parentUri);
+
+ Log.d(TAG, "Querying child documents from URI " + parentRcxUri.toString());
+ final List fileItems = rclone.ls(
+ parentRcxUri.getRemoteItem(rclone),
+ parentRcxUri.getPathForRClone(),
+ false
+ );
+ if (fileItems == null) {
+ throw new FileNotFoundException("rclone call failed.");
+ }
+
+ for (final FileItem file : fileItems) {
+ includeFileItem(result, file, parentRcxUri);
+ }
+
+ return result;
+ }
+
+ @Override
+ public Cursor queryDocument(String uri, String[] projection) throws FileNotFoundException {
+ if (projection == null) {
+ projection = DEFAULT_DOCUMENT_PROJECTION;
+ }
+
+ final MatrixCursor result = new MatrixCursor(projection, 0);
+
+ RcxUri rcxUri = new RcxUri(uri);
+ RcxUri parentUri = rcxUri.getParentRcxUri();
+ if (parentUri == null) {
+ // Special case: we're at root
+ final MatrixCursor.RowBuilder row = result.newRow();
+ row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, uri);
+ row.add(
+ DocumentsContract.Document.COLUMN_MIME_TYPE,
+ DocumentsContract.Document.MIME_TYPE_DIR
+ );
+ row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, rcxUri.getRemoteName());
+ row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, null);
+ row.add(DocumentsContract.Document.COLUMN_FLAGS, SUPPORTED_DOCUMENT_FLAGS);
+ row.add(DocumentsContract.Document.COLUMN_SIZE, null);
+ } else {
+ includeFileItem(result, rcxUri.getFileItem(rclone), parentUri);
+ }
+
+ return result;
+ }
+
+ @Override
+ public String createDocument(String parentUri, String mimeType, String displayName) throws FileNotFoundException {
+ RcxUri rcxUri = new RcxUri(parentUri).getChildRcxUri(displayName);
+
+ if (
+ DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)
+ && rclone.makeDirectory(rcxUri.getRemoteItem(rclone), rcxUri.getPathForRClone())
+ ) {
+ return rcxUri.toString();
+ }
+
+ final Process proc = rclone.rCatFile(
+ rcxUri.getRemoteItem(rclone),
+ rcxUri.getPathForRClone()
+ );
+ final OutputStream stdin = proc.getOutputStream();
+ try {
+ stdin.close();
+ proc.waitFor();
+ } catch (IOException | InterruptedException e) {
+ Log.e(TAG, "Got exception during document creation.", e);
+ }
+
+ if (proc.exitValue() == 0) {
+ return rcxUri.toString();
+ }
+
+ throw new FileNotFoundException(
+ "Couldn't create document at URI " + rcxUri.toString() + "."
+ );
+ }
+
+ @Override
+ public ParcelFileDescriptor openDocument(
+ String uri,
+ String mode,
+ @Nullable CancellationSignal cs
+ ) throws FileNotFoundException {
+ Log.d(TAG, "openDocument, mode: " + mode);
+ RcxUri rcxUri = new RcxUri(uri);
+
+ ParcelFileDescriptor[] pipe;
+ try {
+ pipe = ParcelFileDescriptor.createPipe();
+ } catch (IOException e) {
+ Log.e(TAG, "Couldn't create pipe for document " + uri + ".", e);
+ throw new FileNotFoundException();
+ }
+
+ if ("r".equals(mode)) {
+ final Process proc = rclone.catFile(
+ rcxUri.getRemoteItem(rclone),
+ rcxUri.getPathForRClone()
+ );
+ final InputStream is = proc.getInputStream();
+ final OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]);
+ new BufferedTransferThread(is, os, cs).start();
+ return pipe[0];
+ }
+ else if ("w".equals(mode)) {
+ final Process proc = rclone.rCatFile(
+ rcxUri.getRemoteItem(rclone),
+ rcxUri.getPathForRClone()
+ );
+ final OutputStream os = proc.getOutputStream();
+ final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0]);
+ new BufferedTransferThread(is, os, cs).start();
+ return pipe[1];
+ }
+
+ throw new FileNotFoundException(
+ "Cannot open uri " + uri + " in mode " + mode + "."
+ );
+ }
+
+ @Override
+ public void deleteDocument(String uri) throws FileNotFoundException {
+ RcxUri rcxUri = new RcxUri(uri);
+ Process p = rclone.deleteItems(
+ rcxUri.getRemoteItem(rclone),
+ rcxUri.getFileItem(rclone)
+ );
+ try {
+ p.waitFor();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Delete process was interupted.", e);
+ return;
+ }
+
+ if (p.exitValue() != 0) {
+ Log.e(TAG, "Couldn't delete file at URI " + uri + ".");
+ }
+ }
+
+ private String mvDocument(RcxUri sourceRcxUri, RcxUri targetRcxUri) throws FileNotFoundException {
+ if (!sourceRcxUri.getRemoteName().equals(
+ targetRcxUri.getRemoteName()
+ )) {
+ throw new FileNotFoundException("Can't move remote document to another remote.");
+ }
+
+ if (!rclone.moveTo(
+ sourceRcxUri.getRemoteItem(rclone),
+ sourceRcxUri.getPathForRClone(),
+ targetRcxUri.getPathForRClone()
+ )) {
+ throw new FileNotFoundException(
+ "Couldn't move item file at URI " + sourceRcxUri.toString() + "."
+ );
+ }
+
+ return targetRcxUri.toString();
+ }
+
+ @Override
+ public String renameDocument(String sourceUri, String displayName) throws FileNotFoundException {
+ RcxUri sourceRcxUri = new RcxUri(sourceUri);
+ RcxUri targetRcxUri = sourceRcxUri.getParentRcxUri().getChildRcxUri(displayName);
+ return mvDocument(sourceRcxUri, targetRcxUri);
+ }
+
+ @Override
+ public String moveDocument(String sourceUri, String sourceParentUri, String targetParentUri) throws FileNotFoundException {
+ RcxUri sourceRcxUri = new RcxUri(sourceUri);
+
+ RcxUri targetParentRcxUrl = new RcxUri(targetParentUri);
+ String fileName = sourceRcxUri.getFileName();
+ RcxUri targetRcxUri = targetParentRcxUrl.getChildRcxUri(fileName);
+
+ return mvDocument(sourceRcxUri, targetRcxUri);
+ }
+}
diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Settings/GeneralSettingsFragment.java b/app/src/main/java/ca/pkay/rcloneexplorer/Settings/GeneralSettingsFragment.java
index f2c7b29c..40b1fa06 100644
--- a/app/src/main/java/ca/pkay/rcloneexplorer/Settings/GeneralSettingsFragment.java
+++ b/app/src/main/java/ca/pkay/rcloneexplorer/Settings/GeneralSettingsFragment.java
@@ -215,7 +215,6 @@ private void showAppShortcutDialog() {
Rclone rclone = new Rclone(context);
final ArrayList remotes = new ArrayList<>(rclone.getRemotes());
- RemoteItem.prepareDisplay(context, remotes);
Collections.sort(remotes, (a, b) -> a.getDisplayName().compareTo(b.getDisplayName()));
final CharSequence[] options = new CharSequence[remotes.size()];
int i = 0;
@@ -227,10 +226,10 @@ private void showAppShortcutDialog() {
boolean[] checkedItems = new boolean[options.length];
i = 0;
for (RemoteItem item : remotes) {
- String s = item.getName().toString();
+ String s = item.getName();
String hash = AppShortcutsHelper.getUniqueIdFromString(s);
if (appShortcuts.contains(hash)) {
- userSelected.add(item.getName().toString());
+ userSelected.add(item.getName());
checkedItems[i] = true;
}
i++;
diff --git a/build.gradle b/build.gradle
index 77912987..8af4fed9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,17 +1,20 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
-
repositories {
google()
jcenter()
}
+
dependencies {
- classpath 'com.android.tools.build:gradle:3.6.0'
+ classpath 'com.android.tools.build:gradle:4.0.1'
- if (!getGradle().getStartParameter().getTaskRequests().toString().toLowerCase().contains("oss")) {
- classpath 'com.google.gms:google-services:4.3.3' // google-services plugin
- classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta03'
+ for (String taskName : getGradle().getStartParameter().getTaskNames()) {
+ if (taskName.endsWith('RcxDebug') || taskName.endsWith('RcxRelease')) {
+ classpath 'com.google.gms:google-services:4.3.3'
+ classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta03'
+ break
+ }
}
// NOTE: Do not place your application dependencies here; they belong
diff --git a/gradle.properties b/gradle.properties
index 347c1725..ccb5d7ec 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -14,6 +14,10 @@ android.useAndroidX=true
# android.enableR8=true
org.gradle.jvmargs=-Xmx1536m
+io.github.x0b.rcx.rCloneVersion=1.52.3
+io.github.x0b.rcx.buildToolsVersion=29.0.3
+io.github.x0b.rcx.ndkVersion=21.3.6528147
+
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index aa62ffe1..57c85fd0 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
+#Sun Jul 12 14:10:53 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
-distributionSha256Sum=1f3067073041bc44554d0efe5d402a33bc3d3c93cc39ab684f308586d732a80d
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
diff --git a/rclone/.gitignore b/rclone/.gitignore
index 71a8716d..14d86ad6 100644
--- a/rclone/.gitignore
+++ b/rclone/.gitignore
@@ -1 +1 @@
-/gopath
+/cache
diff --git a/rclone/build.gradle b/rclone/build.gradle
index 5015e0cb..1349b105 100644
--- a/rclone/build.gradle
+++ b/rclone/build.gradle
@@ -1,106 +1,151 @@
-// Rclone version - any git reference (tag, branch, hash) should work
-def buildTag = 'v1.51.0'
-
-//
-// DO NOT EDIT ANYTHING BELOW
-//
import java.nio.file.Paths
-def repository = 'github.com/rclone/rclone'
-def goPath = Paths.get(projectDir.absolutePath, 'gopath').toAbsolutePath().toString()
-def osName = System.properties['os.name'].toLowerCase()
-def osArch = System.properties['os.arch']
-def String os
-if (osName.contains('windows')) {
- if(osArch.equals('amd64')) {
- os = "windows-x86_64"
- } else if (osArch.equals('x86')) {
- os = "windows"
+
+ext {
+ NDK_VERSION = project.properties['io.github.x0b.rcx.ndkVersion']
+ RCLONE_VERSION = project.properties['io.github.x0b.rcx.rCloneVersion']
+ RCLONE_MODULE = 'github.com/rclone/rclone'
+ CACHE_PATH = Paths.get(projectDir.absolutePath, 'cache').toAbsolutePath().toString()
+ GOPATH = Paths.get(CACHE_PATH, "gopath").toString()
+}
+
+def findSdkDir() {
+ def androidHome = System.getenv('ANDROID_HOME')
+ if (androidHome != null) {
+ return androidHome
+ }
+
+ def localPropertiesFile = project.rootProject.file('local.properties')
+ if (localPropertiesFile.exists()) {
+ Properties properties = new Properties()
+ properties.load(localPropertiesFile.newDataInputStream())
+ def sdkDir = properties.get('sdk.dir')
+ if (sdkDir != null) {
+ return sdkDir
+ }
+ }
+
+ throw GradleException(
+ "Couldn't locate your android SDK location. Make sure to set sdk.dir property "
+ + "in your local.properties at the root of the project or set ANDROID_HOME "
+ + "environment variable"
+ )
+}
+
+def findNdkDir() {
+ def sdkDir = findSdkDir()
+ def ndkPath = Paths.get(sdkDir, 'ndk', NDK_VERSION).resolve().toAbsolutePath()
+ if (!ndkPath.toFile().exists()) {
+ throw new GradleException(String.format(
+ "Couldn't find a ndk bundle in %s. Make sure that you have the proper "
+ + "version installed in Android Studio's SDK Manager or that you have "
+ + "run \"%s 'ndk;%s'\".",
+ ndkPath.toString(),
+ Paths.get(sdkDir, 'tools', 'bin', 'sdkmanager').toString(),
+ NDK_VERSION
+ ))
+ }
+ return ndkPath.toString()
+}
+
+def getCrossCompiler(bin) {
+ def osName = System.properties['os.name'].toLowerCase()
+ def osArch = System.properties['os.arch']
+ def os
+ if (osName.contains('windows')) {
+ if(osArch.equals('amd64')) {
+ os = "windows-x86_64"
+ } else if (osArch.equals('x86')) {
+ os = "windows"
+ }
+ } else if (osName.contains("linux")) {
+ os = "linux-x86_64"
+ } else if (osName.contains('mac')) {
+ os = "darwin-x86_64"
+ } else {
+ throw new GradleException("Unsupported OS.")
+ }
+
+ return Paths.get(
+ findNdkDir(),
+ 'toolchains',
+ 'llvm',
+ 'prebuilt',
+ os,
+ 'bin',
+ bin
+ )
+}
+
+def getLibrclone(arch) {
+ return Paths.get('..', 'app', 'lib', arch, 'librclone.so').toString()
+}
+
+def buildRclone(compiler, arch, abi, env = [:]) {
+ return {
+ doLast {
+ exec {
+ environment 'GOPATH', GOPATH
+ def crossCompiler = getCrossCompiler(compiler)
+ environment 'CC', crossCompiler
+ environment 'CC_FOR_TARGET', crossCompiler
+ environment 'GOOS', 'android'
+ environment 'GOARCH', arch
+ environment 'CGO_ENABLED', '1'
+ env.each {entry -> environment "$entry.key", "$entry.value"}
+ workingDir CACHE_PATH
+ commandLine 'go', 'build', '-tags', 'linux', '-o', getLibrclone(abi), RCLONE_MODULE
+ }
+ }
}
-} else if (osName.contains("linux")) {
- os = "linux-x86_64"
-} else if (osName.contains('mac')) {
- os = "darwin-x86_64"
}
-task fetchRclone(type: Exec) {
- mkdir "gopath"
- environment 'GOPATH', Paths.get(projectDir.absolutePath, 'gopath')
- commandLine 'go', 'get', '-d', repository
+task createRcloneModule(type: Exec) {
+ // We create a fake go module as it's the only way to checkout
+ // a specific tag.
+ onlyIf { !Paths.get(CACHE_PATH, 'go.mod').toFile().exists() }
+ mkdir CACHE_PATH
+ workingDir CACHE_PATH
+ environment 'GOPATH', GOPATH
+ commandLine 'go', 'mod', 'init', 'rclone'
}
-task checkoutRclone(type: Exec) {
- dependsOn fetchRclone
- workingDir Paths.get(goPath, "src/${repository}".split('/'))
- commandLine 'git', 'checkout', buildTag
+task checkoutRclone(type: Exec, dependsOn: createRcloneModule) {
+ workingDir CACHE_PATH
+ environment 'GOPATH', GOPATH
+ commandLine 'go', 'get', '-v', '-d', "${RCLONE_MODULE}@v${RCLONE_VERSION}"
}
task cleanNative {
enabled = false
doLast {
- delete '../app/lib/armeabi-v7a/librclone.so'
- delete '../app/lib/arm64-v8a/librclone.so'
- delete '../app/lib/x86/librclone.so'
- delete '../app/lib/x86_64/librclone.so'
+ delete getLibrclone('armeabi-v7a')
+ delete getLibrclone('arm64-v8a')
+ delete getLibrclone('x86')
+ delete getLibrclone('x86_64')
}
}
-task buildArm(type: Exec) {
- dependsOn checkoutRclone
- environment 'GOPATH', Paths.get(projectDir.absolutePath, 'gopath')
- def String crossCompiler = Paths.get(System.getenv('ANDROID_HOME'), 'ndk-bundle', 'toolchains', 'llvm', 'prebuilt', os, 'bin', 'armv7a-linux-androideabi21-clang')
- environment 'CC', crossCompiler
- environment 'CC_FOR_TARGET', crossCompiler
- environment 'GOOS', 'android'
- environment 'GOARCH', 'arm'
- environment 'GOARM', '7'
- environment 'CGO_ENABLED', '1'
- commandLine 'go', 'build', '-tags', 'linux', '-o', '../app/lib/armeabi-v7a/librclone.so', repository
+task buildArm(dependsOn: checkoutRclone) {
+ configure buildRclone('armv7a-linux-androideabi21-clang', 'arm', 'armeabi-v7a', ['GOARM': '7'])
}
-task buildArm64(type: Exec) {
- dependsOn checkoutRclone
- environment 'GOPATH', Paths.get(projectDir.absolutePath, 'gopath')
- def String crossCompiler = Paths.get(System.getenv('ANDROID_HOME'), 'ndk-bundle', 'toolchains', 'llvm', 'prebuilt', os, 'bin', 'aarch64-linux-android21-clang')
- environment 'CC', crossCompiler
- environment 'CC_FOR_TARGET', crossCompiler
- environment 'GOOS', 'android'
- environment 'GOARCH', 'arm64'
- environment 'CGO_ENABLED', '1'
- commandLine 'go', 'build', '-tags', 'linux', '-o', '../app/lib/arm64-v8a/librclone.so', repository
+task buildArm64(dependsOn: checkoutRclone) {
+ configure buildRclone('aarch64-linux-android21-clang', 'arm64', 'arm64-v8a')
}
-task buildx86(type: Exec) {
- dependsOn checkoutRclone
- environment 'GOPATH', Paths.get(projectDir.absolutePath, 'gopath')
- def String crossCompiler = Paths.get(System.getenv('ANDROID_HOME'), 'ndk-bundle', 'toolchains', 'llvm', 'prebuilt', os, 'bin', 'i686-linux-android21-clang')
- environment 'CC', crossCompiler
- environment 'CC_FOR_TARGET', crossCompiler
- environment 'GOOS', 'android'
- environment 'GOARCH', '386'
- environment 'CGO_ENABLED', '1'
- commandLine 'go', 'build', '-tags', 'linux', '-o', '../app/lib/x86/librclone.so', repository
+task buildx86(dependsOn: checkoutRclone) {
+ configure buildRclone('i686-linux-android21-clang', '386', 'x86')
}
-task buildx64(type: Exec) {
- dependsOn checkoutRclone
- environment 'GOPATH', Paths.get(projectDir.absolutePath, 'gopath')
- def String crossCompiler = Paths.get(System.getenv('ANDROID_HOME'), 'ndk-bundle', 'toolchains', 'llvm', 'prebuilt', os, 'bin', 'x86_64-linux-android21-clang')
- environment 'CC', crossCompiler
- environment 'CC_FOR_TARGET', crossCompiler
- environment 'GOOS', 'android'
- environment 'GOARCH', 'amd64'
- environment 'CGO_ENABLED', '1'
- commandLine 'go', 'build', '-tags', 'linux', '-o', '../app/lib/x86_64/librclone.so', repository
+task buildx64(dependsOn: checkoutRclone) {
+ configure buildRclone('x86_64-linux-android21-clang', 'amd64', 'x86_64')
}
task buildNative {
- dependsOn fetchRclone
- dependsOn checkoutRclone
dependsOn buildArm
dependsOn buildArm64
dependsOn buildx86
dependsOn buildx64
}
-buildNative.mustRunAfter(buildArm, buildArm64, buildx86, buildx64)
defaultTasks 'buildNative'
diff --git a/safdav/build.gradle b/safdav/build.gradle
index 5ddc3ba2..17fac3db 100644
--- a/safdav/build.gradle
+++ b/safdav/build.gradle
@@ -2,7 +2,7 @@ apply plugin: 'com.android.library'
android {
compileSdkVersion 29
-
+ buildToolsVersion project.properties['io.github.x0b.rcx.buildToolsVersion']
defaultConfig {
minSdkVersion 21