From 141ffc8e4871861eab53b7eeead85f89692ebbec Mon Sep 17 00:00:00 2001 From: Saptarshi Sarkar Date: Fri, 20 Sep 2024 15:04:44 +0530 Subject: [PATCH] fix: Fixed CoderabbitAI's reviews and github-advanced-security bot's warnings --- CLI/src/main/java/backend/FileDownloader.java | 46 +-- .../main/java/backend/ProgressBarThread.java | 6 +- CLI/src/main/java/main/Drifty_CLI.java | 60 ++-- Core/src/main/java/preferences/Get.java | 10 +- Core/src/main/java/preferences/Set.java | 16 +- Core/src/main/java/support/Constants.java | 14 +- .../java/support/DownloadConfiguration.java | 263 ++++++++++-------- Core/src/main/java/support/Job.java | 11 +- Core/src/main/java/utils/Logger.java | 10 +- Core/src/main/java/utils/Utility.java | 5 +- GUI/src/main/java/backend/FileDownloader.java | 10 +- .../gui/support/GUIDownloadConfiguration.java | 16 +- .../main/java/ui/FileMetadataRetriever.java | 12 +- GUI/src/main/java/ui/UIController.java | 4 +- 14 files changed, 264 insertions(+), 219 deletions(-) diff --git a/CLI/src/main/java/backend/FileDownloader.java b/CLI/src/main/java/backend/FileDownloader.java index 129c691f..b7dc82c8 100644 --- a/CLI/src/main/java/backend/FileDownloader.java +++ b/CLI/src/main/java/backend/FileDownloader.java @@ -65,7 +65,6 @@ private void downloadFile() { File file; for (int i = 0; i < numberOfThreads; i++) { file = Files.createTempFile(fileName.hashCode() + String.valueOf(i), ".tmp").toFile(); - file.deleteOnExit(); // Deletes a temporary file when JVM exits fileOut = new FileOutputStream(file); start = i == 0 ? 0 : ((i * partSize) + 1); // The start of the range of bytes to be downloaded by the thread end = (numberOfThreads - 1) == i ? totalSize : ((i * partSize) + partSize); // The end of the range of bytes to be downloaded by the thread @@ -83,6 +82,9 @@ private void downloadFile() { while (!mergeDownloadedFileParts(fileOutputStreams, partSizes, downloaderThreads, tempFiles)) { sleep(500); } + for (File tempFile : tempFiles) { + Files.deleteIfExists(tempFile.toPath()); + } } else { InputStream urlStream = url.openStream(); readableByteChannel = Channels.newChannel(urlStream); @@ -98,9 +100,9 @@ private void downloadFile() { // keep the main thread from closing the IO for a short amount of time so the UI thread can finish and give output Utility.sleep(1800); } catch (SecurityException e) { - M.msgDownloadError("Write access to \"" + directoryPath.resolve(fileName).toAbsolutePath() + "\" is denied! " + e.getMessage()); + M.msgDownloadError("Write access to the download directory is DENIED! " + e.getMessage()); } catch (FileNotFoundException fileNotFoundException) { - M.msgDownloadError(FILE_NOT_FOUND); + M.msgDownloadError(FILE_NOT_FOUND_ERROR); } catch (IOException e) { M.msgDownloadError(FAILED_TO_DOWNLOAD_CONTENTS + e.getMessage()); } @@ -115,27 +117,27 @@ private void downloadYoutubeOrInstagram(boolean isSpotifySong) { processBuilder.inheritIO(); M.msgDownloadInfo(String.format(DOWNLOADING_F, fileName)); Process process; - int exitCode = 1; + int exitValueOfYtDlp = 1; try { process = processBuilder.start(); process.waitFor(); - exitCode = process.exitValue(); + exitValueOfYtDlp = process.exitValue(); } catch (IOException e) { M.msgDownloadError("Failed to start download process for \"" + fileName + "\""); } catch (Exception e) { String msg = e.getMessage(); String[] messageArray = msg.split(","); if (messageArray.length >= 1 && messageArray[0].toLowerCase().trim().replaceAll(" ", "").contains("cannotrunprogram")) { // If yt-dlp program is not marked as executable - M.msgDownloadError(DRIFTY_COMPONENT_NOT_EXECUTABLE); + M.msgDownloadError(DRIFTY_COMPONENT_NOT_EXECUTABLE_ERROR); } else if (messageArray.length >= 1 && "permissiondenied".equals(messageArray[1].toLowerCase().trim().replaceAll(" ", ""))) { // If a private YouTube / Instagram video is asked to be downloaded - M.msgDownloadError(PERMISSION_DENIED); + M.msgDownloadError(PERMISSION_DENIED_ERROR); } else if ("videounavailable".equals(messageArray[0].toLowerCase().trim().replaceAll(" ", ""))) { // If YouTube / Instagram video is unavailable - M.msgDownloadError(VIDEO_UNAVAILABLE); + M.msgDownloadError(VIDEO_UNAVAILABLE_ERROR); } else { M.msgDownloadError("An Unknown Error occurred! " + e.getMessage()); } } - if (exitCode == 0) { + if (exitValueOfYtDlp == 0) { M.msgDownloadInfo(String.format(SUCCESSFULLY_DOWNLOADED_F, fileName)); if (isSpotifySong) { M.msgDownloadInfo("Converting to mp3 ..."); @@ -146,10 +148,10 @@ private void downloadYoutubeOrInstagram(boolean isSpotifySong) { M.msgDownloadInfo("Successfully converted to mp3!"); } } - } else if (exitCode == 1) { + } else if (exitValueOfYtDlp == 1) { M.msgDownloadError(String.format(FAILED_TO_DOWNLOAD_F, fileName)); } else { - M.msgDownloadError("An Unknown Error occurred! Exit code: " + exitCode); + M.msgDownloadError("An Unknown Error occurred! Exit code: " + exitValueOfYtDlp); } } @@ -173,18 +175,18 @@ public boolean mergeDownloadedFileParts(List fileOutputStreams } // check if it is merged-able if (completed == numberOfThreads) { - fileOutputStream = new FileOutputStream(directoryPath.resolve(fileName).toFile()); - long position = 0; - for (int i = 0; i < numberOfThreads; i++) { - File f = tempFiles.get(i); - FileInputStream fs = new FileInputStream(f); - ReadableByteChannel rbs = Channels.newChannel(fs); - fileOutputStream.getChannel().transferFrom(rbs, position, f.length()); - position += f.length(); - fs.close(); - rbs.close(); + try (FileOutputStream fos = new FileOutputStream(directoryPath.resolve(fileName).toFile())) { + long position = 0; + for (int i = 0; i < numberOfThreads; i++) { + File f = tempFiles.get(i); + FileInputStream fs = new FileInputStream(f); + ReadableByteChannel rbs = Channels.newChannel(fs); + fos.getChannel().transferFrom(rbs, position, f.length()); + position += f.length(); + fs.close(); + rbs.close(); + } } - fileOutputStream.close(); return true; } return false; diff --git a/CLI/src/main/java/backend/ProgressBarThread.java b/CLI/src/main/java/backend/ProgressBarThread.java index a4fb1e62..0ed84e95 100644 --- a/CLI/src/main/java/backend/ProgressBarThread.java +++ b/CLI/src/main/java/backend/ProgressBarThread.java @@ -155,11 +155,11 @@ private void cleanup() { if (isMultiThreadedDownloading) { String sizeWithUnit = UnitConverter.format(totalDownloadedBytes, 2); System.out.print("\r"); - M.msgDownloadInfo(String.format(SUCCESSFULLY_DOWNLOADED_F, fileName) + OF_SIZE + sizeWithUnit + " at " + path); + M.msgDownloadInfo(String.format(SUCCESSFULLY_DOWNLOADED_F, fileName) + OF_SIZE + sizeWithUnit + " at \"" + path + "\""); } else if (downloadedBytes == totalDownloadedBytes) { String sizeWithUnit = UnitConverter.format(downloadedBytes, 2); System.out.print("\r"); - M.msgDownloadInfo(String.format(SUCCESSFULLY_DOWNLOADED_F, fileName) + OF_SIZE + sizeWithUnit + " at " + path); + M.msgDownloadInfo(String.format(SUCCESSFULLY_DOWNLOADED_F, fileName) + OF_SIZE + sizeWithUnit + " at \"" + path + "\""); } else { System.out.println(); M.msgDownloadError(DOWNLOAD_FAILED); @@ -211,7 +211,7 @@ public void run() { } } } catch (IOException e) { - M.msgDownloadError("Error while downloading " + fileName + " : " + e.getMessage()); + M.msgDownloadError("Error while downloading \"" + fileName + "\" : " + e.getMessage()); } finally { downloading = downloadMetrics.isActive(); } diff --git a/CLI/src/main/java/main/Drifty_CLI.java b/CLI/src/main/java/main/Drifty_CLI.java index e4707f46..75ed0e35 100644 --- a/CLI/src/main/java/main/Drifty_CLI.java +++ b/CLI/src/main/java/main/Drifty_CLI.java @@ -24,18 +24,20 @@ import java.net.URISyntaxException; import java.nio.file.*; import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import static cli.support.Constants.*; -import static cli.utils.Utility.*; +import static cli.utils.Utility.isURL; +import static cli.utils.Utility.sleep; public class Drifty_CLI { public static final Logger LOGGER = Logger.getInstance(); protected static final Scanner SC = ScannerFactory.getInstance(); protected static JobHistory jobHistory; private static LinkType linkType; - protected static boolean isYoutubeURL; - protected static boolean isInstagramLink; - protected static boolean isSpotifyLink; private static MessageBroker messageBroker; private static String link; private static String downloadsFolder; @@ -180,6 +182,10 @@ public static void main(String[] args) { } } if (!batchDownloading) { + if (link == null) { + messageBroker.msgInitError("No URL specified! Exiting..."); + Environment.terminate(1); + } boolean isUrlValid; if (Utility.isURL(link)) { isUrlValid = Utility.isLinkValid(link); @@ -189,11 +195,8 @@ public static void main(String[] args) { } if (isUrlValid) { linkType = LinkType.getLinkType(link); - isYoutubeURL = linkType.equals(LinkType.YOUTUBE); - isInstagramLink = linkType.equals(LinkType.INSTAGRAM); - isSpotifyLink = linkType.equals(LinkType.SPOTIFY); downloadsFolder = getProperDownloadsFolder(downloadsFolder); - if (isSpotifyLink && link.contains("playlist")) { + if (linkType.equals(LinkType.SPOTIFY) && link.contains("playlist")) { handleSpotifyPlaylist(); } else { verifyJobAndDownload(); @@ -243,15 +246,8 @@ public static void main(String[] args) { messageBroker.msgInputInfo("Download directory (\".\" for default or \"L\" for " + AppSettings.GET.lastDownloadFolder() + ") : ", false); downloadsFolder = SC.nextLine().split(" ")[0]; downloadsFolder = getProperDownloadsFolder(downloadsFolder); - isYoutubeURL = linkType.equals(LinkType.YOUTUBE); - isInstagramLink = linkType.equals(LinkType.INSTAGRAM); - isSpotifyLink = linkType.equals(LinkType.SPOTIFY); - if (isSpotifyLink) { - if (link.contains("playlist")) { - handleSpotifyPlaylist(); - } else { - verifyJobAndDownload(); - } + if (linkType.equals(LinkType.SPOTIFY) && link.contains("playlist")) { + handleSpotifyPlaylist(); } else { verifyJobAndDownload(); } @@ -387,10 +383,13 @@ private static void printVersion() { private static void handleSpotifyPlaylist() { messageBroker.msgFilenameInfo("Retrieving the number of tracks in the playlist..."); DownloadConfiguration config = new DownloadConfiguration(link, downloadsFolder, null); - Thread prepareFileData = new Thread(config::fetchFileData); - prepareFileData.start(); - while (prepareFileData.isAlive()) { - sleep(100); + try (ExecutorService executor = Executors.newSingleThreadExecutor()) { + Future future = executor.submit(config::fetchFileData); + future.get(); + } catch (ExecutionException e) { + messageBroker.msgLinkError("Failed to retrieve spotify playlist metadata! " + e.getMessage()); + } catch (InterruptedException e) { + messageBroker.msgLinkError("User interrupted the process of retrieving spotify playlist metadata! " + e.getMessage()); } ArrayList> playlistData = config.getFileData(); if (playlistData != null && !playlistData.isEmpty()) { @@ -472,9 +471,6 @@ private static void batchDownloader() { continue; } linkType = LinkType.getLinkType(link); - isYoutubeURL = linkType.equals(LinkType.YOUTUBE); - isInstagramLink = linkType.equals(LinkType.INSTAGRAM); - isSpotifyLink = linkType.equals(LinkType.SPOTIFY); if (".".equals(downloadsFolder)) { downloadsFolder = Utility.getHomeDownloadFolder(); } else if ("L".equalsIgnoreCase(downloadsFolder)) { @@ -489,7 +485,7 @@ private static void batchDownloader() { if (data.containsKey("fileNames") && !data.get("fileNames").get(i).isEmpty()) { fileName = data.get("fileNames").get(i); } - if (isSpotifyLink && link.contains("playlist")) { + if (linkType.equals(LinkType.SPOTIFY) && link.contains("playlist")) { handleSpotifyPlaylist(); } else { verifyJobAndDownload(); @@ -510,7 +506,7 @@ private static void batchDownloader() { } private static void renameFilenameIfRequired() { // Asks the user if the detected filename is to be used or not. If not, then the user is asked to enter a filename. - if ((fileName == null || (fileName.isEmpty())) && (!isYoutubeURL && !isInstagramLink && !isSpotifyLink)) { + if ((fileName == null || (fileName.isEmpty())) && linkType.equals(LinkType.OTHER)) { messageBroker.msgInputInfo(ENTER_FILE_NAME_WITH_EXTENSION, false); fileName = SC.nextLine(); } else { @@ -596,10 +592,13 @@ private static void verifyJobAndDownload() { DownloadConfiguration config = new DownloadConfiguration(link, downloadsFolder, fileName); config.sanitizeLink(); messageBroker.msgFilenameInfo("Retrieving file data..."); - Thread prepareFileData = new Thread(config::fetchFileData); - prepareFileData.start(); - while (prepareFileData.isAlive()) { - sleep(100); + try (ExecutorService executor = Executors.newSingleThreadExecutor()) { + Future future = executor.submit(config::fetchFileData); + future.get(); + } catch (ExecutionException e) { + messageBroker.msgLinkError("Failed to retrieve file metadata! " + e.getMessage()); + } catch (InterruptedException e) { + messageBroker.msgLinkError("User interrupted the process of retrieving file metadata! " + e.getMessage()); } if (config.getStatusCode() != 0) { messageBroker.msgLinkError("Failed to fetch file data!"); @@ -663,6 +662,7 @@ private static void checkHistoryAndDownload(Job job) { jobHistory.addJob(job, true); renameFilenameIfRequired(); if (link != null) { + job = new Job(link, downloadsFolder, fileName, job.getDownloadLink()); FileDownloader downloader = new FileDownloader(job); downloader.run(); } diff --git a/Core/src/main/java/preferences/Get.java b/Core/src/main/java/preferences/Get.java index eed33249..7c2f21a3 100644 --- a/Core/src/main/java/preferences/Get.java +++ b/Core/src/main/java/preferences/Get.java @@ -11,6 +11,7 @@ import utils.Utility; import javax.crypto.*; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Path; @@ -127,15 +128,16 @@ public boolean driftyUpdateAvailable() { public Jobs jobs() { GsonBuilder gsonBuilder = new GsonBuilder(); Gson gson = FxGson.addFxSupport(gsonBuilder).setPrettyPrinting().create(); - Jobs jobs; Path jobBatchFile = Paths.get(Program.get(JOB_FILE)); try { String json = FileUtils.readFileToString(jobBatchFile.toFile(), Charset.defaultCharset()); if (json != null && !json.isEmpty()) { - jobs = gson.fromJson(json, Jobs.class); - return jobs; + return gson.fromJson(json, Jobs.class); } - } catch (IOException ignored) { + } catch (FileNotFoundException e) { + Environment.getMessageBroker().msgLogInfo("Job file not found: " + jobBatchFile); + } catch (IOException e) { + Environment.getMessageBroker().msgLogError("Failed to read job file: " + jobBatchFile); } return new Jobs(); } diff --git a/Core/src/main/java/preferences/Set.java b/Core/src/main/java/preferences/Set.java index e9c43e05..71f4e213 100644 --- a/Core/src/main/java/preferences/Set.java +++ b/Core/src/main/java/preferences/Set.java @@ -128,15 +128,25 @@ public void driftyUpdateAvailable(boolean isUpdateAvailable) { } public void jobs(Jobs jobs) { + String serializedJobs = serializeJobs(jobs); + writeJobsToFile(serializedJobs); + } + + private String serializeJobs(Jobs jobs) { GsonBuilder gsonBuilder = new GsonBuilder(); Gson gson = FxGson.addFxSupport(gsonBuilder).setPrettyPrinting().create(); - String value = gson.toJson(jobs); + return gson.toJson(jobs); + } + + private void writeJobsToFile(String serializedJobs) { AppSettings.CLEAR.jobs(); Path jobBatchFile = Paths.get(Program.get(JOB_FILE)); try { - FileUtils.writeStringToFile(jobBatchFile.toFile(), value, Charset.defaultCharset()); + FileUtils.writeStringToFile(jobBatchFile.toFile(), serializedJobs, Charset.defaultCharset()); } catch (IOException e) { - throw new RuntimeException(e); + String errorMessage = "Failed to write jobs to file: " + e.getMessage(); + Environment.getMessageBroker().msgInitError(errorMessage); + throw new RuntimeException(errorMessage, e); } } } diff --git a/Core/src/main/java/support/Constants.java b/Core/src/main/java/support/Constants.java index b2a69a50..75a3fce3 100644 --- a/Core/src/main/java/support/Constants.java +++ b/Core/src/main/java/support/Constants.java @@ -14,15 +14,15 @@ public class Constants { public static final String INVALID_LINK = "Link is invalid! Please check the link and try again."; public static final String FILENAME_DETECTION_ERROR = "Failed to detect the filename! A default name will be used instead."; public static final String TRYING_TO_AUTO_DETECT_DOWNLOADS_FOLDER = "Trying to automatically detect default Downloads folder..."; - public static final String FAILED_TO_RETRIEVE_DEFAULT_DOWNLOAD_FOLDER = "Failed to retrieve default download folder!"; + public static final String FAILED_TO_RETRIEVE_DEFAULT_DOWNLOAD_FOLDER_ERROR = "Failed to retrieve default download folder!"; public static final String FOLDER_DETECTED = "Default download folder detected : "; public static final String FILENAME_DETECTED = "Filename detected : "; - public static final String FAILED_TO_CREATE_LOG = "Failed to create log : "; - public static final String FAILED_TO_CLEAR_LOG = "Failed to clear Log contents !"; - public static final String FILE_NOT_FOUND = "An error occurred! Requested file does not exist, please check the url."; - public static final String VIDEO_UNAVAILABLE = "The requested video is unavailable, it has been deleted from the platform."; - public static final String PERMISSION_DENIED = "You do not have access to download the video, permission is denied."; - public static final String DRIFTY_COMPONENT_NOT_EXECUTABLE = "A Drifty component (yt-dlp) is not marked as executable."; + public static final String FAILED_TO_CREATE_LOG_ERROR = "Failed to create log : "; + public static final String FAILED_TO_CLEAR_LOG_ERROR = "Failed to clear Log contents !"; + public static final String FILE_NOT_FOUND_ERROR = "An error occurred! Requested file does not exist, please check the url."; + public static final String VIDEO_UNAVAILABLE_ERROR = "The requested video is unavailable, it has been deleted from the platform."; + public static final String PERMISSION_DENIED_ERROR = "You do not have access to download the video, permission is denied."; + public static final String DRIFTY_COMPONENT_NOT_EXECUTABLE_ERROR = "A Drifty component (yt-dlp) is not marked as executable."; public static final String USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"; public static final long ONE_DAY = 86400000; // Value of one day (24 Hours) in milliseconds public static URL updateURL; diff --git a/Core/src/main/java/support/DownloadConfiguration.java b/Core/src/main/java/support/DownloadConfiguration.java index c8a5acfe..0e2c658d 100644 --- a/Core/src/main/java/support/DownloadConfiguration.java +++ b/Core/src/main/java/support/DownloadConfiguration.java @@ -12,10 +12,7 @@ import utils.Utility; import java.util.*; -import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import static utils.Utility.cleanFilename; import static utils.Utility.randomString; @@ -25,11 +22,11 @@ public class DownloadConfiguration { private final ArrayList> fileData; private final LinkType linkType; private final String filename; + private final MessageBroker msgBroker = Environment.getMessageBroker(); private String link; private int fileCount; private int filesProcessed; private int statusCode; - protected MessageBroker msgBroker = Environment.getMessageBroker(); public DownloadConfiguration(String link, String directory, String filename) { this.link = link; @@ -42,9 +39,7 @@ public DownloadConfiguration(String link, String directory, String filename) { public void sanitizeLink() { link = link.trim(); link = link.replace('\\', '/'); - if (!(link.startsWith("http://") || link.startsWith("https://"))) { - link = "https://" + link; - } + link = link.replaceFirst("^(?!https?:)", "https://"); if (link.startsWith("https://github.com/") || (link.startsWith("http://github.com/"))) { if (!link.endsWith("?raw=true")) { link = link + "?raw=true"; @@ -56,142 +51,178 @@ public void sanitizeLink() { } public int fetchFileData() { - if (this.linkType.equals(LinkType.YOUTUBE)) { - String jsonString = Utility.getYtDlpMetadata(link); - if (jsonString == null || jsonString.isEmpty()) { - msgBroker.msgLogError("Failed to process Youtube Link"); - statusCode = -1; - return -1; - } - JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject(); - if (link.contains("playlist")) { - JsonArray entries = jsonObject.get("entries").getAsJsonArray(); - fileCount = entries.size(); - for (JsonElement entry : entries) { - if (Mode.isCLI()) { - System.out.print("\r[" + (filesProcessed + 1) + "/" + fileCount + "] Processing Youtube Playlist..."); - } else { - msgBroker.msgLinkInfo("[" + (filesProcessed + 1) + "/" + fileCount + "] Processing Youtube Playlist..."); - } - JsonObject entryObject = entry.getAsJsonObject(); - String videoLink = entryObject.get("url").getAsString(); - String videoTitle = cleanFilename(entryObject.get("title").getAsString()); - if (videoTitle.isEmpty()) { - videoTitle = "Unknown_YouTube_Video_".concat(randomString(5)); - } - String filename = videoTitle.concat(".mp4"); - HashMap data = new HashMap<>(); - data.put("link", videoLink); - data.put("filename", filename); - data.put("directory", this.directory); - fileData.add(data); - filesProcessed++; - } + return switch (this.linkType) { + case YOUTUBE -> processYouTubeLink(); + case SPOTIFY -> processSpotifyLink(); + case INSTAGRAM -> processInstagramLink(); + default -> processFileLink(); + }; + } + + private int processYouTubeLink() { + String jsonString = Utility.getYtDlpMetadata(link); + if (jsonString == null || jsonString.isEmpty()) { + msgBroker.msgLogError("Failed to process Youtube Link"); + statusCode = -1; + return -1; + } + JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject(); + if (link.contains("playlist")) { + JsonArray entries = jsonObject.get("entries").getAsJsonArray(); + fileCount = entries.size(); + for (JsonElement entry : entries) { if (Mode.isCLI()) { - System.out.println("\rYoutube Playlist processed successfully"); - } else { - msgBroker.msgLinkInfo("Youtube Playlist processed successfully"); + System.out.print("\r[" + (filesProcessed + 1) + "/" + fileCount + "] Processing Youtube Playlist..."); } - } else { - msgBroker.msgLinkInfo("Processing Youtube Video..."); - fileCount = 1; - HashMap data = new HashMap<>(); - String videoTitle = cleanFilename(jsonObject.get("title").getAsString()); + JsonObject entryObject = entry.getAsJsonObject(); + String videoLink = entryObject.get("url").getAsString(); + String videoTitle = cleanFilename(entryObject.get("title").getAsString()); if (videoTitle.isEmpty()) { videoTitle = "Unknown_YouTube_Video_".concat(randomString(5)); } - String filename = videoTitle.concat(".mp4"); - data.put("link", link); - data.put("filename", this.filename == null ? filename : this.filename); + String detectedFilename = videoTitle.concat(".mp4"); + HashMap data = new HashMap<>(); + data.put("link", videoLink); + data.put("filename", detectedFilename); data.put("directory", this.directory); fileData.add(data); filesProcessed++; - msgBroker.msgLinkInfo("Youtube Video processed successfully"); } - } else if (this.linkType.equals(LinkType.SPOTIFY)) { - if (link.contains("playlist")) { - ArrayList> playlistMetadata = Utility.getSpotifyPlaylistMetadata(link); - if (playlistMetadata != null && !playlistMetadata.isEmpty()) { - fileCount = playlistMetadata.size(); - ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - if (Mode.isGUI()) { - executor.scheduleAtFixedRate(this::updateJobList, 1500, 600, TimeUnit.MILLISECONDS); - } - for (HashMap songMetadata : playlistMetadata) { - if (Mode.isCLI()) { - System.out.print("\r[" + filesProcessed + "/" + fileCount + "] Processing Spotify Playlist..."); - } else { - msgBroker.msgLinkInfo("[" + filesProcessed + "/" + fileCount + "] Processing Spotify Playlist..."); - } - HashMap data = Utility.getSpotifySongDownloadData(songMetadata, this.directory); - if (data == null) { - msgBroker.msgLogError("Failed to process Spotify Playlist"); - filesProcessed++; - statusCode = -1; - return -1; - } - fileData.add(data); - filesProcessed++; - } - msgBroker.msgLinkInfo("Spotify Playlist processed successfully"); - if (Mode.isGUI()) { - executor.shutdown(); - } - } + if (Mode.isCLI()) { + System.out.println("\rYoutube Playlist processed successfully"); } else { - HashMap songMetadata = Utility.getSpotifySongMetadata(link); - fileCount = 1; - if (songMetadata != null && !songMetadata.isEmpty()) { - msgBroker.msgLinkInfo("Processing Spotify Song..."); + msgBroker.msgLinkInfo("Youtube Playlist processed successfully"); + } + } else { + msgBroker.msgLinkInfo("Processing Youtube Video..."); + fileCount = 1; + HashMap data = new HashMap<>(); + String videoTitle = cleanFilename(jsonObject.get("title").getAsString()); + if (videoTitle.isEmpty()) { + videoTitle = "Unknown_YouTube_Video_".concat(randomString(5)); + } + String detectedFilename = videoTitle.concat(".mp4"); + data.put("link", link); + data.put("filename", this.filename == null ? detectedFilename : this.filename); + data.put("directory", this.directory); + fileData.add(data); + filesProcessed++; + msgBroker.msgLinkInfo("Youtube Video processed successfully"); + } + if (fileData.isEmpty()) { + statusCode = -1; + return -1; + } else { + statusCode = 0; + return 0; + } + } + + private int processSpotifyLink() { + if (link.contains("playlist")) { + ArrayList> playlistMetadata = Utility.getSpotifyPlaylistMetadata(link); + if (playlistMetadata != null && !playlistMetadata.isEmpty()) { + fileCount = playlistMetadata.size(); + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + if (Mode.isGUI()) { + executor.scheduleAtFixedRate(this::updateJobList, 1500, 600, TimeUnit.MILLISECONDS); + } + for (HashMap songMetadata : playlistMetadata) { + if (Mode.isCLI()) { + System.out.print("\r[" + filesProcessed + "/" + fileCount + "] Processing Spotify Playlist..."); + } else { + msgBroker.msgLinkInfo("[" + filesProcessed + "/" + fileCount + "] Processing Spotify Playlist..."); + } HashMap data = Utility.getSpotifySongDownloadData(songMetadata, this.directory); if (data == null) { - msgBroker.msgLogError("Failed to process Spotify Song"); + msgBroker.msgLogError("Failed to process Spotify Playlist"); filesProcessed++; statusCode = -1; return -1; } - if (this.filename != null) { - data.put("filename", this.filename); - } fileData.add(data); filesProcessed++; - msgBroker.msgLinkInfo("Spotify Song processed successfully"); + } + if (Mode.isCLI()) { + System.out.println("\rSpotify Playlist processed successfully"); + } else { + msgBroker.msgLinkInfo("Spotify Playlist processed successfully"); + } + if (Mode.isGUI()) { + executor.shutdown(); } } - } else if (this.linkType.equals(LinkType.INSTAGRAM)) { - String jsonString = Utility.getYtDlpMetadata(link); + } else { + HashMap songMetadata = Utility.getSpotifySongMetadata(link); fileCount = 1; - if (jsonString != null && !jsonString.isEmpty()) { - msgBroker.msgLinkInfo("Processing Instagram Post..."); - JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject(); - HashMap data = new HashMap<>(); - String instagramVideoName = cleanFilename(jsonObject.get("title").getAsString()); - if (instagramVideoName.isEmpty()) { - instagramVideoName = "Unknown_Instagram_Video_".concat(randomString(5)); + if (songMetadata != null && !songMetadata.isEmpty()) { + msgBroker.msgLinkInfo("Processing Spotify Song..."); + HashMap data = Utility.getSpotifySongDownloadData(songMetadata, this.directory); + if (data == null) { + msgBroker.msgLogError("Failed to process Spotify Song"); + filesProcessed++; + statusCode = -1; + return -1; + } + if (this.filename != null) { + data.put("filename", this.filename); } - String filename = instagramVideoName.concat(".").concat(jsonObject.get("ext").getAsString()); - data.put("link", link); - data.put("filename", this.filename == null ? filename : this.filename); - data.put("directory", this.directory); fileData.add(data); filesProcessed++; - msgBroker.msgLinkInfo("Instagram Post processed successfully"); + msgBroker.msgLinkInfo("Spotify Song processed successfully"); } + } + if (fileData.isEmpty()) { + statusCode = -1; + return -1; } else { - msgBroker.msgLinkInfo("Processing File Link..."); - fileCount = 1; + statusCode = 0; + return 0; + } + } + + private int processInstagramLink() { + String jsonString = Utility.getYtDlpMetadata(link); + fileCount = 1; + if (jsonString != null && !jsonString.isEmpty()) { + msgBroker.msgLinkInfo("Processing Instagram Post..."); + JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject(); HashMap data = new HashMap<>(); - String filename = cleanFilename(Utility.extractFilenameFromURL(link)); - if (filename.isEmpty()) { - filename = "Unknown_File_".concat(randomString(5)); + String instagramVideoName = cleanFilename(jsonObject.get("title").getAsString()); + if (instagramVideoName.isEmpty()) { + instagramVideoName = "Unknown_Instagram_Video_".concat(randomString(5)); } + String detectedFilename = instagramVideoName.concat(".").concat(jsonObject.get("ext").getAsString()); data.put("link", link); - data.put("filename", this.filename == null ? filename : this.filename); + data.put("filename", this.filename == null ? detectedFilename : this.filename); data.put("directory", this.directory); fileData.add(data); filesProcessed++; - msgBroker.msgLinkInfo("File Link processed successfully"); + msgBroker.msgLinkInfo("Instagram Post processed successfully"); + } + if (fileData.isEmpty()) { + statusCode = -1; + return -1; + } else { + statusCode = 0; + return 0; } + } + + private int processFileLink() { + msgBroker.msgLinkInfo("Processing File Link..."); + fileCount = 1; + HashMap data = new HashMap<>(); + String detectedFilename = cleanFilename(Utility.extractFilenameFromURL(link)); + if (detectedFilename.isEmpty()) { + detectedFilename = "Unknown_File_".concat(randomString(5)); + } + data.put("link", link); + data.put("filename", this.filename == null ? detectedFilename : this.filename); + data.put("directory", this.directory); + fileData.add(data); + filesProcessed++; + msgBroker.msgLinkInfo("File Link processed successfully"); if (fileData.isEmpty()) { statusCode = -1; return -1; @@ -202,8 +233,10 @@ public int fetchFileData() { } public void updateJobList() { - Set distinctJobList = new TreeSet<>(Comparator.comparing(Job::hashCode)); // TreeSet to remove duplicates based on Job.hashCode() - distinctJobList.addAll(AppSettings.GET.jobs().jobList()); + Map distinctJobList = new ConcurrentHashMap<>(); + for (Job job : AppSettings.GET.jobs().jobList()) { + distinctJobList.put(job.hashCode(), job); + } if (fileData.isEmpty()) { return; } @@ -218,9 +251,9 @@ public void updateJobList() { } else { job = new Job(link, directory, filename, null); } - distinctJobList.add(job); + distinctJobList.put(job.hashCode(), job); } - AppSettings.GET.jobs().setList(new ConcurrentLinkedDeque<>(distinctJobList)); + AppSettings.GET.jobs().setList(new ConcurrentLinkedDeque<>(distinctJobList.values())); } public String getLink() { diff --git a/Core/src/main/java/support/Job.java b/Core/src/main/java/support/Job.java index 8fc7d14b..7e689b6f 100644 --- a/Core/src/main/java/support/Job.java +++ b/Core/src/main/java/support/Job.java @@ -30,10 +30,13 @@ public String getSourceLink() { } public String getDownloadLink() { - if (downloadLink == null) { + if (downloadLink != null) { + return downloadLink; + } + if (link != null) { return link; } - return downloadLink; + throw new IllegalStateException("Both link and downloadLink are null"); } public String getDir() { @@ -55,7 +58,9 @@ public boolean fileExists() { @Override public boolean equals(Object obj) { if (obj instanceof Job job) { - return job.getSourceLink().equals(link) && job.getDir().equals(dir) && job.getFilename().equals(filename); + return Objects.equals(job.getSourceLink(), link) + && Objects.equals(job.getDir(), dir) + && Objects.equals(job.getFilename(), filename); } return false; } diff --git a/Core/src/main/java/utils/Logger.java b/Core/src/main/java/utils/Logger.java index 95960e26..b0072b51 100644 --- a/Core/src/main/java/utils/Logger.java +++ b/Core/src/main/java/utils/Logger.java @@ -10,8 +10,8 @@ import java.text.SimpleDateFormat; import java.util.Calendar; -import static support.Constants.FAILED_TO_CLEAR_LOG; -import static support.Constants.FAILED_TO_CREATE_LOG; +import static support.Constants.FAILED_TO_CLEAR_LOG_ERROR; +import static support.Constants.FAILED_TO_CREATE_LOG_ERROR; public final class Logger { boolean isLogEmpty; @@ -39,7 +39,7 @@ private File determineLogFile() { try { return Files.createTempFile(logFilename.split("\\.")[0], ".log").toFile(); // Log file will be created in the temp directory } catch (IOException e) { - System.err.println(FAILED_TO_CREATE_LOG + logFilename); + System.err.println(FAILED_TO_CREATE_LOG_ERROR + logFilename); } return new File(logFilename); } @@ -65,7 +65,7 @@ private void clearLog() { isLogEmpty = true; logWriter.write(""); } catch (IOException e) { - System.err.println(FAILED_TO_CLEAR_LOG); + System.err.println(FAILED_TO_CLEAR_LOG_ERROR); } } @@ -78,7 +78,7 @@ public void log(MessageType messageType, String logMessage) { isLogEmpty = true; logWriter.println(dateAndTime + " " + messageType.toString() + " - " + logMessage); } catch (IOException e) { - System.err.println(FAILED_TO_CREATE_LOG + logMessage); + System.err.println(FAILED_TO_CREATE_LOG_ERROR + logMessage); } } } diff --git a/Core/src/main/java/utils/Utility.java b/Core/src/main/java/utils/Utility.java index 9fb21fad..1b108f4b 100644 --- a/Core/src/main/java/utils/Utility.java +++ b/Core/src/main/java/utils/Utility.java @@ -187,7 +187,7 @@ public static String getHomeDownloadFolder() { } if (downloadsFolder.equals(FileSystems.getDefault().getSeparator())) { downloadsFolder = System.getProperty("user.home"); - msgBroker.msgDirError(FAILED_TO_RETRIEVE_DEFAULT_DOWNLOAD_FOLDER); + msgBroker.msgDirError(FAILED_TO_RETRIEVE_DEFAULT_DOWNLOAD_FOLDER_ERROR); } else { msgBroker.msgDirInfo(FOLDER_DETECTED + downloadsFolder); } @@ -514,6 +514,9 @@ public static String getSpotifyDownloadLink(HashMap songMetadata */ public static String cleanFilename(String filename) { String fn = StringEscapeUtils.unescapeJava(filename); + if (fn == null) { + return ""; + } return fn.replaceAll("[^a-zA-Z0-9-.%?*:|_)<(> ]+", "").strip(); } diff --git a/GUI/src/main/java/backend/FileDownloader.java b/GUI/src/main/java/backend/FileDownloader.java index 489fbd25..6d8bae90 100644 --- a/GUI/src/main/java/backend/FileDownloader.java +++ b/GUI/src/main/java/backend/FileDownloader.java @@ -102,11 +102,11 @@ private void downloadYoutubeOrInstagram(boolean isSpotifySong) { String msg = e.getMessage(); String[] messageArray = msg.split(","); if (messageArray.length >= 1 && messageArray[0].toLowerCase().trim().replaceAll(" ", "").contains("cannotrunprogram")) { // If yt-dlp program is not marked as executable - M.msgDownloadError(DRIFTY_COMPONENT_NOT_EXECUTABLE); + M.msgDownloadError(DRIFTY_COMPONENT_NOT_EXECUTABLE_ERROR); } else if (messageArray.length >= 1 && "permissiondenied".equals(messageArray[1].toLowerCase().trim().replaceAll(" ", ""))) { // If a private YouTube / Instagram video is asked to be downloaded - M.msgDownloadError(PERMISSION_DENIED); + M.msgDownloadError(PERMISSION_DENIED_ERROR); } else if ("videounavailable".equals(messageArray[0].toLowerCase().trim().replaceAll(" ", ""))) { // If YouTube / Instagram video is unavailable - M.msgDownloadError(VIDEO_UNAVAILABLE); + M.msgDownloadError(VIDEO_UNAVAILABLE_ERROR); } else { M.msgDownloadError("An Unknown Error occurred! " + e.getMessage()); } @@ -282,7 +282,7 @@ private void splitDownload() { message = String.format(WRITE_ACCESS_DENIED_F, path); exitCode = 1; } catch (FileNotFoundException e) { - message = FILE_NOT_FOUND; + message = FILE_NOT_FOUND_ERROR; exitCode = 1; } catch (UnknownHostException e) { message = "You are not connected to the internet!"; @@ -345,7 +345,7 @@ private void downloadFile() { message = String.format(WRITE_ACCESS_DENIED_F, path); exitCode = 1; } catch (FileNotFoundException e) { - message = FILE_NOT_FOUND; + message = FILE_NOT_FOUND_ERROR; exitCode = 1; } catch (IOException e) { message = String.format(FAILED_CONNECTION_F, url); diff --git a/GUI/src/main/java/gui/support/GUIDownloadConfiguration.java b/GUI/src/main/java/gui/support/GUIDownloadConfiguration.java index 3a9983f2..b2b98e61 100644 --- a/GUI/src/main/java/gui/support/GUIDownloadConfiguration.java +++ b/GUI/src/main/java/gui/support/GUIDownloadConfiguration.java @@ -5,26 +5,22 @@ import support.DownloadConfiguration; public class GUIDownloadConfiguration extends DownloadConfiguration { - protected MessageBroker msgBroker = Environment.getMessageBroker(); - - public GUIDownloadConfiguration(String link, String directory) { - super(link, directory, null); - } + private final MessageBroker messageBroker = Environment.getMessageBroker(); public GUIDownloadConfiguration(String link, String directory, String filename) { super(link, directory, filename); } public void prepareFileData() { - msgBroker.msgLinkInfo("Fetching file data..."); + messageBroker.msgLinkInfo("Fetching file data..."); int statusCode = fetchFileData(); if (statusCode == 0) { - msgBroker.msgLinkInfo("File data fetched successfully"); - msgBroker.msgLinkInfo("Adding file(s) to batch..."); + messageBroker.msgLinkInfo("File data fetched successfully"); + messageBroker.msgLinkInfo("Adding file(s) to batch..."); updateJobList(); - msgBroker.msgLinkInfo("File(s) added to batch successfully"); + messageBroker.msgLinkInfo("File(s) added to batch successfully"); } else { - msgBroker.msgLogError("Failed to fetch file data"); + messageBroker.msgLogError("Failed to fetch file data"); } } } diff --git a/GUI/src/main/java/ui/FileMetadataRetriever.java b/GUI/src/main/java/ui/FileMetadataRetriever.java index cc0416ea..e8f59d8b 100644 --- a/GUI/src/main/java/ui/FileMetadataRetriever.java +++ b/GUI/src/main/java/ui/FileMetadataRetriever.java @@ -12,6 +12,7 @@ public class FileMetadataRetriever extends Task { private final GUIDownloadConfiguration config; + // Progress bar direction boolean dirUp = true; public FileMetadataRetriever(GUIDownloadConfiguration config) { @@ -47,15 +48,8 @@ private TimerTask runProgress() { public void run() { Platform.runLater(() -> { double value = getProgress(); - value = dirUp ? value + .01 : value - .01; - if (value > 1.0) { - dirUp = !dirUp; - value = 1.0; - } - if (value < 0) { - dirUp = !dirUp; - value = 0.0; - } + dirUp = (value >= 1.0 || value <= 0.0) != dirUp; + value += dirUp ? 0.01 : -0.01; updateProgress(value, 1.0); }); } diff --git a/GUI/src/main/java/ui/UIController.java b/GUI/src/main/java/ui/UIController.java index 90b8132d..e101ca58 100644 --- a/GUI/src/main/java/ui/UIController.java +++ b/GUI/src/main/java/ui/UIController.java @@ -355,7 +355,7 @@ If the file has not been downloaded before, it will add the link to the job list downloadConfig = new GUIDownloadConfiguration(link, dir, filename); } } else { - downloadConfig = new GUIDownloadConfiguration(link, getDir()); + downloadConfig = new GUIDownloadConfiguration(link, getDir(), null); // Filename is null because it will be retrieved from the link } downloadConfig.sanitizeLink(); ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); @@ -507,7 +507,7 @@ private void removeJobFromList(Job oldJob) { private void updateBatch() { UPDATING_BATCH.setValue(false); if (selectedJob != null) { - Job job = new Job(getLink(), getDir(), getFilename(), selectedJob.getDownloadLink()); + Job job = new Job(selectedJob.getSourceLink(), getDir(), getFilename(), selectedJob.getDownloadLink()); removeJobFromList(selectedJob); addJob(job); }