From 2fd73b148d012fba7308c86494689103b8aaace4 Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Fri, 29 Mar 2024 19:27:53 +0100 Subject: Move download service to module (#7041) --- net/download/service-interface/build.gradle | 3 + .../serviceinterface/DownloadRequestCreator.java | 122 +++++++++++++++++++++ .../serviceinterface/FileNameGenerator.java | 76 +++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequestCreator.java create mode 100644 net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/FileNameGenerator.java (limited to 'net/download/service-interface') diff --git a/net/download/service-interface/build.gradle b/net/download/service-interface/build.gradle index 84a8dfd05..a6ecd8c58 100644 --- a/net/download/service-interface/build.gradle +++ b/net/download/service-interface/build.gradle @@ -16,8 +16,11 @@ android { dependencies { implementation project(':model') implementation project(':net:common') + implementation project(':storage:preferences') annotationProcessor "androidx.annotation:annotation:$annotationVersion" + implementation "org.apache.commons:commons-lang3:$commonslangVersion" + implementation "commons-io:commons-io:$commonsioVersion" testImplementation "junit:junit:$junitVersion" testImplementation "org.robolectric:robolectric:$robolectricVersion" diff --git a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequestCreator.java b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequestCreator.java new file mode 100644 index 000000000..c0d70523c --- /dev/null +++ b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadRequestCreator.java @@ -0,0 +1,122 @@ +package de.danoeh.antennapod.net.download.serviceinterface; + +import android.util.Log; +import android.webkit.URLUtil; +import de.danoeh.antennapod.model.feed.Feed; +import de.danoeh.antennapod.model.feed.FeedMedia; +import de.danoeh.antennapod.storage.preferences.UserPreferences; +import org.apache.commons.io.FilenameUtils; + +import java.io.File; + +/** + * Creates download requests that can be sent to the DownloadService. + */ +public class DownloadRequestCreator { + private static final String TAG = "DownloadRequestCreat"; + private static final String FEED_DOWNLOADPATH = "cache/"; + private static final String MEDIA_DOWNLOADPATH = "media/"; + + public static DownloadRequestBuilder create(Feed feed) { + File dest = new File(getFeedfilePath(), getFeedfileName(feed)); + if (dest.exists()) { + boolean deleted = dest.delete(); + Log.d(TAG, "deleted" + dest.getPath() + ": " + deleted); + } + Log.d(TAG, "Requesting download of url " + feed.getDownloadUrl()); + + String username = (feed.getPreferences() != null) ? feed.getPreferences().getUsername() : null; + String password = (feed.getPreferences() != null) ? feed.getPreferences().getPassword() : null; + + return new DownloadRequestBuilder(dest.toString(), feed) + .withAuthentication(username, password) + .lastModified(feed.getLastModified()); + } + + public static DownloadRequestBuilder create(FeedMedia media) { + final boolean partiallyDownloadedFileExists = + media.getLocalFileUrl() != null && new File(media.getLocalFileUrl()).exists(); + File dest; + if (partiallyDownloadedFileExists) { + dest = new File(media.getLocalFileUrl()); + } else { + dest = new File(getMediafilePath(media), getMediafilename(media)); + } + + if (dest.exists() && !partiallyDownloadedFileExists) { + dest = findUnusedFile(dest); + } + Log.d(TAG, "Requesting download of url " + media.getDownloadUrl()); + + String username = (media.getItem().getFeed().getPreferences() != null) + ? media.getItem().getFeed().getPreferences().getUsername() : null; + String password = (media.getItem().getFeed().getPreferences() != null) + ? media.getItem().getFeed().getPreferences().getPassword() : null; + + return new DownloadRequestBuilder(dest.toString(), media) + .withAuthentication(username, password); + } + + private static File findUnusedFile(File dest) { + // find different name + File newDest = null; + for (int i = 1; i < Integer.MAX_VALUE; i++) { + String newName = FilenameUtils.getBaseName(dest + .getName()) + + "-" + + i + + FilenameUtils.EXTENSION_SEPARATOR + + FilenameUtils.getExtension(dest.getName()); + Log.d(TAG, "Testing filename " + newName); + newDest = new File(dest.getParent(), newName); + if (!newDest.exists()) { + Log.d(TAG, "File doesn't exist yet. Using " + newName); + break; + } + } + return newDest; + } + + private static String getFeedfilePath() { + return UserPreferences.getDataFolder(FEED_DOWNLOADPATH).toString() + "/"; + } + + private static String getFeedfileName(Feed feed) { + String filename = feed.getDownloadUrl(); + if (feed.getTitle() != null && !feed.getTitle().isEmpty()) { + filename = feed.getTitle(); + } + return "feed-" + FileNameGenerator.generateFileName(filename) + feed.getId(); + } + + private static String getMediafilePath(FeedMedia media) { + String mediaPath = MEDIA_DOWNLOADPATH + + FileNameGenerator.generateFileName(media.getItem().getFeed().getTitle()); + return UserPreferences.getDataFolder(mediaPath).toString() + "/"; + } + + private static String getMediafilename(FeedMedia media) { + String titleBaseFilename = ""; + + // Try to generate the filename by the item title + if (media.getItem() != null && media.getItem().getTitle() != null) { + String title = media.getItem().getTitle(); + titleBaseFilename = FileNameGenerator.generateFileName(title); + } + + String urlBaseFilename = URLUtil.guessFileName(media.getDownloadUrl(), null, media.getMimeType()); + + String baseFilename; + if (!titleBaseFilename.equals("")) { + baseFilename = titleBaseFilename; + } else { + baseFilename = urlBaseFilename; + } + final int filenameMaxLength = 220; + if (baseFilename.length() > filenameMaxLength) { + baseFilename = baseFilename.substring(0, filenameMaxLength); + } + return baseFilename + FilenameUtils.EXTENSION_SEPARATOR + media.getId() + + FilenameUtils.EXTENSION_SEPARATOR + FilenameUtils.getExtension(urlBaseFilename); + } +} diff --git a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/FileNameGenerator.java b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/FileNameGenerator.java new file mode 100644 index 000000000..85e73836f --- /dev/null +++ b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/FileNameGenerator.java @@ -0,0 +1,76 @@ +package de.danoeh.antennapod.net.download.serviceinterface; + +import android.text.TextUtils; + +import androidx.annotation.VisibleForTesting; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; + + +/** Generates valid filenames for a given string. */ +public class FileNameGenerator { + @VisibleForTesting + public static final int MAX_FILENAME_LENGTH = 242; // limited by CircleCI + private static final int MD5_HEX_LENGTH = 32; + + private static final char[] validChars = + ("abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "0123456789" + + " _-").toCharArray(); + + private FileNameGenerator() { + } + + /** + * This method will return a new string that doesn't contain any illegal + * characters of the given string. + */ + public static String generateFileName(String string) { + string = StringUtils.stripAccents(string); + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < string.length(); i++) { + char c = string.charAt(i); + if (Character.isSpaceChar(c) + && (buf.length() == 0 || Character.isSpaceChar(buf.charAt(buf.length() - 1)))) { + continue; + } + if (ArrayUtils.contains(validChars, c)) { + buf.append(c); + } + } + String filename = buf.toString().trim(); + if (TextUtils.isEmpty(filename)) { + return randomString(8); + } else if (filename.length() >= MAX_FILENAME_LENGTH) { + return filename.substring(0, MAX_FILENAME_LENGTH - MD5_HEX_LENGTH - 1) + "_" + md5(filename); + } else { + return filename; + } + } + + private static String randomString(int length) { + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + sb.append(validChars[(int) (Math.random() * validChars.length)]); + } + return sb.toString(); + } + + private static String md5(String md5) { + try { + java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5"); + byte[] array = md.digest(md5.getBytes("UTF-8")); + StringBuilder sb = new StringBuilder(); + for (byte b : array) { + sb.append(Integer.toHexString((b & 0xFF) | 0x100).substring(1, 3)); + } + return sb.toString(); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + return null; + } + } +} -- cgit v1.2.3