Merge pull request #1977 from TeamAmaze/bugfix/1942

This commit is contained in:
Emmanuel
2020-09-17 08:05:11 -03:00
committed by GitHub
35 changed files with 1019 additions and 672 deletions
+8
View File
@@ -114,6 +114,8 @@ dependencies {
implementation "androidx.room:room-runtime:$roomVersion"
implementation "com.android.billingclient:billing:$androidBillingVersion"
annotationProcessor "androidx.room:room-compiler:$roomVersion"
// RxJava support for Room
implementation "androidx.room:room-rxjava2:$roomVersion"
annotationProcessor 'androidx.annotation:annotation:1.1.0'
//For tests
@@ -218,6 +220,12 @@ dependencies {
implementation 'org.tukaani:xz:1.8'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
// Because RxAndroid releases are few and far between, it is recommended you also
// explicitly depend on RxJava's latest version for bug fixes and new features.
// (see https://github.com/ReactiveX/RxJava/releases for latest 3.x.x version)
implementation group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.1.1'
implementation project(':commons_compress_7z')
}
@@ -36,9 +36,6 @@ import com.android.volley.toolbox.Volley;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.StrictMode;
import android.widget.Toast;
@@ -47,6 +44,11 @@ import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatDelegate;
import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import jcifs.Config;
public class AppConfig extends GlideApplication {
public static final String TAG = AppConfig.class.getSimpleName();
@@ -56,9 +58,6 @@ public class AppConfig extends GlideApplication {
private ImageLoader imageLoader;
private UtilsHandler utilsHandler;
private static Handler applicationhandler = new Handler();
private HandlerThread backgroundHandlerThread;
private static Handler backgroundHandler;
private WeakReference<Context> mainActivityContext;
private static ScreenUtils screenUtils;
@@ -77,7 +76,6 @@ public class AppConfig extends GlideApplication {
super.onCreate();
AppCompatDelegate.setCompatVectorFromResourcesEnabled(
true); // selector in srcCompat isn't supported without this
backgroundHandlerThread = new HandlerThread("app_background");
instance = this;
CustomSshJConfig.init();
@@ -87,10 +85,7 @@ public class AppConfig extends GlideApplication {
utilsProvider = new UtilitiesProvider(this);
utilsHandler = new UtilsHandler(this, utilitiesDatabase);
backgroundHandlerThread.start();
backgroundHandler = new Handler(backgroundHandlerThread.getLooper());
runInBackground(jcifs.Config::registerSmbURLHandler);
runInBackground(Config::registerSmbURLHandler);
// disabling file exposure method check for api n+
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
@@ -100,7 +95,6 @@ public class AppConfig extends GlideApplication {
@Override
public void onTerminate() {
super.onTerminate();
backgroundHandlerThread.quit();
}
/**
@@ -108,54 +102,8 @@ public class AppConfig extends GlideApplication {
* this runnable is executed, and {@link #runInBackground(Runnable)} in case we need to execute
* something after execution in background
*/
public static void runInBackground(Runnable runnable) {
synchronized (backgroundHandler) {
backgroundHandler.post(runnable);
}
}
/**
* A compact AsyncTask which runs which executes whatever is passed by callbacks. Supports any
* class that extends an object as param array, and result too.
*/
public static <Params, Result> void runInParallel(
final CustomAsyncCallbacks<Params, Result> customAsyncCallbacks) {
synchronized (customAsyncCallbacks) {
new AsyncTask<Params, Void, Result>() {
@Override
protected void onPreExecute() {
super.onPreExecute();
customAsyncCallbacks.onPreExecute();
}
@Override
protected Result doInBackground(Object... params) {
return customAsyncCallbacks.doInBackground();
}
@Override
protected void onPostExecute(Result aVoid) {
super.onPostExecute(aVoid);
customAsyncCallbacks.onPostExecute(aVoid);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, customAsyncCallbacks.parameters);
}
}
/** Interface providing callbacks utilized by {@link #runInBackground(Runnable)} */
public abstract static class CustomAsyncCallbacks<Params, Result> {
public final @Nullable Params[] parameters;
public CustomAsyncCallbacks(@Nullable Params[] params) {
parameters = params;
}
public abstract Result doInBackground();
public void onPostExecute(Result result) {}
public void onPreExecute() {}
public void runInBackground(Runnable runnable) {
Completable.fromRunnable(runnable).subscribeOn(Schedulers.io()).subscribe();
}
/**
@@ -212,7 +160,7 @@ public class AppConfig extends GlideApplication {
* @param r Runnable to run
*/
public void runInApplicationThread(Runnable r) {
applicationhandler.post(r);
Completable.fromRunnable(r).subscribeOn(AndroidSchedulers.mainThread()).subscribe();
}
public static synchronized AppConfig getInstance() {
@@ -194,27 +194,28 @@ public class MoveFiles extends AsyncTask<ArrayList<String>, String, Boolean> {
}
// updating encrypted db entry if any encrypted file was moved
AppConfig.runInBackground(
() -> {
for (int i = 0; i < paths.size(); i++) {
for (HybridFileParcelable file : files.get(i)) {
if (file.getName(context).endsWith(CryptUtil.CRYPT_EXTENSION)) {
try {
CryptHandler cryptHandler = CryptHandler.getInstance();
EncryptedEntry oldEntry = cryptHandler.findEntry(file.getPath());
EncryptedEntry newEntry = new EncryptedEntry();
newEntry.setId(oldEntry.getId());
newEntry.setPassword(oldEntry.getPassword());
newEntry.setPath(paths.get(i) + "/" + file.getName(context));
cryptHandler.updateEntry(oldEntry, newEntry);
} catch (Exception e) {
e.printStackTrace();
// couldn't change the entry, leave it alone
AppConfig.getInstance()
.runInBackground(
() -> {
for (int i = 0; i < paths.size(); i++) {
for (HybridFileParcelable file : files.get(i)) {
if (file.getName(context).endsWith(CryptUtil.CRYPT_EXTENSION)) {
try {
CryptHandler cryptHandler = CryptHandler.getInstance();
EncryptedEntry oldEntry = cryptHandler.findEntry(file.getPath());
EncryptedEntry newEntry = new EncryptedEntry();
newEntry.setId(oldEntry.getId());
newEntry.setPassword(oldEntry.getPassword());
newEntry.setPath(paths.get(i) + "/" + file.getName(context));
cryptHandler.updateEntry(oldEntry, newEntry);
} catch (Exception e) {
e.printStackTrace();
// couldn't change the entry, leave it alone
}
}
}
}
}
}
});
});
} else {
@@ -20,7 +20,6 @@
package com.amaze.filemanager.database;
import java.util.Arrays;
import java.util.List;
import com.amaze.filemanager.database.models.explorer.CloudEntry;
@@ -29,9 +28,12 @@ import com.amaze.filemanager.ui.fragments.CloudSheetFragment;
import com.amaze.filemanager.utils.OpenMode;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import io.reactivex.schedulers.Schedulers;
/** Created by vishal on 18/4/17. */
public class CloudHandler {
@@ -57,13 +59,21 @@ public class CloudHandler {
if (!CloudSheetFragment.isCloudProviderAvailable(context)) throw new CloudPluginException();
database.cloudEntryDao().insert(cloudEntry);
database.cloudEntryDao().insert(cloudEntry).subscribeOn(Schedulers.io()).subscribe();
}
public void clear(OpenMode serviceType) {
database
.cloudEntryDao()
.delete(database.cloudEntryDao().findByServiceType(serviceType.ordinal()));
.findByServiceType(serviceType.ordinal())
.subscribeOn(Schedulers.io())
.subscribe(
cloudEntry ->
database
.cloudEntryDao()
.delete(cloudEntry)
.subscribeOn(Schedulers.io())
.subscribe());
}
public void updateEntry(OpenMode serviceType, CloudEntry newCloudEntry)
@@ -71,20 +81,29 @@ public class CloudHandler {
if (!CloudSheetFragment.isCloudProviderAvailable(context)) throw new CloudPluginException();
database.cloudEntryDao().update(newCloudEntry);
database.cloudEntryDao().update(newCloudEntry).subscribeOn(Schedulers.io()).subscribe();
}
public CloudEntry findEntry(OpenMode serviceType) throws CloudPluginException {
if (!CloudSheetFragment.isCloudProviderAvailable(context)) throw new CloudPluginException();
return database.cloudEntryDao().findByServiceType(serviceType.ordinal());
try {
return database
.cloudEntryDao()
.findByServiceType(serviceType.ordinal())
.subscribeOn(Schedulers.io())
.blockingGet();
} catch (Exception e) {
// catch error to handle Single#onError for blockingGet
Log.e(getClass().getSimpleName(), e.getMessage());
return null;
}
}
public List<CloudEntry> getAllEntries() throws CloudPluginException {
if (!CloudSheetFragment.isCloudProviderAvailable(context)) throw new CloudPluginException();
return Arrays.asList(database.cloudEntryDao().list());
return database.cloudEntryDao().list().subscribeOn(Schedulers.io()).blockingGet();
}
}
@@ -20,11 +20,17 @@
package com.amaze.filemanager.database;
import java.util.List;
import com.amaze.filemanager.application.AppConfig;
import com.amaze.filemanager.database.models.explorer.EncryptedEntry;
import android.util.Log;
import androidx.annotation.NonNull;
import io.reactivex.schedulers.Schedulers;
/** Created by vishal on 15/4/17. */
public class CryptHandler {
@@ -44,22 +50,31 @@ public class CryptHandler {
}
public void addEntry(EncryptedEntry encryptedEntry) {
AppConfig.runInBackground(() -> database.encryptedEntryDao().insert(encryptedEntry));
database.encryptedEntryDao().insert(encryptedEntry).subscribeOn(Schedulers.io()).subscribe();
}
public void clear(String path) {
AppConfig.runInBackground(() -> database.encryptedEntryDao().delete(path));
database.encryptedEntryDao().delete(path).subscribeOn(Schedulers.io()).subscribe();
}
public void updateEntry(EncryptedEntry oldEncryptedEntry, EncryptedEntry newEncryptedEntry) {
AppConfig.runInBackground(() -> database.encryptedEntryDao().update(newEncryptedEntry));
database.encryptedEntryDao().update(newEncryptedEntry).subscribeOn(Schedulers.io()).subscribe();
}
public EncryptedEntry findEntry(String path) {
return database.encryptedEntryDao().select(path);
try {
return database.encryptedEntryDao().select(path).subscribeOn(Schedulers.io()).blockingGet();
} catch (Exception e) {
// catch error to handle Single#onError for blockingGet
Log.e(getClass().getSimpleName(), e.getMessage());
return null;
}
}
public EncryptedEntry[] getAllEntries() {
return database.encryptedEntryDao().list();
List<EncryptedEntry> encryptedEntryList =
database.encryptedEntryDao().list().subscribeOn(Schedulers.io()).blockingGet();
EncryptedEntry[] encryptedEntries = new EncryptedEntry[encryptedEntryList.size()];
return encryptedEntryList.toArray(encryptedEntries);
}
}
@@ -31,10 +31,13 @@ import com.amaze.filemanager.database.models.explorer.Sort;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.reactivex.schedulers.Schedulers;
/** Created by Ning on 5/28/2018. */
public class SortHandler {
@@ -70,19 +73,25 @@ public class SortHandler {
}
public void addEntry(Sort sort) {
AppConfig.runInBackground(() -> database.sortDao().insert(sort));
database.sortDao().insert(sort).subscribeOn(Schedulers.io()).subscribe();
}
public void clear(String path) {
AppConfig.runInBackground(() -> database.sortDao().clear(path));
database.sortDao().clear(path).subscribeOn(Schedulers.io()).subscribe();
}
public void updateEntry(Sort oldSort, Sort newSort) {
AppConfig.runInBackground(() -> database.sortDao().update(newSort));
database.sortDao().update(newSort).subscribeOn(Schedulers.io()).subscribe();
}
@Nullable
public Sort findEntry(String path) {
return database.sortDao().find(path);
try {
return database.sortDao().find(path).subscribeOn(Schedulers.io()).blockingGet();
} catch (Exception e) {
// catch error to handle Single#onError for blockingGet
Log.e(getClass().getSimpleName(), e.getMessage());
return null;
}
}
}
@@ -20,12 +20,18 @@
package com.amaze.filemanager.database;
import java.util.List;
import com.amaze.filemanager.application.AppConfig;
import com.amaze.filemanager.database.models.explorer.Tab;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.reactivex.schedulers.Schedulers;
/** Created by Vishal on 9/17/2014. */
public class TabHandler {
@@ -45,19 +51,27 @@ public class TabHandler {
}
public void addTab(@NonNull Tab tab) {
database.tabDao().insertTab(tab);
database.tabDao().insertTab(tab).subscribeOn(Schedulers.io()).subscribe();
}
public void clear() {
database.tabDao().clear();
database.tabDao().clear().subscribeOn(Schedulers.io()).subscribe();
}
@Nullable
public Tab findTab(int tabNo) {
return database.tabDao().find(tabNo);
try {
return database.tabDao().find(tabNo).subscribeOn(Schedulers.io()).blockingGet();
} catch (Exception e) {
// catch error to handle Single#onError for blockingGet
Log.e(getClass().getSimpleName(), e.getMessage());
return null;
}
}
public Tab[] getAllTabs() {
return database.tabDao().list();
List<Tab> tabList = database.tabDao().list().subscribeOn(Schedulers.io()).blockingGet();
Tab[] tabs = new Tab[tabList.size()];
return tabList.toArray(tabs);
}
}
@@ -24,7 +24,6 @@ import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
@@ -50,6 +49,8 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import io.reactivex.schedulers.Schedulers;
/**
* Created by Vishal on 29-05-2017. Class handles database with tables having list of various
* utilities like history, hidden files, list paths, grid paths, bookmarks, SMB entry
@@ -80,80 +81,109 @@ public class UtilsHandler {
}
public void saveToDatabase(OperationData operationData) {
AppConfig.runInBackground(
() -> {
switch (operationData.type) {
case HIDDEN:
utilitiesDatabase.hiddenEntryDao().insert(new Hidden(operationData.path));
break;
case HISTORY:
utilitiesDatabase.historyEntryDao().insert(new History(operationData.path));
break;
case LIST:
utilitiesDatabase
.listEntryDao()
.insert(
new com.amaze.filemanager.database.models.utilities.List(operationData.path));
break;
case GRID:
utilitiesDatabase.gridEntryDao().insert(new Grid(operationData.path));
break;
case BOOKMARKS:
utilitiesDatabase
.bookmarkEntryDao()
.insert(new Bookmark(operationData.name, operationData.path));
break;
case SMB:
utilitiesDatabase
.smbEntryDao()
.insert(new SmbEntry(operationData.name, operationData.path));
break;
case SFTP:
utilitiesDatabase
.sftpEntryDao()
.insert(
new SftpEntry(
operationData.path,
operationData.name,
operationData.hostKey,
operationData.sshKeyName,
operationData.sshKey));
break;
default:
throw new IllegalStateException("Unidentified operation!");
}
});
switch (operationData.type) {
case HIDDEN:
utilitiesDatabase
.hiddenEntryDao()
.insert(new Hidden(operationData.path))
.subscribeOn(Schedulers.io())
.subscribe();
break;
case HISTORY:
utilitiesDatabase
.historyEntryDao()
.insert(new History(operationData.path))
.subscribeOn(Schedulers.io())
.subscribe();
break;
case LIST:
utilitiesDatabase
.listEntryDao()
.insert(new com.amaze.filemanager.database.models.utilities.List(operationData.path))
.subscribeOn(Schedulers.io())
.subscribe();
break;
case GRID:
utilitiesDatabase
.gridEntryDao()
.insert(new Grid(operationData.path))
.subscribeOn(Schedulers.io())
.subscribe();
break;
case BOOKMARKS:
utilitiesDatabase
.bookmarkEntryDao()
.insert(new Bookmark(operationData.name, operationData.path))
.subscribeOn(Schedulers.io())
.subscribe();
break;
case SMB:
utilitiesDatabase
.smbEntryDao()
.insert(new SmbEntry(operationData.name, operationData.path))
.subscribeOn(Schedulers.io())
.subscribe();
break;
case SFTP:
utilitiesDatabase
.sftpEntryDao()
.insert(
new SftpEntry(
operationData.path,
operationData.name,
operationData.hostKey,
operationData.sshKeyName,
operationData.sshKey))
.subscribeOn(Schedulers.io())
.subscribe();
break;
default:
throw new IllegalStateException("Unidentified operation!");
}
}
public void removeFromDatabase(OperationData operationData) {
AppConfig.runInBackground(
() -> {
switch (operationData.type) {
case HIDDEN:
utilitiesDatabase.hiddenEntryDao().deleteByPath(operationData.path);
break;
case HISTORY:
utilitiesDatabase.historyEntryDao().deleteByPath(operationData.path);
break;
case LIST:
utilitiesDatabase.listEntryDao().deleteByPath(operationData.path);
break;
case GRID:
utilitiesDatabase.gridEntryDao().deleteByPath(operationData.path);
break;
case BOOKMARKS:
removeBookmarksPath(operationData.name, operationData.path);
break;
case SMB:
removeSmbPath(operationData.name, operationData.path);
break;
case SFTP:
removeSftpPath(operationData.name, operationData.path);
break;
default:
throw new IllegalStateException("Unidentified operation!");
}
});
switch (operationData.type) {
case HIDDEN:
utilitiesDatabase
.hiddenEntryDao()
.deleteByPath(operationData.path)
.subscribeOn(Schedulers.io())
.subscribe();
break;
case HISTORY:
utilitiesDatabase
.historyEntryDao()
.deleteByPath(operationData.path)
.subscribeOn(Schedulers.io())
.subscribe();
break;
case LIST:
utilitiesDatabase
.listEntryDao()
.deleteByPath(operationData.path)
.subscribeOn(Schedulers.io())
.subscribe();
break;
case GRID:
utilitiesDatabase
.gridEntryDao()
.deleteByPath(operationData.path)
.subscribeOn(Schedulers.io())
.subscribe();
break;
case BOOKMARKS:
removeBookmarksPath(operationData.name, operationData.path);
break;
case SMB:
removeSmbPath(operationData.name, operationData.path);
break;
case SFTP:
removeSftpPath(operationData.name, operationData.path);
break;
default:
throw new IllegalStateException("Unidentified operation!");
}
}
public void addCommonBookmarks() {
@@ -181,23 +211,33 @@ public class UtilsHandler {
String sshKeyName,
String sshKey) {
SftpEntry entry = utilitiesDatabase.sftpEntryDao().findByName(oldConnectionName);
utilitiesDatabase
.sftpEntryDao()
.findByName(oldConnectionName)
.subscribeOn(Schedulers.io())
.subscribe(
entry -> {
entry.name = connectionName;
entry.path = path;
entry.hostKey = hostKey;
entry.name = connectionName;
entry.path = path;
entry.hostKey = hostKey;
if (sshKeyName != null && sshKey != null) {
entry.sshKeyName = sshKeyName;
entry.sshKey = sshKey;
}
if (sshKeyName != null && sshKey != null) {
entry.sshKeyName = sshKeyName;
entry.sshKey = sshKey;
}
AppConfig.runInBackground(() -> utilitiesDatabase.sftpEntryDao().update(entry));
utilitiesDatabase
.sftpEntryDao()
.update(entry)
.subscribeOn(Schedulers.io())
.subscribe();
});
}
public LinkedList<String> getHistoryLinkedList() {
LinkedList<String> paths = new LinkedList<>();
for (History history : utilitiesDatabase.historyEntryDao().list()) {
for (History history :
utilitiesDatabase.historyEntryDao().list().subscribeOn(Schedulers.io()).blockingGet()) {
paths.add(history.path);
}
return paths;
@@ -207,24 +247,28 @@ public class UtilsHandler {
ConcurrentRadixTree<VoidValue> paths =
new ConcurrentRadixTree<>(new DefaultCharArrayNodeFactory());
for (String path : utilitiesDatabase.hiddenEntryDao().listPaths()) {
for (String path :
utilitiesDatabase.hiddenEntryDao().listPaths().subscribeOn(Schedulers.io()).blockingGet()) {
paths.put(path, VoidValue.SINGLETON);
}
return paths;
}
public ArrayList<String> getListViewList() {
return new ArrayList<>(Arrays.asList(utilitiesDatabase.listEntryDao().listPaths()));
return new ArrayList<>(
utilitiesDatabase.listEntryDao().listPaths().subscribeOn(Schedulers.io()).blockingGet());
}
public ArrayList<String> getGridViewList() {
return new ArrayList<>(Arrays.asList(utilitiesDatabase.gridEntryDao().listPaths()));
return new ArrayList<>(
utilitiesDatabase.gridEntryDao().listPaths().subscribeOn(Schedulers.io()).blockingGet());
}
public ArrayList<String[]> getBookmarksList() {
ArrayList<String[]> row = new ArrayList<>();
for (Bookmark bookmark : utilitiesDatabase.bookmarkEntryDao().list()) {
for (Bookmark bookmark :
utilitiesDatabase.bookmarkEntryDao().list().subscribeOn(Schedulers.io()).blockingGet()) {
row.add(new String[] {bookmark.name, bookmark.path});
}
return row;
@@ -232,7 +276,8 @@ public class UtilsHandler {
public ArrayList<String[]> getSmbList() {
ArrayList<String[]> retval = new ArrayList<String[]>();
for (SmbEntry entry : utilitiesDatabase.smbEntryDao().list()) {
for (SmbEntry entry :
utilitiesDatabase.smbEntryDao().list().subscribeOn(Schedulers.io()).blockingGet()) {
try {
String path = SmbUtil.getSmbDecryptedPath(context, entry.path);
@@ -253,7 +298,8 @@ public class UtilsHandler {
public List<String[]> getSftpList() {
ArrayList<String[]> retval = new ArrayList<String[]>();
for (SftpEntry entry : utilitiesDatabase.sftpEntryDao().list()) {
for (SftpEntry entry :
utilitiesDatabase.sftpEntryDao().list().subscribeOn(Schedulers.io()).blockingGet()) {
String path = SshClientUtils.decryptSshPathAsNecessary(entry.path);
if (path == null) {
@@ -272,22 +318,56 @@ public class UtilsHandler {
public String getSshHostKey(String uri) {
uri = SshClientUtils.encryptSshPathAsNecessary(uri);
if (uri != null) {
return utilitiesDatabase.sftpEntryDao().getSshHostKey(uri);
try {
return utilitiesDatabase
.sftpEntryDao()
.getSshHostKey(uri)
.subscribeOn(Schedulers.io())
.blockingGet();
} catch (Exception e) {
// catch error to handle Single#onError for blockingGet
Log.e(getClass().getSimpleName(), e.getMessage());
return null;
}
} else {
return null;
}
}
public String getSshAuthPrivateKeyName(String uri) {
return utilitiesDatabase.sftpEntryDao().getSshAuthPrivateKeyName(uri);
try {
return utilitiesDatabase
.sftpEntryDao()
.getSshAuthPrivateKeyName(uri)
.subscribeOn(Schedulers.io())
.blockingGet();
} catch (Exception e) {
// catch error to handle Single#onError for blockingGet
Log.e(getClass().getSimpleName(), e.getMessage());
return null;
}
}
public String getSshAuthPrivateKey(String uri) {
return utilitiesDatabase.sftpEntryDao().getSshAuthPrivateKey(uri);
try {
return utilitiesDatabase
.sftpEntryDao()
.getSshAuthPrivateKey(uri)
.subscribeOn(Schedulers.io())
.blockingGet();
} catch (Exception e) {
// catch error to handle Single#onError for blockingGet
Log.e(getClass().getSimpleName(), e.getMessage());
return null;
}
}
private void removeBookmarksPath(String name, String path) {
utilitiesDatabase.bookmarkEntryDao().deleteByNameAndPath(name, path);
utilitiesDatabase
.bookmarkEntryDao()
.deleteByNameAndPath(name, path)
.subscribeOn(Schedulers.io())
.subscribe();
}
/**
@@ -297,21 +377,45 @@ public class UtilsHandler {
* must encrypt it's password fiend first first
*/
private void removeSmbPath(String name, String path) {
if ("".equals(path)) utilitiesDatabase.smbEntryDao().deleteByName(name);
else utilitiesDatabase.smbEntryDao().deleteByNameAndPath(name, path);
if ("".equals(path))
utilitiesDatabase.smbEntryDao().deleteByName(name).subscribeOn(Schedulers.io()).subscribe();
else
utilitiesDatabase
.smbEntryDao()
.deleteByNameAndPath(name, path)
.subscribeOn(Schedulers.io())
.subscribe();
}
private void removeSftpPath(String name, String path) {
if ("".equals(path)) utilitiesDatabase.sftpEntryDao().deleteByName(name);
else utilitiesDatabase.sftpEntryDao().deleteByNameAndPath(name, path);
if ("".equals(path))
utilitiesDatabase.sftpEntryDao().deleteByName(name).subscribeOn(Schedulers.io()).subscribe();
else
utilitiesDatabase
.sftpEntryDao()
.deleteByNameAndPath(name, path)
.subscribeOn(Schedulers.io())
.subscribe();
}
public void renameBookmark(String oldName, String oldPath, String newName, String newPath) {
Bookmark bookmark = utilitiesDatabase.bookmarkEntryDao().findByNameAndPath(oldName, oldPath);
Bookmark bookmark;
try {
bookmark =
utilitiesDatabase
.bookmarkEntryDao()
.findByNameAndPath(oldName, oldPath)
.subscribeOn(Schedulers.io())
.blockingGet();
} catch (Exception e) {
// catch error to handle Single#onError for blockingGet
Log.e(getClass().getSimpleName(), e.getMessage());
return;
}
bookmark.name = newName;
bookmark.path = newPath;
utilitiesDatabase.bookmarkEntryDao().update(bookmark);
utilitiesDatabase.bookmarkEntryDao().update(bookmark).subscribeOn(Schedulers.io()).subscribe();
}
public void renameSMB(String oldName, String oldPath, String newName, String newPath) {
@@ -321,24 +425,31 @@ public class UtilsHandler {
} catch (GeneralSecurityException | IOException e) {
Log.e(TAG, "Error encrypting SMB path", e);
}
String finalNewPath = newPath;
utilitiesDatabase
.smbEntryDao()
.findByNameAndPath(oldName, oldPath)
.subscribeOn(Schedulers.io())
.subscribe(
smbEntry -> {
smbEntry.name = newName;
smbEntry.path = finalNewPath;
SmbEntry smbEntry = utilitiesDatabase.smbEntryDao().findByNameAndPath(oldName, oldPath);
smbEntry.name = newName;
smbEntry.path = newPath;
utilitiesDatabase.smbEntryDao().update(smbEntry);
utilitiesDatabase
.smbEntryDao()
.update(smbEntry)
.subscribeOn(Schedulers.io())
.subscribe();
});
}
public void clearTable(Operation table) {
AppConfig.runInBackground(
() -> {
switch (table) {
case HISTORY:
utilitiesDatabase.historyEntryDao().clear();
break;
default:
break;
}
});
switch (table) {
case HISTORY:
utilitiesDatabase.historyEntryDao().clear().subscribeOn(Schedulers.io()).subscribe();
break;
default:
break;
}
}
}
@@ -24,6 +24,8 @@ import static com.amaze.filemanager.database.UtilitiesDatabase.COLUMN_NAME;
import static com.amaze.filemanager.database.UtilitiesDatabase.COLUMN_PATH;
import static com.amaze.filemanager.database.UtilitiesDatabase.TABLE_BOOKMARKS;
import java.util.List;
import com.amaze.filemanager.database.models.utilities.Bookmark;
import androidx.room.Dao;
@@ -31,6 +33,9 @@ import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import io.reactivex.Completable;
import io.reactivex.Single;
/**
* {@link Dao} interface definition for {@link Bookmark}. Concrete class is generated by Room during
* build.
@@ -43,13 +48,13 @@ import androidx.room.Update;
public interface BookmarkEntryDao {
@Insert
public void insert(Bookmark instance);
Completable insert(Bookmark instance);
@Update
public void update(Bookmark instance);
Completable update(Bookmark instance);
@Query("SELECT * FROM " + TABLE_BOOKMARKS)
public Bookmark[] list();
Single<List<Bookmark>> list();
@Query(
"SELECT * FROM "
@@ -59,7 +64,7 @@ public interface BookmarkEntryDao {
+ " = :name AND "
+ COLUMN_PATH
+ " = :path")
public Bookmark findByNameAndPath(String name, String path);
Single<Bookmark> findByNameAndPath(String name, String path);
@Query(
"DELETE FROM "
@@ -69,5 +74,5 @@ public interface BookmarkEntryDao {
+ " = :name AND "
+ COLUMN_PATH
+ " = :path")
public void deleteByNameAndPath(String name, String path);
Completable deleteByNameAndPath(String name, String path);
}
@@ -23,6 +23,8 @@ package com.amaze.filemanager.database.daos;
import static com.amaze.filemanager.database.ExplorerDatabase.COLUMN_CLOUD_SERVICE;
import static com.amaze.filemanager.database.ExplorerDatabase.TABLE_CLOUD_PERSIST;
import java.util.List;
import com.amaze.filemanager.database.models.explorer.CloudEntry;
import androidx.room.Dao;
@@ -31,6 +33,9 @@ import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import io.reactivex.Completable;
import io.reactivex.Single;
/**
* {@link Dao} interface definition for {@link CloudEntry}. Concrete class is generated by Room
* during build.
@@ -43,18 +48,18 @@ import androidx.room.Update;
public interface CloudEntryDao {
@Insert
public void insert(CloudEntry entry);
Completable insert(CloudEntry entry);
@Query(
"SELECT * FROM " + TABLE_CLOUD_PERSIST + " WHERE " + COLUMN_CLOUD_SERVICE + " = :serviceType")
public CloudEntry findByServiceType(int serviceType);
Single<CloudEntry> findByServiceType(int serviceType);
@Query("SELECT * FROM " + TABLE_CLOUD_PERSIST)
public CloudEntry[] list();
Single<List<CloudEntry>> list();
@Update
public void update(CloudEntry entry);
Completable update(CloudEntry entry);
@Delete
public void delete(CloudEntry entry);
Completable delete(CloudEntry entry);
}
@@ -23,6 +23,8 @@ package com.amaze.filemanager.database.daos;
import static com.amaze.filemanager.database.ExplorerDatabase.COLUMN_PATH;
import static com.amaze.filemanager.database.ExplorerDatabase.TABLE_ENCRYPTED;
import java.util.List;
import com.amaze.filemanager.database.models.explorer.EncryptedEntry;
import androidx.room.Dao;
@@ -31,6 +33,9 @@ import androidx.room.Query;
import androidx.room.Transaction;
import androidx.room.Update;
import io.reactivex.Completable;
import io.reactivex.Single;
/**
* {@link Dao} interface definition for {@link EncryptedEntry}. Concrete class is generated by Room
* during build.
@@ -43,18 +48,18 @@ import androidx.room.Update;
public interface EncryptedEntryDao {
@Insert
public void insert(EncryptedEntry entry);
Completable insert(EncryptedEntry entry);
@Query("SELECT * FROM " + TABLE_ENCRYPTED + " WHERE " + COLUMN_PATH + " = :path")
public EncryptedEntry select(String path);
Single<EncryptedEntry> select(String path);
@Update
public void update(EncryptedEntry entry);
Completable update(EncryptedEntry entry);
@Transaction
@Query("DELETE FROM " + TABLE_ENCRYPTED + " WHERE " + COLUMN_PATH + " = :path")
public void delete(String path);
Completable delete(String path);
@Query("SELECT * FROM " + TABLE_ENCRYPTED)
public EncryptedEntry[] list();
Single<List<EncryptedEntry>> list();
}
@@ -23,6 +23,8 @@ package com.amaze.filemanager.database.daos;
import static com.amaze.filemanager.database.UtilitiesDatabase.COLUMN_PATH;
import static com.amaze.filemanager.database.UtilitiesDatabase.TABLE_GRID;
import java.util.List;
import com.amaze.filemanager.database.models.utilities.Grid;
import androidx.room.Dao;
@@ -30,6 +32,9 @@ import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import io.reactivex.Completable;
import io.reactivex.Single;
/**
* {@link Dao} interface definition for {@link Grid}. Concrete class is generated by Room during
* build.
@@ -42,14 +47,14 @@ import androidx.room.Update;
public interface GridEntryDao {
@Insert
public void insert(Grid instance);
Completable insert(Grid instance);
@Update
public void update(Grid instance);
Completable update(Grid instance);
@Query("SELECT " + COLUMN_PATH + " FROM " + TABLE_GRID)
public String[] listPaths();
Single<List<String>> listPaths();
@Query("DELETE FROM " + TABLE_GRID + " WHERE " + COLUMN_PATH + " = :path")
public void deleteByPath(String path);
Completable deleteByPath(String path);
}
@@ -23,6 +23,8 @@ package com.amaze.filemanager.database.daos;
import static com.amaze.filemanager.database.UtilitiesDatabase.COLUMN_PATH;
import static com.amaze.filemanager.database.UtilitiesDatabase.TABLE_HIDDEN;
import java.util.List;
import com.amaze.filemanager.database.models.utilities.Hidden;
import androidx.room.Dao;
@@ -30,6 +32,9 @@ import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import io.reactivex.Completable;
import io.reactivex.Single;
/**
* {@link Dao} interface definition for {@link Hidden}. Concrete class is generated by Room during
* build.
@@ -42,14 +47,14 @@ import androidx.room.Update;
public interface HiddenEntryDao {
@Insert
public void insert(Hidden instance);
Completable insert(Hidden instance);
@Update
public void update(Hidden instance);
Completable update(Hidden instance);
@Query("SELECT " + COLUMN_PATH + " FROM " + TABLE_HIDDEN)
public String[] listPaths();
Single<List<String>> listPaths();
@Query("DELETE FROM " + TABLE_HIDDEN + " WHERE " + COLUMN_PATH + " = :path")
public void deleteByPath(String path);
Completable deleteByPath(String path);
}
@@ -23,6 +23,8 @@ package com.amaze.filemanager.database.daos;
import static com.amaze.filemanager.database.UtilitiesDatabase.COLUMN_PATH;
import static com.amaze.filemanager.database.UtilitiesDatabase.TABLE_HISTORY;
import java.util.List;
import com.amaze.filemanager.database.models.utilities.History;
import androidx.room.Dao;
@@ -31,6 +33,9 @@ import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
import io.reactivex.Completable;
import io.reactivex.Single;
/**
* {@link Dao} interface definition for {@link History}. Concrete class is generated by Room during
* build.
@@ -43,17 +48,17 @@ import androidx.room.Update;
public interface HistoryEntryDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insert(History instance);
Completable insert(History instance);
@Update
public void update(History instance);
Completable update(History instance);
@Query("SELECT * FROM " + TABLE_HISTORY)
public History[] list();
Single<List<History>> list();
@Query("DELETE FROM " + TABLE_HISTORY + " WHERE " + COLUMN_PATH + " = :path")
public void deleteByPath(String path);
Completable deleteByPath(String path);
@Query("DELETE FROM " + TABLE_HISTORY)
public void clear();
Completable clear();
}
@@ -31,6 +31,9 @@ import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
import io.reactivex.Completable;
import io.reactivex.Single;
/**
* {@link Dao} interface definition for {@link List}. Concrete class is generated by Room during
* build.
@@ -43,14 +46,14 @@ import androidx.room.Update;
public interface ListEntryDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insert(List instance);
Completable insert(List instance);
@Update
public void update(List instance);
Completable update(List instance);
@Query("SELECT " + COLUMN_PATH + " FROM " + TABLE_LIST)
public String[] listPaths();
Single<java.util.List<String>> listPaths();
@Query("DELETE FROM " + TABLE_LIST + " WHERE " + COLUMN_PATH + " = :path")
public void deleteByPath(String path);
Completable deleteByPath(String path);
}
@@ -27,6 +27,8 @@ import static com.amaze.filemanager.database.UtilitiesDatabase.COLUMN_PRIVATE_KE
import static com.amaze.filemanager.database.UtilitiesDatabase.COLUMN_PRIVATE_KEY_NAME;
import static com.amaze.filemanager.database.UtilitiesDatabase.TABLE_SFTP;
import java.util.List;
import com.amaze.filemanager.database.models.utilities.SftpEntry;
import androidx.room.Dao;
@@ -34,6 +36,9 @@ import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import io.reactivex.Completable;
import io.reactivex.Single;
/**
* {@link Dao} interface definition for {@link SftpEntry}. Concrete class is generated by Room
* during build.
@@ -46,13 +51,13 @@ import androidx.room.Update;
public interface SftpEntryDao {
@Insert
public void insert(SftpEntry instance);
Completable insert(SftpEntry instance);
@Update
public void update(SftpEntry instance);
Completable update(SftpEntry instance);
@Query("SELECT * FROM " + TABLE_SFTP)
public SftpEntry[] list();
Single<List<SftpEntry>> list();
@Query(
"SELECT * FROM "
@@ -62,22 +67,22 @@ public interface SftpEntryDao {
+ " = :name AND "
+ COLUMN_PATH
+ " = :path")
public SftpEntry findByNameAndPath(String name, String path);
Single<SftpEntry> findByNameAndPath(String name, String path);
@Query("SELECT * FROM " + TABLE_SFTP + " WHERE " + COLUMN_NAME + " = :name")
public SftpEntry findByName(String name);
Single<SftpEntry> findByName(String name);
@Query("SELECT " + COLUMN_HOST_PUBKEY + " FROM " + TABLE_SFTP + " WHERE PATH = :uri")
public String getSshHostKey(String uri);
Single<String> getSshHostKey(String uri);
@Query("SELECT " + COLUMN_PRIVATE_KEY_NAME + " FROM " + TABLE_SFTP + " WHERE PATH = :uri")
public String getSshAuthPrivateKeyName(String uri);
Single<String> getSshAuthPrivateKeyName(String uri);
@Query("SELECT " + COLUMN_PRIVATE_KEY + " FROM " + TABLE_SFTP + " WHERE PATH = :uri")
public String getSshAuthPrivateKey(String uri);
Single<String> getSshAuthPrivateKey(String uri);
@Query("DELETE FROM " + TABLE_SFTP + " WHERE " + COLUMN_NAME + " = :name")
public void deleteByName(String name);
Completable deleteByName(String name);
@Query(
"DELETE FROM "
@@ -87,5 +92,5 @@ public interface SftpEntryDao {
+ " = :name AND "
+ COLUMN_PATH
+ " = :path")
public void deleteByNameAndPath(String name, String path);
Completable deleteByNameAndPath(String name, String path);
}
@@ -24,6 +24,8 @@ import static com.amaze.filemanager.database.UtilitiesDatabase.COLUMN_NAME;
import static com.amaze.filemanager.database.UtilitiesDatabase.COLUMN_PATH;
import static com.amaze.filemanager.database.UtilitiesDatabase.TABLE_SMB;
import java.util.List;
import com.amaze.filemanager.database.models.utilities.SmbEntry;
import androidx.room.Dao;
@@ -31,6 +33,9 @@ import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import io.reactivex.Completable;
import io.reactivex.Single;
/**
* {@link Dao} interface definition for {@link SmbEntry}. Concrete class is generated by Room during
* build.
@@ -43,13 +48,13 @@ import androidx.room.Update;
public interface SmbEntryDao {
@Insert
public void insert(SmbEntry instance);
Completable insert(SmbEntry instance);
@Update
public void update(SmbEntry instance);
Completable update(SmbEntry instance);
@Query("SELECT * FROM " + TABLE_SMB)
public SmbEntry[] list();
Single<List<SmbEntry>> list();
@Query(
"SELECT * FROM "
@@ -59,10 +64,10 @@ public interface SmbEntryDao {
+ " = :name AND "
+ COLUMN_PATH
+ " = :path")
public SmbEntry findByNameAndPath(String name, String path);
Single<SmbEntry> findByNameAndPath(String name, String path);
@Query("DELETE FROM " + TABLE_SMB + " WHERE " + COLUMN_NAME + " = :name")
public void deleteByName(String name);
Completable deleteByName(String name);
@Query(
"DELETE FROM "
@@ -72,5 +77,5 @@ public interface SmbEntryDao {
+ " = :name AND "
+ COLUMN_PATH
+ " = :path")
public void deleteByNameAndPath(String name, String path);
Completable deleteByNameAndPath(String name, String path);
}
@@ -31,6 +31,9 @@ import androidx.room.Query;
import androidx.room.Transaction;
import androidx.room.Update;
import io.reactivex.Completable;
import io.reactivex.Single;
/**
* {@link Dao} interface definition for {@link Sort}. Concrete class is generated by Room during
* build.
@@ -43,15 +46,15 @@ import androidx.room.Update;
public interface SortDao {
@Insert
public void insert(Sort entity);
Completable insert(Sort entity);
@Query("SELECT * FROM " + TABLE_SORT + " WHERE " + COLUMN_PATH + " = :path")
public Sort find(String path);
Single<Sort> find(String path);
@Transaction
@Query("DELETE FROM " + TABLE_SORT + " WHERE " + COLUMN_PATH + " = :path")
public void clear(String path);
Completable clear(String path);
@Update
public void update(Sort entity);
Completable update(Sort entity);
}
@@ -23,6 +23,8 @@ package com.amaze.filemanager.database.daos;
import static com.amaze.filemanager.database.ExplorerDatabase.COLUMN_TAB_NO;
import static com.amaze.filemanager.database.ExplorerDatabase.TABLE_TAB;
import java.util.List;
import com.amaze.filemanager.database.models.explorer.Tab;
import androidx.room.Dao;
@@ -31,6 +33,9 @@ import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Transaction;
import io.reactivex.Completable;
import io.reactivex.Single;
/**
* {@link Dao} interface definition for {@link Tab}. Concrete class is generated by Room during
* build.
@@ -42,15 +47,15 @@ import androidx.room.Transaction;
@Dao
public interface TabDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertTab(Tab tab);
Completable insertTab(Tab tab);
@Transaction
@Query("DELETE FROM " + TABLE_TAB)
public void clear();
Completable clear();
@Query("SELECT * FROM " + TABLE_TAB + " WHERE " + COLUMN_TAB_NO + " = :tabNo")
public Tab find(int tabNo);
Single<Tab> find(int tabNo);
@Query("SELECT * FROM " + TABLE_TAB)
public Tab[] list();
Single<List<Tab>> list();
}
@@ -71,6 +71,12 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.documentfile.provider.DocumentFile;
import io.reactivex.Maybe;
import io.reactivex.MaybeObserver;
import io.reactivex.MaybeOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import jcifs.smb.SmbException;
import jcifs.smb.SmbFile;
@@ -200,197 +206,214 @@ public abstract class FileUtil {
@NonNull final ContentResolver contentResolver,
@NonNull final String currentPath) {
AppConfig.runInParallel(
new AppConfig.CustomAsyncCallbacks<Void, List<String>>(null) {
Maybe.create(
(MaybeOnSubscribe<List<String>>)
emitter -> {
List<String> retval = new ArrayList<>();
@Override
public List<String> doInBackground() {
for (Uri uri : uris) {
List<String> retval = new ArrayList<>();
BufferedInputStream bufferedInputStream = null;
try {
bufferedInputStream =
new BufferedInputStream(contentResolver.openInputStream(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
for (Uri uri : uris) {
BufferedOutputStream bufferedOutputStream = null;
BufferedInputStream bufferedInputStream = null;
try {
bufferedInputStream = new BufferedInputStream(contentResolver.openInputStream(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
DocumentFile documentFile = DocumentFile.fromSingleUri(mainActivity, uri);
String filename = documentFile.getName();
if (filename == null) {
filename = uri.getLastPathSegment();
BufferedOutputStream bufferedOutputStream = null;
// For cleaning up slashes. Back in #1217 there is a case of
// Uri.getLastPathSegment() end up with a full file path
if (filename.contains("/"))
filename = filename.substring(filename.lastIndexOf('/') + 1);
}
try {
DocumentFile documentFile = DocumentFile.fromSingleUri(mainActivity, uri);
String filename = documentFile.getName();
if (filename == null) {
filename = uri.getLastPathSegment();
String finalFilePath = currentPath + "/" + filename;
DataUtils dataUtils = DataUtils.getInstance();
// For cleaning up slashes. Back in #1217 there is a case of
// Uri.getLastPathSegment() end up with a full file path
if (filename.contains("/"))
filename = filename.substring(filename.lastIndexOf('/') + 1);
}
HybridFile hFile = new HybridFile(OpenMode.UNKNOWN, currentPath);
hFile.generateMode(mainActivity);
String finalFilePath = currentPath + "/" + filename;
DataUtils dataUtils = DataUtils.getInstance();
switch (hFile.getMode()) {
case FILE:
case ROOT:
File targetFile = new File(finalFilePath);
if (!FileUtil.isWritableNormalOrSaf(
targetFile.getParentFile(), mainActivity.getApplicationContext())) {
AppConfig.toast(
mainActivity,
mainActivity.getResources().getString(R.string.not_allowed));
return;
}
HybridFile hFile = new HybridFile(OpenMode.UNKNOWN, currentPath);
hFile.generateMode(mainActivity);
DocumentFile targetDocumentFile =
getDocumentFile(
targetFile, false, mainActivity.getApplicationContext());
switch (hFile.getMode()) {
case FILE:
case ROOT:
File targetFile = new File(finalFilePath);
if (!FileUtil.isWritableNormalOrSaf(
targetFile.getParentFile(), mainActivity.getApplicationContext())) {
AppConfig.toast(
// Fallback, in case getDocumentFile() didn't properly return a
// DocumentFile
// instance
if (targetDocumentFile == null)
targetDocumentFile = DocumentFile.fromFile(targetFile);
// Lazy check... and in fact, different apps may pass in URI in different
// formats, so we could only check filename matches
// FIXME?: Prompt overwrite instead of simply blocking
if (targetDocumentFile.exists() && targetDocumentFile.length() > 0) {
AppConfig.toast(
mainActivity, mainActivity.getString(R.string.cannot_overwrite));
return;
}
bufferedOutputStream =
new BufferedOutputStream(
contentResolver.openOutputStream(targetDocumentFile.getUri()));
retval.add(targetFile.getPath());
break;
case SMB:
SmbFile targetSmbFile = new SmbFile(finalFilePath);
if (targetSmbFile.exists()) {
AppConfig.toast(
mainActivity, mainActivity.getString(R.string.cannot_overwrite));
emitter.onError(new Exception());
} else {
OutputStream outputStream = targetSmbFile.getOutputStream();
bufferedOutputStream = new BufferedOutputStream(outputStream);
retval.add(HybridFile.parseSmbPath(targetSmbFile.getPath()));
}
break;
case SFTP:
// FIXME: implement support
AppConfig.toast(
mainActivity, mainActivity.getString(R.string.not_allowed));
emitter.onError(new Exception());
case DROPBOX:
CloudStorage cloudStorageDropbox = dataUtils.getAccount(OpenMode.DROPBOX);
String path = CloudUtil.stripPath(OpenMode.DROPBOX, finalFilePath);
cloudStorageDropbox.upload(
path, bufferedInputStream, documentFile.length(), true);
retval.add(path);
break;
case BOX:
CloudStorage cloudStorageBox = dataUtils.getAccount(OpenMode.BOX);
path = CloudUtil.stripPath(OpenMode.BOX, finalFilePath);
cloudStorageBox.upload(
path, bufferedInputStream, documentFile.length(), true);
retval.add(path);
break;
case ONEDRIVE:
CloudStorage cloudStorageOneDrive =
dataUtils.getAccount(OpenMode.ONEDRIVE);
path = CloudUtil.stripPath(OpenMode.ONEDRIVE, finalFilePath);
cloudStorageOneDrive.upload(
path, bufferedInputStream, documentFile.length(), true);
retval.add(path);
break;
case GDRIVE:
CloudStorage cloudStorageGDrive = dataUtils.getAccount(OpenMode.GDRIVE);
path = CloudUtil.stripPath(OpenMode.GDRIVE, finalFilePath);
cloudStorageGDrive.upload(
path, bufferedInputStream, documentFile.length(), true);
retval.add(path);
break;
case OTG:
DocumentFile documentTargetFile =
OTGUtil.getDocumentFile(finalFilePath, mainActivity, true);
if (documentTargetFile.exists()) {
AppConfig.toast(
mainActivity, mainActivity.getString(R.string.cannot_overwrite));
return;
}
bufferedOutputStream =
new BufferedOutputStream(
contentResolver.openOutputStream(documentTargetFile.getUri()),
GenericCopyUtil.DEFAULT_BUFFER_SIZE);
retval.add(documentTargetFile.getUri().getPath());
break;
default:
return;
}
int count = 0;
byte[] buffer = new byte[GenericCopyUtil.DEFAULT_BUFFER_SIZE];
while (count != -1) {
count = bufferedInputStream.read(buffer);
if (count != -1) {
bufferedOutputStream.write(buffer, 0, count);
}
}
bufferedOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
try {
if (bufferedInputStream != null) {
bufferedInputStream.close();
}
if (bufferedOutputStream != null) {
bufferedOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (retval.size() > 0) {
emitter.onSuccess(retval);
} else {
emitter.onError(new Exception());
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new MaybeObserver<List<String>>() {
@Override
public void onSubscribe(Disposable d) {}
@Override
public void onSuccess(List<String> paths) {
if (paths.size() == 1) {
Toast.makeText(
mainActivity,
mainActivity.getResources().getString(R.string.not_allowed));
return null;
}
DocumentFile targetDocumentFile =
getDocumentFile(targetFile, false, mainActivity.getApplicationContext());
// Fallback, in case getDocumentFile() didn't properly return a DocumentFile
// instance
if (targetDocumentFile == null)
targetDocumentFile = DocumentFile.fromFile(targetFile);
// Lazy check... and in fact, different apps may pass in URI in different
// formats, so we could only check filename matches
// FIXME?: Prompt overwrite instead of simply blocking
if (targetDocumentFile.exists() && targetDocumentFile.length() > 0) {
AppConfig.toast(
mainActivity, mainActivity.getString(R.string.cannot_overwrite));
return null;
}
bufferedOutputStream =
new BufferedOutputStream(
contentResolver.openOutputStream(targetDocumentFile.getUri()));
retval.add(targetFile.getPath());
break;
case SMB:
SmbFile targetSmbFile = new SmbFile(finalFilePath);
if (targetSmbFile.exists()) {
AppConfig.toast(
mainActivity, mainActivity.getString(R.string.cannot_overwrite));
return null;
} else {
OutputStream outputStream = targetSmbFile.getOutputStream();
bufferedOutputStream = new BufferedOutputStream(outputStream);
retval.add(HybridFile.parseSmbPath(targetSmbFile.getPath()));
}
break;
case SFTP:
// FIXME: implement support
AppConfig.toast(mainActivity, mainActivity.getString(R.string.not_allowed));
return null;
case DROPBOX:
CloudStorage cloudStorageDropbox = dataUtils.getAccount(OpenMode.DROPBOX);
String path = CloudUtil.stripPath(OpenMode.DROPBOX, finalFilePath);
cloudStorageDropbox.upload(
path, bufferedInputStream, documentFile.length(), true);
retval.add(path);
break;
case BOX:
CloudStorage cloudStorageBox = dataUtils.getAccount(OpenMode.BOX);
path = CloudUtil.stripPath(OpenMode.BOX, finalFilePath);
cloudStorageBox.upload(path, bufferedInputStream, documentFile.length(), true);
retval.add(path);
break;
case ONEDRIVE:
CloudStorage cloudStorageOneDrive = dataUtils.getAccount(OpenMode.ONEDRIVE);
path = CloudUtil.stripPath(OpenMode.ONEDRIVE, finalFilePath);
cloudStorageOneDrive.upload(
path, bufferedInputStream, documentFile.length(), true);
retval.add(path);
break;
case GDRIVE:
CloudStorage cloudStorageGDrive = dataUtils.getAccount(OpenMode.GDRIVE);
path = CloudUtil.stripPath(OpenMode.GDRIVE, finalFilePath);
cloudStorageGDrive.upload(
path, bufferedInputStream, documentFile.length(), true);
retval.add(path);
break;
case OTG:
DocumentFile documentTargetFile =
OTGUtil.getDocumentFile(finalFilePath, mainActivity, true);
if (documentTargetFile.exists()) {
AppConfig.toast(
mainActivity, mainActivity.getString(R.string.cannot_overwrite));
return null;
}
bufferedOutputStream =
new BufferedOutputStream(
contentResolver.openOutputStream(documentTargetFile.getUri()),
GenericCopyUtil.DEFAULT_BUFFER_SIZE);
retval.add(documentTargetFile.getUri().getPath());
break;
default:
return null;
}
int count = 0;
byte[] buffer = new byte[GenericCopyUtil.DEFAULT_BUFFER_SIZE];
while (count != -1) {
count = bufferedInputStream.read(buffer);
if (count != -1) {
bufferedOutputStream.write(buffer, 0, count);
}
}
bufferedOutputStream.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bufferedInputStream != null) {
bufferedInputStream.close();
}
if (bufferedOutputStream != null) {
bufferedOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
mainActivity.getString(R.string.saved_single_file, paths.get(0)),
Toast.LENGTH_LONG)
.show();
} else {
Toast.makeText(
mainActivity,
mainActivity.getString(R.string.saved_multi_files, paths.size()),
Toast.LENGTH_LONG)
.show();
}
}
}
return (retval.size() > 0) ? retval : null;
}
@Override
public void onPostExecute(List<String> result) {
if (result != null) {
List<String> paths = (List<String>) result;
if (paths.size() == 1) {
Toast.makeText(
mainActivity,
mainActivity.getString(R.string.saved_single_file, paths.get(0)),
Toast.LENGTH_LONG)
.show();
} else {
Toast.makeText(
mainActivity,
mainActivity.getString(R.string.saved_multi_files, paths.size()),
Toast.LENGTH_LONG)
.show();
@Override
public void onError(Throwable e) {
Log.e(
getClass().getSimpleName(),
"Failed to write uri to storage due to " + e.getCause());
e.printStackTrace();
}
}
}
});
@Override
public void onComplete() {}
});
}
/**
@@ -60,6 +60,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import jcifs.smb.SmbException;
import jcifs.smb.SmbFile;
import net.schmizz.sshj.SSHClient;
@@ -438,7 +440,6 @@ public class HybridFile {
}
public boolean isDirectory(Context context) {
boolean isDirectory;
switch (mode) {
case SFTP:
@@ -452,19 +453,24 @@ public class HybridFile {
.getType()
.equals(FileMode.Type.DIRECTORY);
} catch (SFTPException notFound) {
Log.e(
getClass().getSimpleName(),
"Fail to execute isDirectory for SFTP path :" + path);
notFound.printStackTrace();
return false;
}
}
});
case SMB:
try {
isDirectory = new SmbFile(path).isDirectory();
} catch (SmbException e) {
isDirectory =
Single.fromCallable(() -> new SmbFile(path).isDirectory())
.subscribeOn(Schedulers.io())
.blockingGet();
} catch (Exception e) {
isDirectory = false;
e.printStackTrace();
} catch (MalformedURLException e) {
isDirectory = false;
e.printStackTrace();
if (e.getCause() != null) e.getCause().printStackTrace();
else e.printStackTrace();
}
break;
case FILE:
@@ -36,6 +36,13 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.Html;
import android.text.Spanned;
import android.util.Log;
import io.reactivex.Single;
import io.reactivex.SingleObserver;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
/**
* Special immutable class for handling cut/copy operations.
@@ -129,26 +136,47 @@ public final class PasteHelper implements Parcelable {
}
private void showSnackbar() {
snackbar =
Utils.showThemedSnackbar(
mainActivity,
getSnackbarContent(),
BaseTransientBottomBar.LENGTH_INDEFINITE,
R.string.paste,
() -> {
String path = mainActivity.getCurrentMainFragment().getCurrentPath();
ArrayList<HybridFileParcelable> arrayList = new ArrayList<>(Arrays.asList(paths));
boolean move = operation == PasteHelper.OPERATION_CUT;
new PrepareCopyTask(
mainActivity.getCurrentMainFragment(),
path,
move,
mainActivity,
mainActivity.isRootExplorer())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, arrayList);
dismissSnackbar(true);
Single.fromCallable(() -> getSnackbarContent())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new SingleObserver<Spanned>() {
@Override
public void onSubscribe(Disposable d) {}
@Override
public void onSuccess(Spanned spanned) {
snackbar =
Utils.showThemedSnackbar(
mainActivity,
spanned,
BaseTransientBottomBar.LENGTH_INDEFINITE,
R.string.paste,
() -> {
String path = mainActivity.getCurrentMainFragment().getCurrentPath();
ArrayList<HybridFileParcelable> arrayList =
new ArrayList<>(Arrays.asList(paths));
boolean move = operation == PasteHelper.OPERATION_CUT;
new PrepareCopyTask(
mainActivity.getCurrentMainFragment(),
path,
move,
mainActivity,
mainActivity.isRootExplorer())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, arrayList);
dismissSnackbar(true);
});
Utils.invalidateFab(mainActivity, () -> dismissSnackbar(true), true);
}
@Override
public void onError(Throwable e) {
Log.e(
getClass().getSimpleName(),
"Failed to show paste snackbar due to " + e.getCause());
e.printStackTrace();
}
});
Utils.invalidateFab(mainActivity, () -> dismissSnackbar(true), true);
}
private Spanned getSnackbarContent() {
@@ -27,6 +27,7 @@ import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.List;
import java.util.concurrent.Callable;
import com.amaze.filemanager.R;
import com.amaze.filemanager.application.AppConfig;
@@ -47,6 +48,8 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.sftp.FileAttributes;
@@ -55,7 +58,8 @@ import net.schmizz.sshj.sftp.RemoteResourceInfo;
import net.schmizz.sshj.sftp.SFTPClient;
public abstract class SshClientUtils {
private static final String TAG = "SshClientUtils";
private static final String TAG = SshClientUtils.class.getSimpleName();
/**
* Execute the given SshClientTemplate.
@@ -68,17 +72,20 @@ public abstract class SshClientUtils {
* @return Template execution results
*/
public static final <T> T execute(@NonNull SshClientTemplate template) {
SSHClient client = null;
SSHClient client = SshConnectionPool.getInstance().getConnection(template.url);
T retval = null;
try {
client = SshConnectionPool.getInstance().getConnection(template.url);
if (client != null) retval = template.execute(client);
else throw new RuntimeException("Unable to execute template");
} catch (Exception e) {
Log.e(TAG, "Error executing template method", e);
} finally {
if (client != null && template.closeClientOnFinish) {
tryDisconnect(client);
if (client != null) {
try {
retval =
Single.fromCallable((Callable<T>) () -> template.execute(client))
.subscribeOn(Schedulers.io())
.blockingGet();
} catch (Exception e) {
Log.e(TAG, "Error executing template method", e);
} finally {
if (template.closeClientOnFinish) {
tryDisconnect(client);
}
}
}
return retval;
@@ -58,12 +58,14 @@ public class SshConnectionPool {
private static final String TAG = SshConnectionPool.class.getSimpleName();
private static SshConnectionPool instance = null;
private final Map<String, SSHClient> connections;
private SshConnectionPool() {
connections = new ConcurrentHashMap<String, SSHClient>();
connections = new ConcurrentHashMap<>();
}
private static final class SshConnectionPoolHolder {
private static final SshConnectionPool instance = new SshConnectionPool();
}
/**
@@ -72,9 +74,7 @@ public class SshConnectionPool {
* @return {@link SshConnectionPool} instance
*/
public static final SshConnectionPool getInstance() {
if (instance == null) instance = new SshConnectionPool();
return instance;
return SshConnectionPoolHolder.instance;
}
/**
@@ -172,16 +172,17 @@ public class SshConnectionPool {
* @see MainActivity#onDestroy()
* @see MainActivity#exit()
*/
public void expungeAllConnections() {
AppConfig.runInBackground(
() -> {
if (!connections.isEmpty()) {
for (SSHClient connection : connections.values()) {
SshClientUtils.tryDisconnect(connection);
}
connections.clear();
}
});
public void shutdown() {
AppConfig.getInstance()
.runInBackground(
() -> {
if (!connections.isEmpty()) {
for (SSHClient connection : connections.values()) {
SshClientUtils.tryDisconnect(connection);
}
connections.clear();
}
});
}
// Logic for creating SSH connection. Depends on password existence in given Uri password or
@@ -153,6 +153,11 @@ import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import eu.chainfire.libsuperuser.Shell;
import io.reactivex.Completable;
import io.reactivex.CompletableObserver;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class MainActivity extends PermissionsActivity
implements SmbConnectionListener,
@@ -305,7 +310,7 @@ public class MainActivity extends PermissionsActivity
if (CloudSheetFragment.isCloudProviderAvailable(this)) {
getSupportLoaderManager().initLoader(REQUEST_CODE_CLOUD_LIST_KEYS, null, this);
LoaderManager.getInstance(this).initLoader(REQUEST_CODE_CLOUD_LIST_KEYS, null, this);
}
path = getIntent().getStringExtra("path");
@@ -365,79 +370,87 @@ public class MainActivity extends PermissionsActivity
checkForExternalPermission();
AppConfig.runInParallel(
new AppConfig.CustomAsyncCallbacks<Void, Void>(null) {
@Override
public Void doInBackground() {
Completable.fromRunnable(
() -> {
dataUtils.setHiddenFiles(utilsHandler.getHiddenFilesConcurrentRadixTree());
dataUtils.setHistory(utilsHandler.getHistoryLinkedList());
dataUtils.setGridfiles(utilsHandler.getGridViewList());
dataUtils.setListfiles(utilsHandler.getListViewList());
dataUtils.setBooks(utilsHandler.getBookmarksList());
ArrayList<String[]> servers = new ArrayList<>();
servers.addAll(utilsHandler.getSmbList());
servers.addAll(utilsHandler.getSftpList());
dataUtils.setServers(servers);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {}
dataUtils.setHiddenFiles(utilsHandler.getHiddenFilesConcurrentRadixTree());
dataUtils.setHistory(utilsHandler.getHistoryLinkedList());
dataUtils.setGridfiles(utilsHandler.getGridViewList());
dataUtils.setListfiles(utilsHandler.getListViewList());
dataUtils.setBooks(utilsHandler.getBookmarksList());
ArrayList<String[]> servers = new ArrayList<String[]>();
servers.addAll(utilsHandler.getSmbList());
servers.addAll(utilsHandler.getSftpList());
dataUtils.setServers(servers);
return null;
}
@Override
public void onPostExecute(Void result) {
drawer.refreshDrawer();
if (savedInstanceState == null) {
if (openProcesses) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(
R.id.content_frame, new ProcessViewerFragment(), KEY_INTENT_PROCESS_VIEWER);
// transaction.addToBackStack(null);
openProcesses = false;
// title.setText(utils.getString(con, R.string.process_viewer));
// Commit the transaction
transaction.commit();
supportInvalidateOptionsMenu();
} else if (intent.getAction() != null
&& intent.getAction().equals(TileService.ACTION_QS_TILE_PREFERENCES)) {
// tile preferences, open ftp fragment
FragmentTransaction transaction2 = getSupportFragmentManager().beginTransaction();
transaction2.replace(R.id.content_frame, new FtpServerFragment());
appBarLayout
.animate()
.translationY(0)
.setInterpolator(new DecelerateInterpolator(2))
.start();
drawer.deselectEverything();
transaction2.commit();
} else {
if (path != null && path.length() > 0) {
HybridFile file = new HybridFile(OpenMode.UNKNOWN, path);
file.generateMode(MainActivity.this);
if (file.isDirectory(MainActivity.this)) goToMain(path);
else {
goToMain(null);
FileUtils.openFile(new File(path), MainActivity.this, getPrefs());
}
} else {
goToMain(null);
}
@Override
public void onComplete() {
drawer.refreshDrawer();
invalidateFragmentAndBundle(savedInstanceState);
}
} else {
pasteHelper = savedInstanceState.getParcelable(PASTEHELPER_BUNDLE);
oppathe = savedInstanceState.getString(KEY_OPERATION_PATH);
oppathe1 = savedInstanceState.getString(KEY_OPERATED_ON_PATH);
oparrayList = savedInstanceState.getParcelableArrayList(KEY_OPERATIONS_PATH_LIST);
operation = savedInstanceState.getInt(KEY_OPERATION);
// mainFragment = (Main) savedInstanceState.getParcelable("main_fragment");
int selectedStorage = savedInstanceState.getInt(KEY_DRAWER_SELECTED, 0);
getDrawer().selectCorrectDrawerItem(selectedStorage);
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
});
}
private void invalidateFragmentAndBundle(Bundle savedInstanceState) {
if (savedInstanceState == null) {
if (openProcesses) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(
R.id.content_frame, new ProcessViewerFragment(), KEY_INTENT_PROCESS_VIEWER);
// transaction.addToBackStack(null);
openProcesses = false;
// title.setText(utils.getString(con, R.string.process_viewer));
// Commit the transaction
transaction.commit();
supportInvalidateOptionsMenu();
} else if (intent.getAction() != null
&& intent.getAction().equals(TileService.ACTION_QS_TILE_PREFERENCES)) {
// tile preferences, open ftp fragment
FragmentTransaction transaction2 = getSupportFragmentManager().beginTransaction();
transaction2.replace(R.id.content_frame, new FtpServerFragment());
appBarLayout
.animate()
.translationY(0)
.setInterpolator(new DecelerateInterpolator(2))
.start();
drawer.deselectEverything();
transaction2.commit();
} else {
if (path != null && path.length() > 0) {
HybridFile file = new HybridFile(OpenMode.UNKNOWN, path);
file.generateMode(MainActivity.this);
if (file.isDirectory(MainActivity.this)) goToMain(path);
else {
goToMain(null);
FileUtils.openFile(new File(path), MainActivity.this, getPrefs());
}
});
} else {
goToMain(null);
}
}
} else {
pasteHelper = savedInstanceState.getParcelable(PASTEHELPER_BUNDLE);
oppathe = savedInstanceState.getString(KEY_OPERATION_PATH);
oppathe1 = savedInstanceState.getString(KEY_OPERATED_ON_PATH);
oparrayList = savedInstanceState.getParcelableArrayList(KEY_OPERATIONS_PATH_LIST);
operation = savedInstanceState.getInt(KEY_OPERATION);
// mainFragment = (Main) savedInstanceState.getParcelable("main_fragment");
int selectedStorage = savedInstanceState.getInt(KEY_DRAWER_SELECTED, 0);
getDrawer().selectCorrectDrawerItem(selectedStorage);
}
}
private void checkForExternalPermission() {
@@ -603,10 +616,11 @@ public class MainActivity extends PermissionsActivity
data.append(AppConstants.NEW_LINE).append(extras.getString(Intent.EXTRA_TEXT));
}
String fileName = Long.toString(System.currentTimeMillis());
AppConfig.runInBackground(
() ->
FileUtil.mktextfile(
data.toString(), getCurrentMainFragment().getCurrentPath(), fileName));
AppConfig.getInstance()
.runInBackground(
() ->
FileUtil.mktextfile(
data.toString(), getCurrentMainFragment().getCurrentPath(), fileName));
}
public void clearFabActionItems() {
@@ -864,7 +878,7 @@ public class MainActivity extends PermissionsActivity
public void exit() {
if (backPressedToExitOnce) {
SshConnectionPool.getInstance().expungeAllConnections();
SshConnectionPool.getInstance().shutdown();
finish();
if (isRootExplorer()) {
// TODO close all shells
@@ -1122,12 +1136,13 @@ public class MainActivity extends PermissionsActivity
int pathLayout = dataUtils.getListOrGridForPath(ma.getCurrentPath(), DataUtils.LIST);
if (ma.IS_LIST) {
if (pathLayout == DataUtils.LIST) {
AppConfig.runInBackground(
() -> {
utilsHandler.removeFromDatabase(
new OperationData(
UtilsHandler.Operation.LIST, mainFragment.getCurrentPath()));
});
AppConfig.getInstance()
.runInBackground(
() -> {
utilsHandler.removeFromDatabase(
new OperationData(
UtilsHandler.Operation.LIST, mainFragment.getCurrentPath()));
});
}
utilsHandler.saveToDatabase(
new OperationData(UtilsHandler.Operation.GRID, mainFragment.getCurrentPath()));
@@ -1135,12 +1150,13 @@ public class MainActivity extends PermissionsActivity
dataUtils.setPathAsGridOrList(ma.getCurrentPath(), DataUtils.GRID);
} else {
if (pathLayout == DataUtils.GRID) {
AppConfig.runInBackground(
() -> {
utilsHandler.removeFromDatabase(
new OperationData(
UtilsHandler.Operation.GRID, mainFragment.getCurrentPath()));
});
AppConfig.getInstance()
.runInBackground(
() -> {
utilsHandler.removeFromDatabase(
new OperationData(
UtilsHandler.Operation.GRID, mainFragment.getCurrentPath()));
});
}
utilsHandler.saveToDatabase(
@@ -1323,7 +1339,7 @@ public class MainActivity extends PermissionsActivity
// TODO: 6/5/2017 Android may choose to not call this method before destruction
// TODO: https://developer.android.com/reference/android/app/Activity.html#onDestroy%28%29
closeInteractiveShell();
SshConnectionPool.getInstance().expungeAllConnections();
SshConnectionPool.getInstance().shutdown();
}
/** Closes the interactive shell and threads associated */
@@ -1836,10 +1852,11 @@ public class MainActivity extends PermissionsActivity
if (i != -1) {
dataUtils.removeServer(i);
AppConfig.runInBackground(
() -> {
utilsHandler.renameSMB(oldname, oldPath, name, path);
});
AppConfig.getInstance()
.runInBackground(
() -> {
utilsHandler.renameSMB(oldname, oldPath, name, path);
});
// mainActivity.grid.removePath(oldname, oldPath, DataUtils.SMB);
}
dataUtils.addServer(s);
@@ -1856,11 +1873,12 @@ public class MainActivity extends PermissionsActivity
if (i != -1) {
dataUtils.removeServer(i);
AppConfig.runInBackground(
() -> {
utilsHandler.removeFromDatabase(
new OperationData(UtilsHandler.Operation.SMB, name, path));
});
AppConfig.getInstance()
.runInBackground(
() -> {
utilsHandler.removeFromDatabase(
new OperationData(UtilsHandler.Operation.SMB, name, path));
});
// grid.removePath(name, path, DataUtils.SMB);
drawer.refreshDrawer();
}
@@ -114,7 +114,6 @@ public class RenameBookmark extends DialogFragment {
if (studiomode != 0) {
if (path.startsWith("smb:/")) {
try {
jcifs.Config.registerSmbURLHandler();
URL a = new URL(path);
String userinfo = a.getUserInfo();
if (userinfo != null) {
@@ -309,17 +309,18 @@ public class SftpConnectDialog extends DialogFragment {
if (i != -1) {
DataUtils.getInstance().removeServer(i);
AppConfig.runInBackground(
() -> {
utilsHandler.removeFromDatabase(
new OperationData(
UtilsHandler.Operation.SFTP,
path,
connectionName,
null,
null,
null));
});
AppConfig.getInstance()
.runInBackground(
() -> {
utilsHandler.removeFromDatabase(
new OperationData(
UtilsHandler.Operation.SFTP,
path,
connectionName,
null,
null,
null));
});
((MainActivity) getActivity()).getDrawer().refreshDrawer();
}
dialog.dismiss();
@@ -476,16 +477,17 @@ public class SftpConnectDialog extends DialogFragment {
Collections.sort(DataUtils.getInstance().getServers(), new BookSorter());
((MainActivity) getActivity()).getDrawer().refreshDrawer();
AppConfig.runInBackground(
() -> {
utilsHandler.updateSsh(
connectionName,
getArguments().getString("name"),
encryptedPath,
hostKeyFingerprint,
selectedParsedKeyPairName,
getPemContents());
});
AppConfig.getInstance()
.runInBackground(
() -> {
utilsHandler.updateSsh(
connectionName,
getArguments().getString("name"),
encryptedPath,
hostKeyFingerprint,
selectedParsedKeyPairName,
getPemContents());
});
dismiss();
return true;
@@ -227,13 +227,14 @@ public class FoldersPref extends PreferenceFragment
new String[] {editText1.getText().toString(), editText2.getText().toString()};
dataUtils.addBook(values);
AppConfig.runInBackground(
() ->
utilsHandler.renameBookmark(
oldName,
oldPath,
editText1.getText().toString(),
editText2.getText().toString()));
AppConfig.getInstance()
.runInBackground(
() ->
utilsHandler.renameBookmark(
oldName,
oldPath,
editText1.getText().toString(),
editText2.getText().toString()));
dialog.dismiss();
});
@@ -395,7 +395,7 @@ public class DataUtils {
public void clearHistory() {
history.clear();
if (dataChangeListener != null) {
AppConfig.runInBackground(() -> dataChangeListener.onHistoryCleared());
AppConfig.getInstance().runInBackground(() -> dataChangeListener.onHistoryCleared());
}
}
@@ -0,0 +1,96 @@
/*
* Copyright (C) 2014-2020 Arpit Khurana <arpitkh96@gmail.com>, Vishal Nehra <vishalmeham2@gmail.com>,
* Emmanuel Messulam<emmanuelbendavid@gmail.com>, Raymond Lai <airwave209gt at gmail.com> and Contributors.
*
* This file is part of Amaze File Manager.
*
* Amaze File Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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.amaze.filemanager.asynchronous.asynctasks;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.ArrayList;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import com.amaze.filemanager.filesystem.HybridFileParcelable;
import com.amaze.filemanager.filesystem.ssh.AbstractSftpServerTest;
import com.amaze.filemanager.shadows.ShadowMultiDex;
import com.amaze.filemanager.utils.OpenMode;
import android.os.Environment;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
@Config(shadows = {ShadowMultiDex.class})
public class MoveFilesTest extends AbstractSftpServerTest {
private File sshServerRoot;
@Before
@Override
public void setUp() throws IOException {
sshServerRoot = Environment.getExternalStoragePublicDirectory("sshserver-test");
serverPort =
createSshServer(
new VirtualFileSystemFactory(Paths.get(sshServerRoot.getAbsolutePath())), 64000);
prepareSshConnection();
}
@Test
@Ignore("Not yet for CI testing until pass")
public void testMoveFilesUsingSftp() throws Exception {
File sourceFile = new File(sshServerRoot, "testfile.bin");
SecureRandom rnd = new SecureRandom();
byte[] randomBytes = new byte[32];
rnd.nextBytes(randomBytes);
IOUtils.copy(new ByteArrayInputStream(randomBytes), new FileOutputStream(sourceFile));
ArrayList<HybridFileParcelable> filesToCopy = new ArrayList<>();
HybridFileParcelable file =
new HybridFileParcelable(
"ssh://testuser:testpassword@127.0.0.1:" + serverPort + "/testfile.bin");
file.generateMode(ApplicationProvider.getApplicationContext());
filesToCopy.add(file);
ArrayList<ArrayList<HybridFileParcelable>> filesToCopyPerFolder = new ArrayList<>();
filesToCopyPerFolder.add(filesToCopy);
MoveFiles task =
new MoveFiles(
filesToCopyPerFolder, null, ApplicationProvider.getApplicationContext(), OpenMode.FILE);
ArrayList<String> paths = new ArrayList<>();
paths.add(Environment.getExternalStorageDirectory().getAbsolutePath());
task.doInBackground(paths);
assertFalse(sourceFile.exists());
assertTrue(new File(Environment.getExternalStorageDirectory(), "testfile.bin").exists());
}
}
@@ -72,7 +72,7 @@ public abstract class AbstractSftpServerTest {
@After
public void tearDown() throws IOException {
SshConnectionPool.getInstance().expungeAllConnections();
SshConnectionPool.getInstance().shutdown();
if (server != null && server.isOpen()) {
server.stop(true);
}
@@ -42,13 +42,14 @@ import com.amaze.filemanager.database.models.OperationData;
import com.amaze.filemanager.filesystem.ssh.test.TestKeyProvider;
import com.amaze.filemanager.shadows.ShadowMultiDex;
import com.amaze.filemanager.test.ShadowCryptUtil;
import com.amaze.filemanager.test.TestUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
import net.schmizz.sshj.common.SecurityUtils;
@RunWith(AndroidJUnit4.class)
@@ -67,6 +68,8 @@ public class SshConnectionPoolTest {
public static void bootstrap() throws Exception {
hostKeyProvider = new TestKeyProvider();
userKeyProvider = new TestKeyProvider();
RxJavaPlugins.reset();
RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
}
@After
@@ -341,7 +344,5 @@ public class SshConnectionPoolTest {
SecurityUtils.getFingerprint(hostKeyProvider.getKeyPair().getPublic()),
"id_rsa",
privateKeyContents));
TestUtils.flushAppConfigHandlerThread();
}
}
@@ -20,11 +20,14 @@
package com.amaze.filemanager.test;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.concurrent.TimeUnit;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
@@ -39,10 +42,19 @@ import com.amaze.filemanager.shadows.ShadowMultiDex;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
@RunWith(AndroidJUnit4.class)
@Config(shadows = {ShadowMultiDex.class, ShadowCryptUtil.class})
public class ShadowCryptUtilTest {
@BeforeClass
public static void setUpBeforeClass() {
RxJavaPlugins.reset();
RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
}
@Test
public void testEncryptDecrypt() throws GeneralSecurityException, IOException {
String text = "test";
@@ -71,9 +83,13 @@ public class ShadowCryptUtilTest {
null,
null));
TestUtils.flushAppConfigHandlerThread();
assertEquals(fingerprint, utilsHandler.getSshHostKey(url));
utilitiesDatabase.close();
await()
.atMost(10, TimeUnit.SECONDS)
.until(
() -> {
assertEquals(fingerprint, utilsHandler.getSshHostKey(url));
utilitiesDatabase.close();
return true;
});
}
}
@@ -24,16 +24,11 @@ import static android.os.Build.VERSION_CODES.P;
import static org.robolectric.Shadows.shadowOf;
import java.io.File;
import java.lang.reflect.Field;
import org.robolectric.shadows.ShadowStorageManager;
import org.robolectric.util.Scheduler;
import com.amaze.filemanager.application.AppConfig;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Parcel;
import android.os.UserHandle;
import android.os.storage.StorageManager;
@@ -44,38 +39,6 @@ import androidx.test.core.app.ApplicationProvider;
public class TestUtils {
/**
* Flush the background handler thread.
*
* <p>Due to the way Robolectric implement threads, Runnables posted to the handler thread via
* {@link AppConfig#runInBackground(Runnable)} may not be executed, making tests involving it will
* fail. This method accesses the {@link Scheduler} behind the {@link
* org.robolectric.shadows.ShadowLooper} of the background {@link Handler} and execute any {@link
* Runnable}s posted to it.
*
* <p>This method throws {@link AssertionError} directly so test code don't need to handle
* possible exceptions occur when accessing
*
* <pre>AppConfig.backgroundHandler</pre>
*
* via reflection.
*
* @see Scheduler#advanceToNextPostedRunnable()
* @see AppConfig#backgroundHandler
* @see AppConfig#runInBackground(Runnable)
*/
public static void flushAppConfigHandlerThread() {
try {
Field f = AppConfig.class.getDeclaredField("backgroundHandler");
f.setAccessible(true);
Handler h = (Handler) f.get(null);
Scheduler scheduler = shadowOf(h.getLooper()).getScheduler();
scheduler.advanceToLastPostedRunnable();
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new AssertionError("Unable to access backgroundHandler within AppConfig");
}
}
/**
* Populate "internal device storage" to StorageManager with directory as provided by Robolectric.
*
@@ -34,6 +34,7 @@ import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
@@ -56,6 +57,9 @@ import androidx.test.core.app.ActivityScenario;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {
@@ -72,6 +76,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
@LooperMode(LooperMode.Mode.PAUSED)
public class MainActivityTest {
@BeforeClass
public static void setUpBeforeClass() {
RxJavaPlugins.reset();
RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
}
@Before
public void setUp() {
if (Build.VERSION.SDK_INT >= N) TestUtils.initializeInternalStorage();