Skip to content

Commit

Permalink
Implement temporary file downloading and moving - and add 'READ_EXTER…
Browse files Browse the repository at this point in the history
…NAL_STORAGE' right
  • Loading branch information
gzsombor committed Oct 10, 2021
1 parent 65315fa commit 2f3e933
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 18 deletions.
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package="free.rm.skytube">

<uses-permission android:name="android.permission.INTERNET" /> <!-- Mandatory permission -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <!-- [Optional] Used for database backups and to download videos/thumbnails -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- [Optional] Used for database backups and to download videos/thumbnails -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <!-- [Optional] Only used by SkyTube Extra flavor to update the app -->
<!-- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@
import android.net.Uri;
import android.os.Environment;
import android.webkit.MimeTypeMap;
import android.widget.Toast;

import androidx.core.content.ContextCompat;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.nio.channels.FileChannel;
import java.util.regex.Pattern;

import free.rm.skytube.app.SkyTubeApp;
Expand All @@ -44,7 +49,7 @@
public abstract class FileDownloader implements Serializable, PermissionsActivity.PermissionsTask {

/** The remote file URL that is going to be downloaded. */
private String remoteFileUrl = null;
private String remoteFileUrl = null;
/** The directory type: e.g. Environment.DIRECTORY_MOVIES or Environment.DIRECTORY_PICTURES */
private String dirType = null;
/** The title that will be displayed by the Android's download manager. */
Expand All @@ -64,6 +69,7 @@ public abstract class FileDownloader implements Serializable, PermissionsActivit

private long downloadId;
private transient BroadcastReceiver onComplete;
private FileInformation fileInformation;

private Pattern invalidCharacters = Pattern.compile("[^\\w\\d]+");

Expand Down Expand Up @@ -100,14 +106,14 @@ private void download() {
return;
}

Uri remoteFileUri = Uri.parse(remoteFileUrl);
String downloadFileName = getCompleteFileName(remoteFileUri);
FileInformation fileInformation = new FileInformation(downloadFileName, parentDirectory, dirType);
Uri remoteFileUri = Uri.parse(remoteFileUrl);
String downloadFileName = getCompleteFileName();
fileInformation = new FileInformation(downloadFileName, outputDirectoryName, parentDirectory, dirType);
String fullDownloadFileName = fileInformation.getFullDownloadFileName();
final File downloadDestinationFile = fileInformation.getFile();


Logger.w(this, "Downloading video %s into %s -> %s", outputFileName, outputDirectoryName, downloadDestinationFile.getAbsolutePath());
Logger.w(this, "Downloading video %s into %s -> %s (temp: %s)", outputFileName, outputDirectoryName, downloadDestinationFile.getAbsolutePath(), fileInformation.getTemporaryFile());
if (downloadDestinationFile.exists()) {
onFileDownloadCompleted(true, Uri.parse(downloadDestinationFile.toURI().toString()));
return;
Expand All @@ -119,12 +125,7 @@ private void download() {
.setDescription(description)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);

String videoDir = SkyTubeApp.getSettings().getDownloadFolder(null);
if(videoDir != null) {
request.setDestinationUri(Uri.fromFile(new File(videoDir, fullDownloadFileName)));
} else {
request.setDestinationInExternalPublicDir(dirType, fullDownloadFileName);
}
request.setDestinationUri(Uri.fromFile(fileInformation.getDownloadDestinationFile()));

if (!allowedOverRoaming) {
request.setAllowedNetworkTypes(allowedNetworkTypesFlags);
Expand Down Expand Up @@ -163,7 +164,7 @@ public void onReceive(Context context, Intent intent) {

// check the referenceId for this download
if (referenceId == downloadId) {
fileDownloadStatus();
fileDownloadStatus(context);
}
}
}
Expand All @@ -185,16 +186,16 @@ private boolean isExternalStorageAvailable() {
/**
* Concatenates the outputFileName together with the appropriate file extension.
*/
private String getCompleteFileName(Uri remoteFileUri) {
String fileExt = (outputFileExtension != null) ? outputFileExtension : MimeTypeMap.getFileExtensionFromUrl(remoteFileUri.toString());
private String getCompleteFileName() {
String fileExt = (outputFileExtension != null) ? outputFileExtension : MimeTypeMap.getFileExtensionFromUrl(remoteFileUrl);
return outputFileName + "." + fileExt;
}


/**
* Notify the user whether the download was successful or not.
*/
private void fileDownloadStatus() {
private void fileDownloadStatus(Context context) {
boolean downloadSuccessful = false;
Uri downloadedFileUri = null;
Cursor cursor = ContextCompat.getSystemService(getContext(), DownloadManager.class)
Expand All @@ -212,6 +213,7 @@ private void fileDownloadStatus() {

// output why the download has failed...
Logger.e(this, "Download failed. Reason=%s", reason);
Toast.makeText(context, "Download failed, reasone: " + reason, Toast.LENGTH_LONG).show();
}

cursor.close();
Expand All @@ -220,9 +222,51 @@ private void fileDownloadStatus() {
getContext().unregisterReceiver(onComplete);

// file download is now completed
onFileDownloadCompleted(downloadSuccessful, downloadedFileUri);
if (downloadSuccessful && fileInformation.getTemporaryFile() != null) {
try {
Logger.i(this, "Downloaded into temporary file %s, moving to %s", fileInformation.getTemporaryFile().getCanonicalPath(), fileInformation.getFile().getCanonicalPath());
} catch (IOException e) {
Logger.e(this, "Error: "+e.getMessage(), e);
}
try {
move(fileInformation.getTemporaryFile(), fileInformation.getFile());
onFileDownloadCompleted(downloadSuccessful, Uri.fromFile(fileInformation.getFile()));
} catch(IOException e) {
Logger.e(this, "Failed to move file from "+ fileInformation.getTemporaryFile()+ " -> "+fileInformation.getFile() + " : " + e.getMessage(),e);
onFileDownloadCompleted(downloadSuccessful, downloadedFileUri);
}
} else {
onFileDownloadCompleted(downloadSuccessful, downloadedFileUri);
}
}

private void move(File from, File destination) throws IOException{
final File parentDir = destination.getParentFile();
if (!parentDir.exists()) {
Logger.e(this, "Directory doesn't exists, try to create: "+ parentDir);
if (parentDir.mkdirs()) {
Logger.e(this, "Destination now exists:"+ parentDir.exists());
} else {
Logger.e(this, "Failed to create directory:"+ parentDir +" exists:"+ parentDir.exists());
}
} else {
Logger.e(this, "directory exists "+ parentDir.getCanonicalPath());
}
if (!from.renameTo(destination)) {
FileChannel outputChannel = null;
FileChannel inputChannel = null;
try {
outputChannel = new FileOutputStream(destination).getChannel();
inputChannel = new FileInputStream(from).getChannel();
inputChannel.transferTo(0, inputChannel.size(), outputChannel);
inputChannel.close();
from.delete();
} finally {
if (inputChannel != null) inputChannel.close();
if (outputChannel != null) outputChannel.close();
}
}
}

public FileDownloader setRemoteFileUrl(String remoteFileUrl) {
this.remoteFileUrl = remoteFileUrl;
Expand Down Expand Up @@ -257,6 +301,9 @@ public FileDownloader setDescription(String description) {
public FileDownloader setOutputFileName(String outputFileName) {
// replace all the special characters with space.
this.outputFileName = invalidCharacters.matcher(outputFileName).replaceAll(" ").trim();
if (this.outputFileName.length() > 50) {
this.outputFileName = this.outputFileName.substring(0,49).trim();
}
return this;
}

Expand Down Expand Up @@ -333,11 +380,12 @@ public FileDownloader setAllowedNetworkTypesFlags(Integer allowedNetworkTypesFla
*/
public abstract void onExternalStorageNotAvailable();

private class FileInformation {
private static class FileInformation implements Serializable {
private final String fullDownloadFileName;
private final File file;
private final File temporaryFile;

public FileInformation(String downloadFileName,
public FileInformation(String downloadFileName, String outputDirectoryName,
File parentDirectory, String dirType) {
// if there's already a local file for this video for some reason, then do not redownload the
// file and halt
Expand All @@ -354,6 +402,12 @@ public FileInformation(String downloadFileName,
} else {
fullDownloadFileName = downloadFileName;
}
boolean useTempDir = SkyTubeApp.getSettings().isDownloadToTemporaryFolder();
if (useTempDir && !externalStorageDir.equals(parentDir)) {
temporaryFile = new File(externalStorageDir, downloadFileName);
} else {
temporaryFile = null;
}
file = new File(parentDir, downloadFileName);
}

Expand All @@ -364,5 +418,13 @@ public String getFullDownloadFileName() {
public File getFile() {
return file;
}

public File getTemporaryFile() {
return temporaryFile;
}

public File getDownloadDestinationFile() {
return temporaryFile != null ? temporaryFile : file;
}
}
}

0 comments on commit 2f3e933

Please sign in to comment.