diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9687507be..59ffe2cd8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -185,4 +185,4 @@ jobs: gh release create v$VERSION --generate-notes 'linux/CLI/Drifty-CLI_linux#Drifty-CLI_linux' 'linux/GUI/Drifty-GUI_linux#Drifty-GUI_linux' 'macos/CLI/Drifty-CLI_macos#Drifty-CLI_macos' 'macos/GUI/Drifty-GUI.pkg#Drifty-GUI.pkg' 'windows/CLI/Drifty-CLI.exe#Drifty-CLI.exe' 'windows/GUI/Drifty-GUI.msi#Drifty-GUI.msi' 'windows/GUI/Drifty-GUI.exe#Drifty-GUI.exe' echo "[Released :white_check_mark: Drifty v$VERSION](https://github.com/SaptarshiSarkar12/Drifty/releases/tag/v$VERSION) successfully :rocket:!" >> $GITHUB_STEP_SUMMARY env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7856e02fe..fc945a8dc 100644 --- a/pom.xml +++ b/pom.xml @@ -302,6 +302,107 @@ + + + build-drifty-updater-for-ubuntu-latest + + + + org.graalvm.buildtools + native-maven-plugin + ${native-maven-plugin.version} + true + + updater + Updater.Updater + target/Updater/linux + + --no-fallback + -march=compatibility + -H:+ReportExceptionStackTraces + --verbose + + + + + build + + build + + package + + + + + + + + + build-drifty-updater-for-macos-latest + + + + org.graalvm.buildtools + native-maven-plugin + ${native-maven-plugin.version} + true + + updater + Updater.Updater + target/Updater/macos + + --no-fallback + -march=compatibility + -H:+ReportExceptionStackTraces + --verbose + + + + + build + + build + + package + + + + + + + + + build-drifty-updater-for-windows-latest + + + + org.graalvm.buildtools + native-maven-plugin + ${native-maven-plugin.version} + true + + updater + Updater.Updater + target/Updater/windows + + --no-fallback + -H:+ReportExceptionStackTraces + --verbose + + + + + build + + build + + package + + + + + + build-drifty-gui-for-macos-latest diff --git a/src/main/java/Backend/CopyExecutables.java b/src/main/java/Backend/CopyExecutables.java index 18c2861d0..45429d23d 100644 --- a/src/main/java/Backend/CopyExecutables.java +++ b/src/main/java/Backend/CopyExecutables.java @@ -7,10 +7,10 @@ import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Set; public class CopyExecutables { private static final MessageBroker M = Environment.getMessageBroker(); @@ -23,26 +23,9 @@ public final void copyExecutables(final String[] executableNames) throws IOExcep if (!executablePath.toFile().getParentFile().exists()) { FileUtils.createParentDirectories(executablePath.toFile()); } - try (OutputStream outputStream = Files.newOutputStream(executablePath)) { - if (inputStream != null) { - byte[] buffer = new byte[4096]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - } - } - if (!Files.isExecutable(executablePath)) { - ProcessBuilder makeExecutable = new ProcessBuilder("chmod", "+x", executablePath.toString()); - makeExecutable.inheritIO(); - Process chmod = makeExecutable.start(); - chmod.waitFor(); - } - } catch (FileAlreadyExistsException e) { - M.msgLogWarning(executableName + " not copied to " + Program.get(Program.DRIFTY_PATH) + " because it already exists!"); - } catch (InterruptedException e) { - M.msgLogWarning("Failed to make " + executableName + " executable: " + e.getMessage()); - } catch (IOException e) { - M.msgInitError("Failed to copy " + executableName + " executable: " + e.getMessage()); + Files.copy(inputStream, executablePath); + if (!Files.isExecutable(executablePath)) { + Files.setPosixFilePermissions(executablePath, Set.of(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)); } } } diff --git a/src/main/java/Backend/FileDownloader.java b/src/main/java/Backend/FileDownloader.java index 26084bc9e..19b98b4b2 100644 --- a/src/main/java/Backend/FileDownloader.java +++ b/src/main/java/Backend/FileDownloader.java @@ -1,5 +1,6 @@ package Backend; +import Enums.Mode; import Enums.Program; import Utils.Environment; import Utils.MessageBroker; @@ -88,7 +89,9 @@ private void downloadFile() { } ProgressBarThread progressBarThread = new ProgressBarThread(fileOutputStreams, partSizes, fileName, getDir(), totalSize, downloadMetrics); progressBarThread.start(); - M.msgDownloadInfo(String.format(DOWNLOADING_F, fileName)); + if (!Mode.isUpdating()) { + M.msgDownloadInfo(String.format(DOWNLOADING_F, fileName)); + } // check if all the files are downloaded while (!mergeDownloadedFileParts(fileOutputStreams, partSizes, downloaderThreads, tempFiles)) { sleep(500); @@ -186,7 +189,6 @@ public boolean mergeDownloadedFileParts(List fileOutputStreams public void run() { boolean isYouTubeLink = isYoutube(link); boolean isInstagramLink = isInstagram(link); - boolean isSpotifyLink = isSpotify(link); try { // If the link is of a YouTube or Instagram video, then the following block of code will execute. if (isYouTubeLink || isInstagramLink) { @@ -201,8 +203,6 @@ public void run() { } catch (Exception e) { if (isYouTubeLink) { M.msgDownloadError(YOUTUBE_DOWNLOAD_FAILED); - } else if (isSpotifyLink) { - M.msgDownloadError(SPOTIFY_DOWNLOAD_FAILED); } else { M.msgDownloadError(INSTAGRAM_DOWNLOAD_FAILED); } @@ -230,7 +230,7 @@ public void run() { String[] webPaths = url.getFile().trim().split("/"); fileName = webPaths[webPaths.length - 1]; } - M.msgDownloadInfo("Trying to download \"" + fileName + "\" ..."); + M.msgLogInfo("Trying to download \"" + fileName + "\" ..."); downloadFile(); } } catch (MalformedURLException | URISyntaxException e) { diff --git a/src/main/java/Backend/ProgressBarThread.java b/src/main/java/Backend/ProgressBarThread.java index e3582d3d1..20ea7d28f 100644 --- a/src/main/java/Backend/ProgressBarThread.java +++ b/src/main/java/Backend/ProgressBarThread.java @@ -100,7 +100,7 @@ private String generateProgressBar() { return "[" + spinner + "] " + fileName + " [" + bar + "](" + UnitConverter.format(totalDownloadedBytes, 2) + ") " + downloadSpeedWithoutUnit + " " + downloadSpeedUnit + "/s"; } else { int numberOfThreads = fileOutputStreams.size(); - StringBuilder result = new StringBuilder("[" + spinner + "] " + UnitConverter.format(totalDownloadedBytes, 2)); + StringBuilder result = new StringBuilder("[" + spinner + "] " + fileName + " "); float filled; totalDownloadedBytes = 0; long downloadSpeed = 0; @@ -144,7 +144,7 @@ private String generateProgressBar() { downloadSpeedWithoutUnit = 0; downloadSpeedUnit = "bytes"; } - result.append(" [").append(bar).append("] ").append(String.format("%.2f", downloadSpeedWithoutUnit)).append(" ").append(downloadSpeedUnit).append("/s"); + result.append(" [").append(bar).append("] (").append(UnitConverter.format(totalDownloadedBytes, 2)).append("/").append(UnitConverter.format(totalSizeOfTheFile, 2)).append(") ").append(String.format("%.2f", downloadSpeedWithoutUnit)).append(" ").append(downloadSpeedUnit).append("/s"); return result.toString(); } } diff --git a/src/main/java/Enums/MessageCategory.java b/src/main/java/Enums/MessageCategory.java index 860840b34..babb3362f 100644 --- a/src/main/java/Enums/MessageCategory.java +++ b/src/main/java/Enums/MessageCategory.java @@ -4,5 +4,5 @@ * This class is used to provide a category to the message that is sent to the Message Broker. */ public enum MessageCategory { - LINK, DIRECTORY, DOWNLOAD, FILENAME, LOG, INITIALIZATION, STYLE, BATCH, SETTINGS, INPUT, HISTORY + LINK, DIRECTORY, DOWNLOAD, FILENAME, LOG, INITIALIZATION, STYLE, BATCH, SETTINGS, INPUT, HISTORY, UPDATE } diff --git a/src/main/java/Enums/Mode.java b/src/main/java/Enums/Mode.java index 206130910..350e210e8 100644 --- a/src/main/java/Enums/Mode.java +++ b/src/main/java/Enums/Mode.java @@ -1,10 +1,10 @@ package Enums; /** - * This enum class specifies whether Drifty is opened in GUI or CLI mode + * This enum class specifies whether Drifty is opened in GUI or CLI or UPDATE mode. */ public enum Mode { - CLI, GUI; + CLI, GUI, UPDATE; private static Mode mode = Mode.CLI; @@ -19,4 +19,20 @@ public static boolean isGUI() { public static boolean isCLI() { return mode.equals(Mode.CLI); } + + public static void setUpdateMode() { + Mode.mode = Mode.UPDATE; + } + + public static boolean isUpdating() { + return mode.equals(Mode.UPDATE); + } + + public static void setMode(Mode mode) { // This method is used to set the mode (CLI, GUI, or UPDATE) in which Drifty is running + Mode.mode = mode; + } + + public static Mode getMode() { // This method returns the mode (CLI, GUI, or UPDATE) in which Drifty is running + return mode; + } } diff --git a/src/main/java/Enums/OS.java b/src/main/java/Enums/OS.java index 60c48e700..60ad49e56 100644 --- a/src/main/java/Enums/OS.java +++ b/src/main/java/Enums/OS.java @@ -26,7 +26,7 @@ private static void setOSType() { } } - private static OS getOSType() { + public static OS getOSType() { if (osType == null) { setOSType(); } @@ -44,4 +44,8 @@ public static boolean isWindows() { public static boolean isMac() { return getOSType().equals(OS.MAC); } + + public static boolean isLinux() { + return getOSType().equals(OS.LINUX); + } } diff --git a/src/main/java/GUI/Forms/FormsController.java b/src/main/java/GUI/Forms/FormsController.java index 9f2b96c5e..6b0d18127 100644 --- a/src/main/java/GUI/Forms/FormsController.java +++ b/src/main/java/GUI/Forms/FormsController.java @@ -108,8 +108,8 @@ private void setControlProperties() { setFilename(job.getFilename()); selectJob(job); }); - form.cbAutoPaste.setSelected(AppSettings.GET.mainAutoPaste()); - form.cbAutoPaste.selectedProperty().addListener(((observable, oldValue, newValue) -> AppSettings.SET.mainAutoPaste(newValue))); + form.cbAutoPaste.setSelected(AppSettings.GET.autoPaste()); + form.cbAutoPaste.selectedProperty().addListener(((observable, oldValue, newValue) -> AppSettings.SET.autoPaste(newValue))); form.tfDir.textProperty().addListener(((observable, oldValue, newValue) -> { if (!newValue.equals(oldValue)) { DIRECTORY_EXISTS.setValue(false); @@ -376,7 +376,7 @@ private Runnable getFilenames(String link) { We use the checkHistoryAddJobs method to look for discovered filenames. If we didn't do it this way, then we would need to wait until all filenames are discovered then add the jobs to the batch list in one action. Doing it this way gives the user more consistent feedback of the process while it is happening. This matters when a link contains - a lot of files because each file discovered takes a while, and when there are even hundreds of files, this process + a lot of files because each file discovered takes a while. When there are even hundreds of files, this process can appear to take a long time, so constant feedback for the user becomes relevant. */ @@ -630,7 +630,7 @@ public static void resetDownloadFoldersToActiveList() { INSTANCE.folders = AppSettings.GET.folders(); } - public static boolean isAutoPaste() { + public static boolean isAutoPasteEnabled() { return form.cbAutoPaste.isSelected() || AppSettings.GET.alwaysAutoPaste(); } diff --git a/src/main/java/GUI/Forms/GetFilename.java b/src/main/java/GUI/Forms/GetFilename.java index 9f88664b2..ab070a462 100644 --- a/src/main/java/GUI/Forms/GetFilename.java +++ b/src/main/java/GUI/Forms/GetFilename.java @@ -25,9 +25,9 @@ import static Utils.Utility.sleep; public class GetFilename extends Task> { + private final String regex = "(\\[download] Downloading item \\d+ of )(\\d+)"; private final String link; private final String dir; - private final String regex = "(\\[download] Downloading item \\d+ of )(\\d+)"; private final Pattern pattern = Pattern.compile(regex); private final String lineFeed = System.lineSeparator(); private int result = -1; @@ -67,7 +67,7 @@ protected ConcurrentLinkedDeque call() { timer.scheduleAtFixedRate(getJson(), 1000, 150); Utility.getLinkMetadata(link); timer.cancel(); - sleep(500); //give timerTask enough time to do its last run + sleep(500); // give timerTask enough time to do its last run jobList.clear(); FormsController.setDownloadInfoColor(Colors.GREEN); updateMessage("File(s) added to batch."); @@ -126,7 +126,6 @@ public void run() { }; } - private TimerTask runProgress() { return new TimerTask() { @Override diff --git a/src/main/java/GUI/Forms/Main.java b/src/main/java/GUI/Forms/Main.java index d127ebb70..06521b28b 100644 --- a/src/main/java/GUI/Forms/Main.java +++ b/src/main/java/GUI/Forms/Main.java @@ -61,7 +61,7 @@ private void createScene() { firstRun = false; return; } - if (FormsController.isAutoPaste()) { + if (FormsController.isAutoPasteEnabled()) { Clipboard clipboard = Clipboard.getSystemClipboard(); if (clipboard.hasString()) { String clipboardText = clipboard.getString(); @@ -107,7 +107,7 @@ private Menu getMenuItemsOfMenu() { return menu; } - private MenuBar menuBar(Menu... menus) { + private MenuBar menuBar(Menu ...menus) { return new MenuBar(menus); } diff --git a/src/main/java/GUI/Support/Job.java b/src/main/java/GUI/Support/Job.java index 60fa30f04..186033fb6 100644 --- a/src/main/java/GUI/Support/Job.java +++ b/src/main/java/GUI/Support/Job.java @@ -4,13 +4,16 @@ import java.nio.file.Path; import java.nio.file.Paths; +import static Utils.Utility.cleanFilename; +import static Utils.Utility.randomString; + /** * This is a data structure class for batch jobs. It holds the relevant information for a batch job */ public class Job { private final String link; private final String dir; - private final String filename; + private String filename; private boolean repeatDownload; public Job(String link, String dir, String filename, boolean repeatDownload) { @@ -60,8 +63,13 @@ public boolean fileExists() { } private String getName() { - String[] nameParts = link.split("/"); - return nameParts[nameParts.length - 1]; + String file = link.substring(link.lastIndexOf("/") + 1); + if (file.isEmpty()) { + return cleanFilename("Unknown_Filename_") + randomString(15); + } + // file.png?width=200 -> file.png + filename = file.split("([?])")[0]; + return filename; } public boolean repeatOK() { diff --git a/src/main/java/Preferences/Clear.java b/src/main/java/Preferences/Clear.java index b3c337a4c..7511c95a4 100644 --- a/src/main/java/Preferences/Clear.java +++ b/src/main/java/Preferences/Clear.java @@ -19,14 +19,18 @@ public void folders() { preferences.remove(FOLDERS.toString()); } - public void mainAutoPaste() { - preferences.remove(MAIN_AUTO_PASTE.toString()); + public void autoPaste() { + preferences.remove(AUTO_PASTE.toString()); } public void lastDLPUpdateTime() { preferences.remove(LAST_YT_DLP_UPDATE_TIME.toString()); } + public void lastDriftyUpdateTime() { + preferences.remove(LAST_DRIFTY_UPDATE_TIME.toString()); + } + public void lastFolder() { preferences.remove(LAST_FOLDER.toString()); } diff --git a/src/main/java/Preferences/Get.java b/src/main/java/Preferences/Get.java index 98194bbee..3289a07d5 100644 --- a/src/main/java/Preferences/Get.java +++ b/src/main/java/Preferences/Get.java @@ -42,14 +42,18 @@ public Folders folders() { return folders; } - public boolean mainAutoPaste() { - return preferences.getBoolean(MAIN_AUTO_PASTE.toString(), false); + public boolean autoPaste() { + return preferences.getBoolean(AUTO_PASTE.toString(), false); } public long lastDLPUpdateTime() { return preferences.getLong(LAST_YT_DLP_UPDATE_TIME.toString(), 1000L); } + public long lastDriftyUpdateTime() { + return preferences.getLong(LAST_DRIFTY_UPDATE_TIME.toString(), 1000L); + } + public String lastDownloadFolder() { String defaultPath = Paths.get(System.getProperty("user.home"), "Downloads").toAbsolutePath().toString(); return preferences.get(LAST_FOLDER.toString(), defaultPath); diff --git a/src/main/java/Preferences/Labels.java b/src/main/java/Preferences/Labels.java index 2986c71e7..44e026ffd 100644 --- a/src/main/java/Preferences/Labels.java +++ b/src/main/java/Preferences/Labels.java @@ -3,7 +3,7 @@ import java.util.prefs.Preferences; enum Labels { - FOLDERS, MAIN_AUTO_PASTE, LAST_YT_DLP_UPDATE_TIME, LAST_FOLDER, JOBS, MENU_BAR_SYSTEM, ALWAYS_AUTO_PASTE, YT_DLP_VERSION, SPOTDL_VERSION; + FOLDERS, AUTO_PASTE, LAST_YT_DLP_UPDATE_TIME, LAST_FOLDER, JOBS, MENU_BAR_SYSTEM, ALWAYS_AUTO_PASTE, YT_DLP_VERSION, SPOTDL_VERSION, LAST_DRIFTY_UPDATE_TIME; public static final Preferences PREFERENCES = Preferences.userNodeForPackage(Labels.class); } diff --git a/src/main/java/Preferences/Set.java b/src/main/java/Preferences/Set.java index 1d5ca7194..a185027fc 100644 --- a/src/main/java/Preferences/Set.java +++ b/src/main/java/Preferences/Set.java @@ -36,9 +36,9 @@ public void folders(Folders folders) { preferences.put(FOLDERS.toString(), value); } - public void mainAutoPaste(boolean isMainAutoPasteEnabled) { - AppSettings.CLEAR.mainAutoPaste(); - preferences.putBoolean(MAIN_AUTO_PASTE.toString(), isMainAutoPasteEnabled); + public void autoPaste(boolean value) { + AppSettings.CLEAR.autoPaste(); + preferences.putBoolean(AUTO_PASTE.toString(), value); } public void lastDLPUpdateTime(long value) { @@ -46,6 +46,11 @@ public void lastDLPUpdateTime(long value) { preferences.putLong(LAST_YT_DLP_UPDATE_TIME.toString(), value); } + public void lastDriftyUpdateTime(long value) { + AppSettings.CLEAR.lastDriftyUpdateTime(); + preferences.putLong(LAST_DRIFTY_UPDATE_TIME.toString(), value); + } + public void lastFolder(String lastFolderPath) { AppSettings.CLEAR.lastFolder(); preferences.put(LAST_FOLDER.toString(), lastFolderPath); diff --git a/src/main/java/Updater/Updater.java b/src/main/java/Updater/Updater.java new file mode 100644 index 000000000..b766e4132 --- /dev/null +++ b/src/main/java/Updater/Updater.java @@ -0,0 +1,66 @@ +package Updater; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static Enums.MessageType.INFO; + +public class Updater { + private static final DateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private static final Calendar DEFAULT_CALENDAR = Calendar.getInstance(); + private static final String TIMESTAMP = TIMESTAMP_FORMAT.format(DEFAULT_CALENDAR.getTime()); + private static String logFilename; + private static String applicationType; + + public static void main(String[] args) { + String oldExecLocation = args[0]; + String latestExecLocation = args[1]; + applicationType = args[2]; + String parentDirectory = oldExecLocation.substring(0, oldExecLocation.lastIndexOf(File.separator)); + logFilename = Paths.get(parentDirectory, "Drifty " + applicationType + ".log").toAbsolutePath().toString(); + Path originalExecPath = Paths.get(oldExecLocation); + Path latestExecPath = Paths.get(latestExecLocation); + try { + TimeUnit.MILLISECONDS.sleep(2000); + Files.delete(originalExecPath); + try (InputStream inputStream = Files.newInputStream(latestExecPath)) { + Files.copy(inputStream, originalExecPath); + } + if (Files.exists(originalExecPath)) { + if (!Files.isExecutable(originalExecPath)) { + Files.setPosixFilePermissions(originalExecPath, Set.of(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)); + } + log("Drifty updated successfully!"); + } else { + log("Drifty failed to update!"); + } + ProcessBuilder processBuilder = new ProcessBuilder(originalExecPath.toString()); + Process process = processBuilder.start(); + process.waitFor(); + } catch (IOException | InterruptedException e) { + log("Drifty failed to update!"); + } finally { + try { + Files.delete(latestExecPath); + } catch (IOException e) { + log("Failed to delete the latest Drifty executable!"); + } + } + } + + private static void log(String message) { + try (PrintWriter logWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFilename, true))))) { + logWriter.println(TIMESTAMP + " " + INFO + " - " + message); + } catch (IOException e) { + System.out.println("Failed to create log : " + "\"" + message + "\""); + } + } +} diff --git a/src/main/java/Utils/Environment.java b/src/main/java/Utils/Environment.java index 6c720d68c..edf74d313 100644 --- a/src/main/java/Utils/Environment.java +++ b/src/main/java/Utils/Environment.java @@ -1,6 +1,7 @@ package Utils; import Backend.CopyExecutables; +import Enums.Mode; import Enums.OS; import Enums.Program; import Preferences.AppSettings; @@ -15,12 +16,6 @@ public final class Environment { private static MessageBroker msgBroker = Environment.getMessageBroker(); - /* - This method is called by both CLI.Main and GUI.Forms.Main classes. - It first determines which yt-dlp program to copy out of resources based on the OS. - Next, it figures out which path to use to store yt-dlp and the users batch list. - Finally, it updates yt-dlp if it has not been updated in the last 24 hours. - */ public static void initializeEnvironment() { msgBroker.msgLogInfo("OS : " + OS.getOSName()); String ytDlpExecName = OS.isWindows() ? "yt-dlp.exe" : OS.isMac() ? "yt-dlp_macos" : "yt-dlp"; @@ -30,16 +25,25 @@ public static void initializeEnvironment() { Program.setSpotdlExecutableName(spotDLExecName); Program.setDriftyPath(driftyFolderPath); CopyExecutables copyExecutables = new CopyExecutables(); - boolean ytDLPExists = false; try { copyExecutables.copyExecutables(new String[]{ytDlpExecName, spotDLExecName}); } catch (IOException e) { msgBroker.msgInitError("Failed to copy yt-dlp! " + e.getMessage()); msgBroker.msgInitError("Failed to copy spotDL! " + e.getMessage()); } + boolean ytDLPExists = Files.exists(Paths.get(driftyFolderPath, ytDlpExecName)); + Mode previousMode = Mode.getMode(); + Mode.setUpdateMode(); + if (!isDriftyUpdated()) { + if (Utility.isUpdateAvailable()) { + msgBroker.msgUpdateInfo("Updating Drifty..."); + Utility.updateDrifty(previousMode); + } + } if (ytDLPExists && !isYtDLPUpdated()) { checkAndUpdateYtDlp(); } + Mode.setMode(previousMode); new Thread(Utility.setYtDlpVersion()).start(); new Thread(Utility.setSpotDLVersion()).start(); File folder = new File(driftyFolderPath); @@ -81,6 +85,12 @@ public static boolean isYtDLPUpdated() { return timeSinceLastUpdate <= oneDay; } + public static boolean isDriftyUpdated() { + final long oneDay = 1000 * 60 * 60 * 24; // Value of one day (24 Hours) in milliseconds + long timeSinceLastUpdate = System.currentTimeMillis() - AppSettings.GET.lastDriftyUpdateTime(); + return timeSinceLastUpdate <= oneDay; + } + public static MessageBroker getMessageBroker() { return msgBroker; } diff --git a/src/main/java/Utils/Logger.java b/src/main/java/Utils/Logger.java index 2694295fd..48042b103 100644 --- a/src/main/java/Utils/Logger.java +++ b/src/main/java/Utils/Logger.java @@ -18,7 +18,7 @@ public final class Logger { boolean isLogEmpty; private static Logger cliLoggerInstance; private static Logger guiLoggerInstance; - private final DateFormat dateFormat; + private static final DateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private final Calendar calendarObject = Calendar.getInstance(); private final String logFilename; @@ -28,7 +28,6 @@ private Logger() { } else { logFilename = "Drifty GUI.log"; } - dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } public static Logger getInstance() { @@ -57,15 +56,15 @@ private void clearLog() { } public void log(MessageType messageType, String logMessage) { - String dateAndTime = dateFormat.format(calendarObject.getTime()); + String currentTimeStamp = TIMESTAMP_FORMAT.format(calendarObject.getTime()); if (!isLogEmpty) { clearLog(); } try (PrintWriter logWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFilename, true))))) { isLogEmpty = true; - logWriter.println(dateAndTime + " " + messageType.toString() + " - " + logMessage); + logWriter.println(currentTimeStamp + " " + messageType.toString() + " - " + logMessage); } catch (IOException e) { - System.out.println(FAILED_TO_CREATE_LOG + logMessage); + System.out.println(FAILED_TO_CREATE_LOG + "\" " + logMessage + " \""); } } } diff --git a/src/main/java/Utils/MessageBroker.java b/src/main/java/Utils/MessageBroker.java index a34717593..fa46f13d0 100644 --- a/src/main/java/Utils/MessageBroker.java +++ b/src/main/java/Utils/MessageBroker.java @@ -89,6 +89,14 @@ public void msgStyleInfo(String message) { sendMessage(message, MessageType.INFO, MessageCategory.STYLE); } + public void msgUpdateInfo(String message) { + sendMessage(message, MessageType.INFO, MessageCategory.UPDATE); + } + + public void msgUpdateError(String message) { + sendMessage(message, MessageType.ERROR, MessageCategory.UPDATE); + } + public void msgInputError(String message, boolean endWithNewLine) { this.endWithNewLine = endWithNewLine; sendMessage(message, MessageType.ERROR, MessageCategory.INPUT); @@ -143,6 +151,11 @@ private void sendMessage(String message, MessageType messageType, MessageCategor } } } + } else if (Mode.isUpdating()) { + if (!messageCategory.equals(LOG)) { + System.out.println(message); + } + logger.log(messageType, message); } } } diff --git a/src/main/java/Utils/Utility.java b/src/main/java/Utils/Utility.java index 01d457fa4..10a02b0aa 100644 --- a/src/main/java/Utils/Utility.java +++ b/src/main/java/Utils/Utility.java @@ -1,13 +1,13 @@ package Utils; import Backend.DownloadFolderLocator; +import Backend.FileDownloader; +import CLI.Main; +import Enums.Mode; import Enums.OS; import Enums.Program; import Preferences.AppSettings; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; +import com.google.gson.*; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.text.StringEscapeUtils; @@ -20,12 +20,11 @@ import java.io.InputStreamReader; import java.net.*; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.LinkedList; -import java.util.Objects; -import java.util.Random; -import java.util.Scanner; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -114,17 +113,6 @@ public static String findFilenameInLink(String link) { M.msgFilenameError(AUTO_FILE_NAME_DETECTION_FAILED); return null; } - int index = file.lastIndexOf("."); - if (index < 0) { - M.msgFilenameError(AUTO_FILE_NAME_DETECTION_FAILED); - return null; - } - String extension = file.substring(index); - // edge case 1: "example.com/." - if (extension.length() == 1) { - M.msgFilenameError(AUTO_FILE_NAME_DETECTION_FAILED); - return null; - } // file.png?width=200 -> file.png filename = file.split("([?])")[0]; M.msgFilenameInfo(FILENAME_DETECTED + "\"" + filename + "\""); @@ -233,7 +221,13 @@ public static String renameFile(String filename, String dir) { String newFilename = filename; int fileNum = -1; String baseName = FilenameUtils.getBaseName(filename.replaceAll(" \\(\\d+\\)\\.", ".")); - String ext = "." + FilenameUtils.getExtension(filename); + String ext; + if (filename.contains(".")) { + ext = "."; + } else { + ext = ""; + } + ext += FilenameUtils.getExtension(filename); while (path.toFile().exists()) { fileNum += 1; newFilename = baseName + " (" + fileNum + ")" + ext; @@ -378,6 +372,108 @@ public static String getSpotifyDownloadLink(String link) { return null; } + public static boolean isUpdateAvailable() { + M.msgUpdateInfo("Checking for Drifty updates..."); + M.msgLogInfo("Current version : " + VERSION_NUMBER); + String latestVersion = getLatestVersion(); + if (latestVersion.isEmpty()) { + M.msgUpdateError("Failed to check for Drifty updates!"); + return false; + } + boolean isUpdateAvailable = compareVersions(latestVersion, VERSION_NUMBER); + if (isUpdateAvailable) { + M.msgUpdateInfo("Update available!"); + } else { + M.msgUpdateInfo("Drifty is up to date!"); + } + AppSettings.SET.lastDriftyUpdateTime(System.currentTimeMillis()); + return isUpdateAvailable; + } + + public static void updateDrifty(Mode currentAppMode) { + ArrayList osNames = new ArrayList<>(Arrays.asList(OS.values())); + String[] executableNames = {"Drifty-" + currentAppMode + ".exe", "Drifty-" + currentAppMode + "_macos", "Drifty-" + currentAppMode + "_linux"}; + Path currentExecPath = Paths.get(Main.class.getProtectionDomain().getCodeSource().getLocation().getPath()); + currentExecPath = Paths.get(URLDecoder.decode(currentExecPath.toString(), StandardCharsets.UTF_8)).toAbsolutePath(); + OS currentOS = OS.getOSType(); + int index = osNames.indexOf(currentOS); + String executableUrl = "https://github.com/SaptarshiSarkar12/Drifty/releases/latest/download/" + executableNames[index]; + String fileName = executableNames[index]; + String dirPath = switch (osNames.get(index)) { + case WIN -> Paths.get(System.getenv("LOCALAPPDATA"), "Drifty", "updates").toAbsolutePath().toString(); + case MAC, LINUX -> Paths.get(System.getProperty("user.home"), ".drifty", "updates").toAbsolutePath().toString(); + default -> { + M.msgUpdateError("Update not available for this OS (" + OS.getOSName() + ")!"); + M.msgUpdateError("Update cancelled!"); + yield null; + } + }; + if (dirPath == null) { + return; + } + try { + final Path updateFolderPath = Paths.get(dirPath); + if (!Files.exists(updateFolderPath) && !Files.isDirectory(updateFolderPath)) { + Files.createDirectory(updateFolderPath); + } + } catch (IOException e) { + M.msgUpdateError("Failed to create temporary directory for Drifty updates! " + e.getMessage()); + return; + } + M.msgUpdateInfo("Downloading latest Drifty executable..."); + FileDownloader downloadLatestExec = new FileDownloader(executableUrl, fileName, dirPath); + downloadLatestExec.run(); + Path latestExecFilePath = Paths.get(dirPath, fileName).toAbsolutePath(); + File currentExecFile = new File(currentExecPath.toString()); + String currentExecFileName = currentExecFile.getName(); + String parentDir = currentExecPath.toString().substring(0, currentExecPath.toString().lastIndexOf(File.separator)); + Path oldPath = Paths.get(parentDir, currentExecFileName + ".old").toAbsolutePath(); + currentExecFile.renameTo(new File(oldPath.toString())); + try { + Files.copy(latestExecFilePath, currentExecPath); + } catch (IOException e) { + M.msgUpdateError("Failed to copy latest Drifty executable to the current directory! " + e.getMessage()); + } + try { + FileUtils.forceDelete(oldPath.toFile()); + } catch (IOException e) { + M.msgUpdateError("Failed to delete old Drifty executable! " + e.getMessage()); + } + currentExecFile.setExecutable(true); + M.msgUpdateInfo("Drifty " + currentAppMode.toString() + " updated successfully!"); + } + + private static String getLatestVersion() { + try { + URL url = new URI("https://api.github.com/repos/SaptarshiSarkar12/Drifty/releases/latest").toURL(); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String response = reader.readLine(); + JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject(); + String tagValue = jsonObject.get("tag_name").getAsString(); + M.msgLogInfo("Latest version : " + tagValue); + return tagValue; + } + } catch (Exception e) { + System.out.println("Failed to fetch latest version !"); + return ""; + } + } + + private static boolean compareVersions(String newVersion, String currentVersion) { + newVersion = newVersion.replaceFirst("^v", ""); + currentVersion = currentVersion.replaceFirst("^v", ""); + String[] newVersionParts = newVersion.split("\\."); + String[] currentVersionParts = currentVersion.split("\\."); + for (int i = 0; i < 3; i++) { + if (Integer.parseInt(newVersionParts[i]) > Integer.parseInt(currentVersionParts[i])) { + return true; + } + } + return false; + } + public static Runnable setYtDlpVersion() { return () -> { String command = Program.get(YT_DLP);