Skip to content

Commit 5eae41d

Browse files
authored
v1.3.1 (#81)
* Layout improvements * Recreate view after theme changes * Option to export config file * Add Firebase Crashlytics * Firebase Cloud Msg for app updates notifications * Add option to delete remotes * Add option to delete remotes * Layout improvements * Recreate view after theme changes * Option to export config file * Add Firebase Crashlytics * Firebase Cloud Msg for app updates notifications * Add option to delete remotes * Upload only one file at a time * Download one file at a time * Move/delete each file individually View is refreshed after each file is moved or deleted * Option to empty trash for supported remotes * Update app version
1 parent fe2e944 commit 5eae41d

14 files changed

+232
-269
lines changed

.idea/caches/build_file_checksums.ser

0 Bytes
Binary file not shown.

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ Grab the [latest version](https://github.com/kaczmarkiewiczp/rcloneExplorer/rele
4444

4545
Credits/Libraries
4646
-----------------
47+
- [Android Support Libraries](https://developer.android.com/topic/libraries/support-library)
4748
- [Floating Action Button SpeedDial](https://github.com/leinardi/FloatingActionButtonSpeedDial) - A Floating Action Button Speed Dial implementation for Android that follows the Material Design specification.
49+
- [Glide](https://github.com/bumptech/glide) - An image loading and caching library for Android focused on smooth scrolling.
4850
- [Markdown View](https://github.com/falnatsheh/MarkdownView) - MarkdownView is an Android webview with the capablity of loading Markdown text or file and display it as HTML, it uses MarkdownJ and extends Android webview.
4951
- [Material Design Icons](https://github.com/Templarian/MaterialDesign) - 2200+ Material Design Icons from the Community.
5052
- [rclone](https://github.com/ncw/rclone) - "rsync for cloud storage"

app/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ android {
77
applicationId "ca.pkay.rcloneexplorer"
88
minSdkVersion 21
99
targetSdkVersion 27
10-
versionCode 16
11-
versionName "1.3.0"
10+
versionCode 17
11+
versionName "1.3.1"
1212
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
1313
}
1414
buildTypes {

app/src/main/assets/changelog.md

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
### 1.3.1
2+
* **New:** Option to delete remotes
3+
* **New:** Option to export rclone config file
4+
* **New:** Option to empty trash for remotes that support it
5+
* Encrypted remotes (crypt) are not supported
6+
* **New:** Firebase Crashlytics
7+
* Any app crashes will be reported to help fix them faster
8+
* No identifiable information is sent, only line of code on which the crash occurred
9+
* **New:** Push notifications when new app version is available on GitHub (can be disabled in settings)
10+
* **Fix:** Recreate view after theme changes
11+
* **Update:** Upload only one file at a time (fix any possible bottlenecks)
12+
* **Update:** Download only one file at a time (fix any possible bottlenecks)
13+
* **Update:** Move/delete operations - view is updated after each file is moved or deleted
14+
15+
***
16+
117
### 1.3.0
218
* **New:** Remote creation - ability to create new remotes right from the app!
319
* Most of the rclone remotes are here

app/src/main/java/ca/pkay/rcloneexplorer/AboutLibsActivity.java

+6
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ private void createData() {
9393
libraryLicences.put(floatingActionButtonSpeedDial, "Licensed under Apache-2.0");
9494
libraryLicenceUrls.put(floatingActionButtonSpeedDial, "https://github.com/leinardi/FloatingActionButtonSpeedDial/blob/master/LICENSE");
9595

96+
String glide = "Glide";
97+
libraryNames.add(glide);
98+
libraryUrls.put(glide, "https://github.com/bumptech/glide");
99+
libraryLicences.put(glide, "BSD, part MIT and Apache 2.0");
100+
libraryLicenceUrls.put(glide, "https://github.com/bumptech/glide/blob/master/LICENSE");
101+
96102
String markDownView = "MarkDown View";
97103
libraryNames.add(markDownView);
98104
libraryUrls.put(markDownView, "https://github.com/falnatsheh/MarkdownView");

app/src/main/java/ca/pkay/rcloneexplorer/Fragments/FileExplorerFragment.java

+61-24
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import ca.pkay.rcloneexplorer.FilePicker;
6161
import ca.pkay.rcloneexplorer.Items.DirectoryObject;
6262
import ca.pkay.rcloneexplorer.Items.FileItem;
63+
import ca.pkay.rcloneexplorer.Items.RemoteItem;
6364
import ca.pkay.rcloneexplorer.MainActivity;
6465
import ca.pkay.rcloneexplorer.Dialogs.OpenAsDialog;
6566
import ca.pkay.rcloneexplorer.R;
@@ -345,20 +346,26 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
345346
for (File file : result) {
346347
uploadList.add(file.getPath());
347348
}
348-
Intent intent = new Intent(getContext(), UploadService.class);
349-
intent.putStringArrayListExtra(UploadService.LOCAL_PATH_ARG, uploadList);
350-
intent.putExtra(UploadService.UPLOAD_PATH_ARG, directoryObject.getCurrentPath());
351-
intent.putExtra(UploadService.REMOTE_ARG, remote);
352-
context.startService(intent);
349+
350+
for (String uploadFile : uploadList) {
351+
Intent intent = new Intent(getContext(), UploadService.class);
352+
intent.putExtra(UploadService.LOCAL_PATH_ARG, uploadFile);
353+
intent.putExtra(UploadService.UPLOAD_PATH_ARG, directoryObject.getCurrentPath());
354+
intent.putExtra(UploadService.REMOTE_ARG, remote);
355+
context.startService(intent);
356+
}
353357
} else if (requestCode == FILE_PICKER_DOWNLOAD_RESULT && resultCode == FragmentActivity.RESULT_OK) {
354358
String selectedPath = data.getStringExtra(FilePicker.FILE_PICKER_RESULT);
355359
final ArrayList<FileItem> downloadList = new ArrayList<>(recyclerViewAdapter.getSelectedItems());
356360
recyclerViewAdapter.cancelSelection();
357-
Intent intent = new Intent(getContext(), DownloadService.class);
358-
intent.putParcelableArrayListExtra(DownloadService.DOWNLOAD_LIST_ARG, downloadList);
359-
intent.putExtra(DownloadService.DOWNLOAD_PATH_ARG, selectedPath);
360-
intent.putExtra(DownloadService.REMOTE_ARG, remote);
361-
context.startService(intent);
361+
362+
for (FileItem downloadItem : downloadList) {
363+
Intent intent = new Intent(getContext(), DownloadService.class);
364+
intent.putExtra(DownloadService.DOWNLOAD_ITEM_ARG, downloadItem);
365+
intent.putExtra(DownloadService.DOWNLOAD_PATH_ARG, selectedPath);
366+
intent.putExtra(DownloadService.REMOTE_ARG, remote);
367+
context.startService(intent);
368+
}
362369
} else if (requestCode == STREAMING_INTENT_RESULT) {
363370
Intent serveIntent = new Intent(getContext(), StreamingService.class);
364371
context.stopService(serveIntent);
@@ -372,6 +379,10 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
372379
menuPropertiesAction = menu.findItem(R.id.action_file_properties);
373380
menuOpenAsAction = menu.findItem(R.id.action_open_as);
374381
menuSelectAll = menu.findItem(R.id.action_select_all);
382+
383+
if (!RemoteItem.hasTrashCan(remoteType)) {
384+
menu.findItem(R.id.action_empty_trash).setVisible(false);
385+
}
375386
}
376387

377388
@Override
@@ -401,6 +412,9 @@ public boolean onOptionsItemSelected(MenuItem item) {
401412
case R.id.action_open_as:
402413
showOpenAsDialog();
403414
return true;
415+
case R.id.action_empty_trash:
416+
new EmptyTrashTash().execute();
417+
return true;
404418
default:
405419
return super.onOptionsItemSelected(item);
406420
}
@@ -640,13 +654,15 @@ private void moveLocationSelected() {
640654
} else {
641655
path2 = "//" + remote;
642656
}
643-
Intent intent = new Intent(context, BackgroundService.class);
644-
intent.putExtra(BackgroundService.TASK_TYPE, BackgroundService.TASK_TYPE_MOVE);
645-
intent.putExtra(BackgroundService.REMOTE_ARG, remote);
646-
intent.putExtra(BackgroundService.MOVE_DEST_PATH, directoryObject.getCurrentPath());
647-
intent.putExtra(BackgroundService.MOVE_LIST, moveList);
648-
intent.putExtra(BackgroundService.PATH2, path2);
649-
context.startService(intent);
657+
for (FileItem moveItem : moveList) {
658+
Intent intent = new Intent(context, BackgroundService.class);
659+
intent.putExtra(BackgroundService.TASK_TYPE, BackgroundService.TASK_TYPE_MOVE);
660+
intent.putExtra(BackgroundService.REMOTE_ARG, remote);
661+
intent.putExtra(BackgroundService.MOVE_DEST_PATH, directoryObject.getCurrentPath());
662+
intent.putExtra(BackgroundService.MOVE_ITEM, moveItem);
663+
intent.putExtra(BackgroundService.PATH2, path2);
664+
context.startService(intent);
665+
}
650666
Toasty.info(context, getString(R.string.moving_info), Toast.LENGTH_SHORT, true).show();
651667
moveList.clear();
652668
unlockOrientation();
@@ -1003,12 +1019,14 @@ private void deleteClicked() {
10031019
@Override
10041020
public void onClick(DialogInterface dialog, int which) {
10051021
recyclerViewAdapter.cancelSelection();
1006-
Intent intent = new Intent(context, BackgroundService.class);
1007-
intent.putExtra(BackgroundService.TASK_TYPE, BackgroundService.TASK_TYPE_DELETE);
1008-
intent.putExtra(BackgroundService.REMOTE_ARG, remote);
1009-
intent.putExtra(BackgroundService.DELETE_LIST, deleteList);
1010-
intent.putExtra(BackgroundService.PATH, directoryObject.getCurrentPath());
1011-
context.startService(intent);
1022+
for (FileItem deleteItem : deleteList) {
1023+
Intent intent = new Intent(context, BackgroundService.class);
1024+
intent.putExtra(BackgroundService.TASK_TYPE, BackgroundService.TASK_TYPE_DELETE);
1025+
intent.putExtra(BackgroundService.REMOTE_ARG, remote);
1026+
intent.putExtra(BackgroundService.DELETE_ITEM, deleteItem);
1027+
intent.putExtra(BackgroundService.PATH, directoryObject.getCurrentPath());
1028+
context.startService(intent);
1029+
}
10121030
Toasty.info(context, getString(R.string.deleting_info), Toast.LENGTH_SHORT, true).show();
10131031
}
10141032
});
@@ -1336,7 +1354,7 @@ protected Boolean doInBackground(FileItem... fileItems) {
13361354

13371355
fileLocation = saveLocation + "/" + fileItem.getName();
13381356

1339-
process = rclone.downloadItems(remote, fileItem, saveLocation);
1357+
process = rclone.downloadFile(remote, fileItem, saveLocation);
13401358

13411359
try {
13421360
process.waitFor();
@@ -1479,4 +1497,23 @@ protected Void doInBackground(FileItem... fileItems) {
14791497
return null;
14801498
}
14811499
}
1500+
1501+
@SuppressLint("StaticFieldLeak")
1502+
private class EmptyTrashTash extends AsyncTask<Void, Void, Boolean> {
1503+
1504+
@Override
1505+
protected Boolean doInBackground(Void... voids) {
1506+
return rclone.emptyTrashCan(remote);
1507+
}
1508+
1509+
@Override
1510+
protected void onPostExecute(Boolean result) {
1511+
super.onPostExecute(result);
1512+
if (result) {
1513+
Toasty.success(context, getString(R.string.trash_emptied), Toast.LENGTH_SHORT, true).show();
1514+
} else {
1515+
Toasty.error(context, getString(R.string.error_emptying_trash), Toast.LENGTH_SHORT, true).show();
1516+
}
1517+
}
1518+
}
14821519
}

app/src/main/java/ca/pkay/rcloneexplorer/Items/RemoteItem.java

+11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ public RemoteItem(String name, String type) {
1212
this.type = type;
1313
}
1414

15+
public static boolean hasTrashCan(String remoteType) {
16+
switch (remoteType) {
17+
case "drive":
18+
case "pcloud":
19+
case "yandex":
20+
return true;
21+
default:
22+
return false;
23+
}
24+
}
25+
1526
public String getName() {
1627
return name;
1728
}

app/src/main/java/ca/pkay/rcloneexplorer/Rclone.java

+56-77
Original file line numberDiff line numberDiff line change
@@ -228,33 +228,7 @@ public Process serveHttp(String remote, String servePath) {
228228
}
229229
}
230230

231-
public List<Process> downloadItems(String remote, List<FileItem> downloadList, String downloadPath) {
232-
List<Process> runningProcesses = new ArrayList<>();
233-
Process process;
234-
String[] command;
235-
String remoteFilePath;
236-
String localFilePath;
237-
238-
for (FileItem item : downloadList) {
239-
remoteFilePath = remote + ":" + item.getPath();
240-
if (item.isDir()) {
241-
localFilePath = downloadPath + "/" + item.getName();
242-
} else {
243-
localFilePath = downloadPath;
244-
}
245-
command = createCommand("copy", remoteFilePath, localFilePath);
246-
247-
try {
248-
process = Runtime.getRuntime().exec(command);
249-
runningProcesses.add(process);
250-
} catch (IOException e) {
251-
e.printStackTrace();
252-
}
253-
}
254-
return runningProcesses;
255-
}
256-
257-
public Process downloadItems(String remote, FileItem downloadItem, String downloadPath) {
231+
public Process downloadFile(String remote, FileItem downloadItem, String downloadPath) {
258232
String[] command;
259233
String remoteFilePath;
260234
String localFilePath;
@@ -275,58 +249,51 @@ public Process downloadItems(String remote, FileItem downloadItem, String downlo
275249
}
276250
}
277251

278-
public List<Process> uploadFiles(String remote, String uploadPath, ArrayList<String> uploadList) {
279-
List<Process> runningProcesses = new ArrayList<>();
280-
Process process;
252+
public Process uploadFile(String remote, String uploadPath, String uploadFile) {
281253
String path;
282254
String[] command;
283255

284-
for (String localPath : uploadList) {
285-
File file = new File(localPath);
286-
if (file.isDirectory()) {
287-
int index = localPath.lastIndexOf('/');
288-
String dirName = localPath.substring(index + 1);
289-
path = (uploadPath.compareTo("//" + remote) == 0) ? remote + ":" + dirName: remote + ":" + uploadPath + "/" + dirName;
290-
} else {
291-
path = (uploadPath.compareTo("//" + remote) == 0) ? remote + ":" : remote + ":" + uploadPath;
292-
}
256+
File file = new File(uploadFile);
257+
if (file.isDirectory()) {
258+
int index = uploadFile.lastIndexOf('/');
259+
String dirName = uploadFile.substring(index + 1);
260+
path = (uploadPath.compareTo("//" + remote) == 0) ? remote + ":" + dirName: remote + ":" + uploadPath + "/" + dirName;
261+
} else {
262+
path = (uploadPath.compareTo("//" + remote) == 0) ? remote + ":" : remote + ":" + uploadPath;
263+
}
293264

294-
command = createCommand("copy", localPath, path);
265+
command = createCommand("copy", uploadFile, path);
295266

296-
try {
297-
process = Runtime.getRuntime().exec(command);
298-
runningProcesses.add(process);
299-
} catch (IOException e) {
300-
e.printStackTrace();
301-
}
267+
try {
268+
return Runtime.getRuntime().exec(command);
269+
} catch (IOException e) {
270+
e.printStackTrace();
271+
return null;
302272
}
303273

304-
return runningProcesses;
305274
}
306275

307-
public Boolean deleteItems(String remote, List<FileItem> deleteList) {
276+
public Boolean deleteItems(String remote, FileItem deleteItem) {
308277
String[] command;
309278
String filePath;
310279
Boolean result = true;
311280

312-
for (FileItem item : deleteList) {
313-
filePath = remote + ":" + item.getPath();
314-
if (item.isDir()) {
315-
command = createCommand("purge", filePath);
316-
} else {
317-
command = createCommand("delete", filePath);
318-
}
281+
filePath = remote + ":" + deleteItem.getPath();
282+
if (deleteItem.isDir()) {
283+
command = createCommand("purge", filePath);
284+
} else {
285+
command = createCommand("delete", filePath);
286+
}
319287

320-
try {
321-
Process process = Runtime.getRuntime().exec(command);
322-
process.waitFor();
323-
if (process.exitValue() != 0) {
324-
result = false;
325-
}
326-
} catch (IOException | InterruptedException e) {
327-
e.printStackTrace();
288+
try {
289+
Process process = Runtime.getRuntime().exec(command);
290+
process.waitFor();
291+
if (process.exitValue() != 0) {
328292
result = false;
329293
}
294+
} catch (IOException | InterruptedException e) {
295+
e.printStackTrace();
296+
result = false;
330297
}
331298
return result;
332299
}
@@ -348,27 +315,25 @@ public Boolean makeDirectory(String remote, String path) {
348315
return true;
349316
}
350317

351-
public Boolean moveTo(String remote, List<FileItem> moveList, String newLocation) {
318+
public Boolean moveTo(String remote, FileItem moveItem, String newLocation) {
352319
String[] command;
353320
String oldFilePath;
354321
String newFilePath;
355322
Boolean result = true;
356323

357-
for (FileItem fileItem : moveList) {
358-
oldFilePath = remote + ":" + fileItem.getPath();
359-
newFilePath = (newLocation.compareTo("//" + remote) == 0) ? remote + ":" + fileItem.getName() : remote + ":" + newLocation + "/" + fileItem.getName();
360-
command = createCommand("moveto", oldFilePath, newFilePath);
361-
try {
362-
Process process = Runtime.getRuntime().exec(command);
363-
process.waitFor();
364-
if (process.exitValue() != 0) {
365-
logErrorOutput(process);
366-
result = false;
367-
}
368-
} catch (IOException | InterruptedException e) {
369-
e.printStackTrace();
324+
oldFilePath = remote + ":" + moveItem.getPath();
325+
newFilePath = (newLocation.compareTo("//" + remote) == 0) ? remote + ":" + moveItem.getName() : remote + ":" + newLocation + "/" + moveItem.getName();
326+
command = createCommand("moveto", oldFilePath, newFilePath);
327+
try {
328+
Process process = Runtime.getRuntime().exec(command);
329+
process.waitFor();
330+
if (process.exitValue() != 0) {
331+
logErrorOutput(process);
370332
result = false;
371333
}
334+
} catch (IOException | InterruptedException e) {
335+
e.printStackTrace();
336+
result = false;
372337
}
373338
return result;
374339
}
@@ -391,6 +356,20 @@ public Boolean moveTo(String remote, String oldFile, String newFile) {
391356
return true;
392357
}
393358

359+
public boolean emptyTrashCan(String remote) {
360+
String[] command = createCommand("cleanup", remote + ":");
361+
Process process = null;
362+
363+
try {
364+
process = Runtime.getRuntime().exec(command);
365+
process.waitFor();
366+
} catch (IOException | InterruptedException e) {
367+
e.printStackTrace();
368+
}
369+
370+
return process != null && process.exitValue() == 0;
371+
}
372+
394373
public String calculateMD5(String remote, FileItem fileItem) {
395374
String remoteAndPath = remote + ":" + fileItem.getName();
396375
String[] command = createCommand("md5sum", remoteAndPath);

0 commit comments

Comments
 (0)