diff --git a/pom.xml b/pom.xml index a5ab96eea..65aecfb41 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,16 @@ xuggle-xuggler-server-all 5.7.0-SNAPSHOT + + com.sparkjava + spark-core + 2.9.4 + + + com.google.code.gson + gson + 2.8.9 + UTF-8 @@ -102,6 +112,14 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + MegaBasterd diff --git a/src/main/java/com/tonikelope/megabasterd/ChunkDownloader.java b/src/main/java/com/tonikelope/megabasterd/ChunkDownloader.java index eb1d8725b..cec96dcc3 100644 --- a/src/main/java/com/tonikelope/megabasterd/ChunkDownloader.java +++ b/src/main/java/com/tonikelope/megabasterd/ChunkDownloader.java @@ -31,7 +31,7 @@ import java.util.logging.Logger; */ public class ChunkDownloader implements Runnable, SecureSingleThreadNotifiable { - public static final int SMART_PROXY_RECHECK_509_TIME = 3600; + public static final int SMART_PROXY_RECHECK_509_TIME = 300; private static final Logger LOG = Logger.getLogger(ChunkDownloader.class.getName()); private final int _id; private final Download _download; @@ -44,6 +44,7 @@ public class ChunkDownloader implements Runnable, SecureSingleThreadNotifiable { private volatile boolean _reset_current_chunk; private volatile InputStream _chunk_inputstream = null; private volatile long _509_timestamp = -1; + private volatile boolean _bandwidth_exceeded; private String _current_smart_proxy; @@ -74,7 +75,7 @@ public class ChunkDownloader implements Runnable, SecureSingleThreadNotifiable { _excluded_proxy_list = new ArrayList<>(); _error_wait = false; _reset_current_chunk = false; - + _bandwidth_exceeded = false; } public boolean isChunk_exception() { @@ -85,6 +86,23 @@ public class ChunkDownloader implements Runnable, SecureSingleThreadNotifiable { return _current_smart_proxy; } + public boolean isBandwidthExceeded() { + return _bandwidth_exceeded; + } + + public void forceRestartAfter509() { + // Reset the 509 timestamp to allow an immediate retry + _509_timestamp = -1; + + // Optionally reset other states to prepare for a restart + _reset_current_chunk = true; // Mark the current chunk for reset if needed + + LOG.log(Level.INFO, "Force restarting download after 509 error for Worker [{0}]", new Object[]{_id}); + + // Wake up any waiting process (if it's paused or waiting due to an error) + secureNotify(); // Notify the secure wait to ensure the process can proceed + } + public void setExit(boolean exit) { _exit = exit; } @@ -300,7 +318,9 @@ public class ChunkDownloader implements Runnable, SecureSingleThreadNotifiable { http_error = http_status; + _bandwidth_exceeded = http_status == 509; } else { + _bandwidth_exceeded = false; chunk_file = new File(_download.getChunkmanager().getChunks_dir() + "/" + MiscTools.HashString("sha1", _download.getUrl()) + ".chunk" + chunk_id); diff --git a/src/main/java/com/tonikelope/megabasterd/DBTools.java b/src/main/java/com/tonikelope/megabasterd/DBTools.java index e95f9634c..579e5c08d 100644 --- a/src/main/java/com/tonikelope/megabasterd/DBTools.java +++ b/src/main/java/com/tonikelope/megabasterd/DBTools.java @@ -32,13 +32,13 @@ public class DBTools { try (Connection conn = SqliteSingleton.getInstance().getConn(); Statement stat = conn.createStatement()) { - stat.executeUpdate("CREATE TABLE IF NOT EXISTS downloads(url TEXT, email TEXT, path TEXT, filename TEXT, filekey TEXT, filesize UNSIGNED BIG INT, filepass VARCHAR(64), filenoexpire VARCHAR(64), custom_chunks_dir TEXT, PRIMARY KEY ('url'), UNIQUE(path, filename));"); + stat.executeUpdate("CREATE TABLE IF NOT EXISTS downloads(url TEXT, email TEXT, path TEXT, filename TEXT, filekey TEXT, filesize UNSIGNED BIG INT, filepass VARCHAR(64), filenoexpire VARCHAR(64), custom_chunks_dir TEXT, createdAt BIG INT, PRIMARY KEY ('url'), UNIQUE(path, filename));"); stat.executeUpdate("CREATE TABLE IF NOT EXISTS uploads(filename TEXT, email TEXT, url TEXT, ul_key TEXT, parent_node TEXT, root_node TEXT, share_key TEXT, folder_link TEXT, bytes_uploaded UNSIGNED BIG INT, meta_mac TEXT, PRIMARY KEY ('filename'), UNIQUE(filename, email));"); stat.executeUpdate("CREATE TABLE IF NOT EXISTS settings(key VARCHAR(255), value TEXT, PRIMARY KEY('key'));"); stat.executeUpdate("CREATE TABLE IF NOT EXISTS mega_accounts(email TEXT, password TEXT, password_aes TEXT, user_hash TEXT, PRIMARY KEY('email'));"); stat.executeUpdate("CREATE TABLE IF NOT EXISTS elc_accounts(host TEXT, user TEXT, apikey TEXT, PRIMARY KEY('host'));"); stat.executeUpdate("CREATE TABLE IF NOT EXISTS mega_sessions(email TEXT, ma BLOB, crypt INT, PRIMARY KEY('email'));"); - stat.executeUpdate("CREATE TABLE IF NOT EXISTS downloads_queue(url TEXT, PRIMARY KEY('url'));"); + stat.executeUpdate("CREATE TABLE IF NOT EXISTS downloads_queue(url TEXT, createdAt BIG INT, PRIMARY KEY('url'));"); stat.executeUpdate("CREATE TABLE IF NOT EXISTS uploads_queue(filename TEXT, PRIMARY KEY('filename'));"); } } @@ -51,15 +51,20 @@ public class DBTools { } } + private static long currentUnixTime() { + return System.currentTimeMillis(); + } + public static synchronized void insertDownloadsQueue(ArrayList queue) throws SQLException { - try (Connection conn = SqliteSingleton.getInstance().getConn(); PreparedStatement ps = conn.prepareStatement("INSERT OR REPLACE INTO downloads_queue (url) VALUES (?)")) { + try (Connection conn = SqliteSingleton.getInstance().getConn(); PreparedStatement ps = conn.prepareStatement("INSERT OR REPLACE INTO downloads_queue (url, createdAt) VALUES (?,?)")) { if (!queue.isEmpty()) { for (String url : queue) { ps.setString(1, url); + ps.setLong(2, currentUnixTime()); ps.addBatch(); } @@ -77,7 +82,7 @@ public class DBTools { try (Connection conn = SqliteSingleton.getInstance().getConn(); Statement stat = conn.createStatement()) { - res = stat.executeQuery("SELECT * FROM downloads_queue ORDER BY rowid"); + res = stat.executeQuery("SELECT * FROM downloads_queue ORDER BY createdAt ASC"); while (res.next()) { @@ -190,7 +195,7 @@ public class DBTools { public static synchronized void insertDownload(String url, String email, String path, String filename, String filekey, Long size, String filepass, String filenoexpire, String custom_chunks_dir) throws SQLException { - try (Connection conn = SqliteSingleton.getInstance().getConn(); PreparedStatement ps = conn.prepareStatement("INSERT INTO downloads (url, email, path, filename, filekey, filesize, filepass, filenoexpire, custom_chunks_dir) VALUES (?,?,?,?,?,?,?,?,?)")) { + try (Connection conn = SqliteSingleton.getInstance().getConn(); PreparedStatement ps = conn.prepareStatement("INSERT INTO downloads (url, email, path, filename, filekey, filesize, filepass, filenoexpire, custom_chunks_dir, createdAt) VALUES (?,?,?,?,?,?,?,?,?,?)")) { ps.setString(1, url); ps.setString(2, email); @@ -201,6 +206,18 @@ public class DBTools { ps.setString(7, filepass); ps.setString(8, filenoexpire); ps.setString(9, custom_chunks_dir); + ps.setLong(10, currentUnixTime()); + + ps.executeUpdate(); + } + } + + public static synchronized void updateDownloadFilename(String filename, String url) throws SQLException { + + try (Connection conn = SqliteSingleton.getInstance().getConn(); PreparedStatement ps = conn.prepareStatement("UPDATE downloads SET filename=? WHERE url=?")) { + + ps.setString(1, filename); + ps.setString(2, url); ps.executeUpdate(); } @@ -396,7 +413,7 @@ public class DBTools { try (Connection conn = SqliteSingleton.getInstance().getConn(); Statement stat = conn.createStatement()) { - res = stat.executeQuery("SELECT * FROM downloads"); + res = stat.executeQuery("SELECT * FROM downloads ORDER BY createdAt ASC"); while (res.next()) { @@ -418,6 +435,29 @@ public class DBTools { return downloads; } + public static synchronized HashMap selectDownload(String url) throws SQLException { + + HashMap download = new HashMap<>(); + + try (Connection conn = SqliteSingleton.getInstance().getConn(); PreparedStatement ps = conn.prepareStatement("SELECT * FROM downloads WHERE url=?")) { + + ps.setString(1, url); + + ResultSet res = ps.executeQuery(); + + download.put("email", res.getString("email")); + download.put("path", res.getString("path")); + download.put("filename", res.getString("filename")); + download.put("filekey", res.getString("filekey")); + download.put("filesize", res.getLong("filesize")); + download.put("filepass", res.getString("filepass")); + download.put("filenoexpire", res.getString("filenoexpire")); + download.put("custom_chunks_dir", res.getString("custom_chunks_dir")); + } + + return download; + } + public static synchronized HashMap> selectUploads() throws SQLException { HashMap> uploads = new HashMap<>(); diff --git a/src/main/java/com/tonikelope/megabasterd/Download.java b/src/main/java/com/tonikelope/megabasterd/Download.java index 49fca5799..22d4112b7 100644 --- a/src/main/java/com/tonikelope/megabasterd/Download.java +++ b/src/main/java/com/tonikelope/megabasterd/Download.java @@ -91,6 +91,7 @@ public class Download implements Transference, Runnable, SecureSingleThreadNotif private volatile boolean _pause; private final ConcurrentLinkedQueue _partialProgressQueue; private volatile long _progress; + private volatile long _speed; private ChunkWriterManager _chunkmanager; private String _last_download_url; private boolean _provision_ok; @@ -350,6 +351,11 @@ public class Download implements Transference, Runnable, SecureSingleThreadNotif return _progress; } + @Override + public long getSpeed() { + return _speed; + } + public OutputStream getOutput_stream() { return _output_stream; } @@ -378,6 +384,27 @@ public class Download implements Transference, Runnable, SecureSingleThreadNotif return _file_name; } + public boolean setFile_name(String newName) { + try { + // don't rename unless completed + if(!isExit()) { + return false; + } + + File file = new File(this.getDownload_path(), _file_name); + File renamedFile = new File(file.getParent(), newName); + boolean renamed = file.renameTo(renamedFile); + if(renamed) { + _file_name = newName; + this.getView().getFile_name_label().setText(_file_name); + } + return renamed; + } catch (Exception ex){ + LOG.severe("Error renaming download. "+ex.toString()); + return false; + } + } + public String getFile_pass() { return _file_pass; } @@ -884,13 +911,19 @@ public class Download implements Transference, Runnable, SecureSingleThreadNotif } } else { - getView().hideAllExceptStatus(); + var dl = selectDownload(_url); - _status_error = "FILE WITH SAME NAME AND SIZE ALREADY EXISTS"; + if(dl.isEmpty()){ + getView().hideAllExceptStatus(); - _auto_retry_on_error = false; + _status_error = "FILE WITH SAME NAME AND SIZE ALREADY EXISTS"; - getView().printStatusError(_status_error); + _auto_retry_on_error = false; + + getView().printStatusError(_status_error); + } else { + getView().printStatusOK("File successfully downloaded!"); + } } } else if (_status_error != null) { @@ -920,6 +953,8 @@ public class Download implements Transference, Runnable, SecureSingleThreadNotif getView().printStatusError(_status_error); LOG.log(Level.SEVERE, ex.getMessage()); + + this.close(); } if (_file != null && !getView().isKeepTempFileSelected()) { @@ -945,11 +980,11 @@ public class Download implements Transference, Runnable, SecureSingleThreadNotif if ((_status_error == null && !_canceled) || global_cancel || !_auto_retry_on_error) { - try { - deleteDownload(_url); - } catch (SQLException ex) { - LOG.log(SEVERE, null, ex); - } + // try { + // deleteDownload(_url); + // } catch (SQLException ex) { + // LOG.log(SEVERE, null, ex); + // } } @@ -1732,6 +1767,11 @@ public class Download implements Transference, Runnable, SecureSingleThreadNotif } } + @Override + public void setSpeed(long speed) { + _speed = speed; + } + @Override public boolean isStatusError() { return _status_error != null; diff --git a/src/main/java/com/tonikelope/megabasterd/DownloadView.java b/src/main/java/com/tonikelope/megabasterd/DownloadView.java index 1bd655c44..fe1d49080 100644 --- a/src/main/java/com/tonikelope/megabasterd/DownloadView.java +++ b/src/main/java/com/tonikelope/megabasterd/DownloadView.java @@ -35,6 +35,10 @@ public class DownloadView extends javax.swing.JPanel implements TransferenceView private final Download _download; + public Download getDownload() { + return _download; + } + public JButton getQueue_bottom_button() { return queue_bottom_button; } diff --git a/src/main/java/com/tonikelope/megabasterd/FolderLinkDialog.java b/src/main/java/com/tonikelope/megabasterd/FolderLinkDialog.java index 340599893..8ba0a0218 100644 --- a/src/main/java/com/tonikelope/megabasterd/FolderLinkDialog.java +++ b/src/main/java/com/tonikelope/megabasterd/FolderLinkDialog.java @@ -37,6 +37,8 @@ public class FolderLinkDialog extends javax.swing.JDialog { private boolean _download; + public boolean isReady = false; + private final List _download_links; private long _total_space; @@ -116,7 +118,7 @@ public class FolderLinkDialog extends javax.swing.JDialog { } else if (_mega_error == -18) { MiscTools.GUIRun(() -> { - JOptionPane.showMessageDialog(tthis, LabelTranslatorSingleton.getInstance().translate("MEGA FOLDER TEMPORARILY UNAVAILABLE!"), "Error", JOptionPane.ERROR_MESSAGE); + if(isVisible()) JOptionPane.showMessageDialog(tthis, LabelTranslatorSingleton.getInstance().translate("MEGA FOLDER TEMPORARILY UNAVAILABLE!"), "Error", JOptionPane.ERROR_MESSAGE); setVisible(false); }); @@ -124,15 +126,14 @@ public class FolderLinkDialog extends javax.swing.JDialog { } else if (_mega_error == -16) { MiscTools.GUIRun(() -> { - JOptionPane.showMessageDialog(tthis, LabelTranslatorSingleton.getInstance().translate("MEGA FOLDER BLOCKED/DELETED"), "Error", JOptionPane.ERROR_MESSAGE); + if(isVisible()) JOptionPane.showMessageDialog(tthis, LabelTranslatorSingleton.getInstance().translate("MEGA FOLDER BLOCKED/DELETED"), "Error", JOptionPane.ERROR_MESSAGE); setVisible(false); }); } else { - MiscTools.GUIRun(() -> { - JOptionPane.showMessageDialog(tthis, LabelTranslatorSingleton.getInstance().translate("MEGA FOLDER LINK ERROR!"), "Error", JOptionPane.ERROR_MESSAGE); + if(isVisible()) JOptionPane.showMessageDialog(tthis, LabelTranslatorSingleton.getInstance().translate("MEGA FOLDER LINK ERROR!"), "Error", JOptionPane.ERROR_MESSAGE); setVisible(false); }); @@ -415,7 +416,7 @@ public class FolderLinkDialog extends javax.swing.JDialog { private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing // TODO add your handling code here: - if (working && JOptionPane.showConfirmDialog(this, "EXIT?") == 0) { + if (working && isVisible() && JOptionPane.showConfirmDialog(this, "EXIT?") == 0) { dispose(); exit = true; } else if (!working) { @@ -449,7 +450,7 @@ public class FolderLinkDialog extends javax.swing.JDialog { int r = -1; - if (ma.existsCachedFolderNodes(folder_id)) { + if (isVisible() && ma.existsCachedFolderNodes(folder_id)) { r = JOptionPane.showConfirmDialog(this, "Do you want to use FOLDER CACHED VERSION?\n\n(It could speed up the loading of very large folders)", "FOLDER CACHE", JOptionPane.YES_NO_OPTION); } @@ -702,8 +703,9 @@ public class FolderLinkDialog extends javax.swing.JDialog { node_bar.setVisible(false); working = false; - }); + isReady = true; + }); }); }); } diff --git a/src/main/java/com/tonikelope/megabasterd/MainPanel.java b/src/main/java/com/tonikelope/megabasterd/MainPanel.java index bf04727ae..d649daf67 100644 --- a/src/main/java/com/tonikelope/megabasterd/MainPanel.java +++ b/src/main/java/com/tonikelope/megabasterd/MainPanel.java @@ -49,6 +49,8 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutorService; import static java.util.concurrent.Executors.newCachedThreadPool; + +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import static java.util.logging.Level.SEVERE; import java.util.logging.Logger; @@ -76,6 +78,7 @@ public final class MainPanel { public static final int STREAMER_PORT = 1337; public static final int WATCHDOG_PORT = 1338; public static final int DEFAULT_MEGA_PROXY_PORT = 9999; + public static final int DEFAULT_REMOTE_API_PORT = 8127; public static final int RUN_COMMAND_TIME = 120; public static final String DEFAULT_LANGUAGE = "EN"; public static final boolean DEFAULT_SMART_PROXY = false; @@ -103,6 +106,7 @@ public final class MainPanel { public static volatile long LAST_EXTERNAL_COMMAND_TIMESTAMP; private static final Logger LOG = Logger.getLogger(MainPanel.class.getName()); private static volatile boolean CHECK_RUNNING = true; + private RemoteAPI _megabasterd_api; public static void main(String args[]) { @@ -318,6 +322,8 @@ public final class MainPanel { THREAD_POOL.execute((_clipboardspy = new ClipboardSpy())); + _megabasterd_api = new RemoteAPI(this); + try { _streamserver = new KissVideoStreamServer(this); _streamserver.start(STREAMER_PORT, "/video"); @@ -1249,8 +1255,16 @@ public final class MainPanel { tot_downloads = res.size(); + String max_down = DBTools.selectSettingValue("max_downloads"); + int max_dl = Download.SIM_TRANSFERENCES_DEFAULT; + if (max_down != null) { + max_dl = Integer.parseInt(max_down); + } + Iterator downloads_queue_iterator = downloads_queue.iterator(); + int processed_downloads = 0; // Counter to track number of downloads processed + while (downloads_queue_iterator.hasNext()) { try { @@ -1276,11 +1290,19 @@ public final class MainPanel { getDownload_manager().getTransference_provision_queue().add(download); conta_downloads++; + processed_downloads++; // Increment the processed download counter downloads_queue_iterator.remove(); } else { tot_downloads--; } + + // Check if 5 downloads have been processed, then wait for 10 seconds + if (processed_downloads == max_dl) { + System.out.println("Pausing for 10 seconds..."); + TimeUnit.SECONDS.sleep(10); // Wait for 10 seconds + } + TimeUnit.MILLISECONDS.sleep(500); // Wait for 0.5 second } } catch (Exception ex) { diff --git a/src/main/java/com/tonikelope/megabasterd/MegaAPI.java b/src/main/java/com/tonikelope/megabasterd/MegaAPI.java index e5d0f5c25..4080b11d0 100644 --- a/src/main/java/com/tonikelope/megabasterd/MegaAPI.java +++ b/src/main/java/com/tonikelope/megabasterd/MegaAPI.java @@ -1201,6 +1201,10 @@ public class MegaAPI implements Serializable { } public ArrayList GENERATE_N_LINKS(Set links) { + // Call overloaded method with default value for the optional parameter + return GENERATE_N_LINKS(links, false); // 10 is an example default value + } + public ArrayList GENERATE_N_LINKS(Set links, boolean quite) { HashMap> map = new HashMap<>(); @@ -1235,7 +1239,7 @@ public class MegaAPI implements Serializable { int r = -1; - if (existsCachedFolderNodes(folder_parts[0])) { + if (!quite && existsCachedFolderNodes(folder_parts[0])) { r = JOptionPane.showConfirmDialog(MainPanelView.getINSTANCE(), "Do you want to use FOLDER [" + folder_parts[0] + "] CACHED VERSION?\n\n(It could speed up the loading of very large folders)", "FOLDER CACHE", JOptionPane.YES_NO_OPTION); } diff --git a/src/main/java/com/tonikelope/megabasterd/RemoteAPI.java b/src/main/java/com/tonikelope/megabasterd/RemoteAPI.java new file mode 100644 index 000000000..6c33119bf --- /dev/null +++ b/src/main/java/com/tonikelope/megabasterd/RemoteAPI.java @@ -0,0 +1,409 @@ +package com.tonikelope.megabasterd; +import static com.tonikelope.megabasterd.MiscTools.*; + +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.*; +import java.util.*; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static spark.Spark.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class RemoteAPI { + private static final Logger LOG = Logger.getLogger(ChunkDownloader.class.getName()); + private final MainPanel _main_panel; + private final DownloadManager _download_manager; + private final javax.swing.JTree file_tree = new javax.swing.JTree(); + private final MegaAPI ma = new MegaAPI(); + public boolean enabled = false; + public int port = 0; + public RemoteAPI(MainPanel main_panel) { + _main_panel = main_panel; + _download_manager = _main_panel.getDownload_manager(); + + try{ + String enable_remote_api_val = DBTools.selectSettingValue("enable_remote_api"); + if (enable_remote_api_val != null) { + enabled = enable_remote_api_val.equals("yes"); + } + + String remote_api_p = DBTools.selectSettingValue("remote_api_port"); + port = Integer.parseInt(remote_api_p); + + // do not start if not enabled or port is 0 + if(!enabled || port == 0) return; + port(port); // Set the port number + } catch (Exception ex){ + LOG.log(Level.SEVERE, "Unable to start remote api server.", ex.getMessage()); + } + + Gson gson = new Gson(); + + get("/status", (req, res) -> { + res.type("application/json"); + + // system status + Map status = new HashMap<>(); + status.put("size", _download_manager.get_total_size()); + status.put("loaded", _download_manager.get_total_progress());; + status.put("running", _download_manager.getTransference_running_list().size() > 0 && !_download_manager.isPaused_all()); + + // get downloads + ArrayList> downloads = new ArrayList<>(); + for (Download dl : getDownloads()) { + String dlStatus = dl.getView().getStatus_label().getText(); + boolean finished = false; + + switch(dlStatus) { + case "File successfully downloaded!": + case "File successfully downloaded! (Integrity check PASSED)": + case "File successfully downloaded! (but integrity check CANCELED)": + finished = true; + break; + } + + // Check if Bandwidth Limit is Exceeded + int error509Count = 0; + ArrayList workers = dl.getChunkworkers(); + for (ChunkDownloader chunkDownloader : dl.getChunkworkers()) { + if(chunkDownloader.isBandwidthExceeded()){ + error509Count ++; + } + } + if(error509Count > 0){ + dlStatus = "509 Bandwidth Limit Exceeded"; + } + + if (dl.isStatusError()){ + dlStatus = "Error"; + } + + downloads.add(createDownloadStatus(dl.getUrl(), dl.getFile_name(), getDownloadPath(dl), + dl.getFile_size(), dl.getProgress(), dl.getFile_size() == dl.getProgress() ? 0 : dl.getSpeed(), + dlStatus, dl.getStatus_error(), finished, workers.size(), error509Count)); + } + status.put("downloads", downloads); + + ObjectMapper mapper = new ObjectMapper(); + return mapper.writeValueAsString(status); + }); + + post("/clear509", (req, res) -> { + res.type("application/json"); + + for (Download dl : getDownloads()) { + for (ChunkDownloader chunkDownloader : dl.getChunkworkers()) { + if(chunkDownloader.isBandwidthExceeded()) { + chunkDownloader.forceRestartAfter509(); + chunkDownloader.run(); + } + } + } + + return "{\"messsage\": \"Cleared 509 Errors.\"}"; + }); + + post("/pause", (req, res) -> { + res.type("application/json"); + + _download_manager.pauseAll(); + return "{\"messsage\": \"Paused all downloads\"}"; + }); + + post("/resume", (req, res) -> { + res.type("application/json"); + + _download_manager.resumeAll(); + return "{\"messsage\": \"Resumed all downloads\"}"; + }); + + post("/start", (req, res) -> { + try { + + res.type("application/json"); + + // Parse the JSON body + JsonObject jsonBody = JsonParser.parseString(req.body()).getAsJsonObject(); + if (!jsonBody.has("urls")) { + res.status(400); // Bad Request + return gson.toJson(new ErrorResponse("Missing required parameters.")); + } + + String linksText = jsonBody.get("urls").getAsString(); + Set urls = parseUrls(linksText); + + if (urls.isEmpty()) { + res.status(400); // Bad Request + return gson.toJson(new ErrorResponse("No mega files links found. Note: folder links not supported")); + } + + String downloadPath = _main_panel.getDefault_download_path(); + if(jsonBody.has("dest")){ + downloadPath = Paths.get(_main_panel.getDefault_download_path(), jsonBody.get("dest").getAsString()).toString(); + File path = new File(downloadPath); + if(!path.exists()) path.mkdirs(); + } + + // add urls + for (String url : urls) { + Download download = new Download( + _main_panel, ma, url, downloadPath, + null, null, null, null, null, _main_panel.isUse_slots_down(), + false, _main_panel.isUse_custom_chunks_dir() ? _main_panel.getCustom_chunks_dir() : null,false); + _download_manager.getTransference_provision_queue().add(download); + _download_manager.secureNotify(); + } + + return "{\"message\": \""+urls.size()+" Downloads Started, "+urls.toString()+"\"}"; + } catch (Exception ex){ + res.status(500); + return "{\"message\": \""+ex.toString()+"\"}"; + } + }); + + post("/stop", (req, res) -> { + try { + res.type("application/json"); + + // Parse the JSON body + JsonObject jsonBody = JsonParser.parseString(req.body()).getAsJsonObject(); + if (!jsonBody.has("url")) { + res.status(400); // Bad Request + return gson.toJson(new ErrorResponse("Missing required parameters.")); + } + + String downloadUrl = jsonBody.get("url").getAsString(); + + boolean deleteFiles = false; + if(jsonBody.has("delete")) deleteFiles = jsonBody.get("delete").getAsBoolean(); + + Download download = findDownloadByURL(downloadUrl); + if(download == null) { + res.status(404); + return "{\"message\": \"Download not found.\"}"; + } else { + // stop download + download.getView().getPause_button().doClick(); + download.getView().getKeep_temp_checkbox().setSelected(!deleteFiles); + download.getView().getStop_button().doClick(); + + // wait for clear but then remove download from list with 30-second timeout + long startTime = System.currentTimeMillis(); + while (!download.getView().getClose_button().isVisible()) { + if (System.currentTimeMillis() - startTime > 30000) { + break; // Timeout after 30 seconds + } + Thread.sleep(100); + } + + // If the close button is visible, click it + if (download.getView().getClose_button().isVisible()) { + download.getView().getClose_button().doClick(); + + // delete files if delete = true, is not default and folder is empty + Path downloadPath = Paths.get(download.getDownload_path()); + boolean deleted = false; + if(deleteFiles && !downloadPath.toString().equals(_main_panel.getDefault_download_path())) { + String downloadFolder = getDownloadPath(download); + + // delete file + Path filePath = Paths.get(_main_panel.getDefault_download_path(), downloadFolder, download.getFile_name()); + File file = new File(filePath.toString()); + if(file.exists()) file.delete(); + + // delete folders if empty + ArrayList pathsToCheck = new ArrayList<>(); + Path currentPath = Paths.get(_main_panel.getDefault_download_path()); + for (String folder : downloadFolder.split("/")) { + currentPath = Paths.get(currentPath.toString(), folder); + pathsToCheck.add(currentPath.toString()); + } + + Collections.reverse(pathsToCheck); + for (String folder : pathsToCheck) { + Path folderPath = Paths.get(folder); + if (isFolderEmpty(folderPath)) { + Files.delete(folderPath); + deleted = true; + } + } + } + + if(deleted){ + return "{\"message\": \"Removed download and deleted\"}"; + } + return "{\"message\": \"Removed download\"}"; + } else { + return "{\"message\": \"Failed to remove download within timeout.\"}"; + } + } + } catch (Exception ex){ + res.status(500); + return "{\"message\": \""+ex.toString()+"\"}"; + } + }); + + + post("/rename", (req, res) -> { + try { + res.type("application/json"); + + // Parse the JSON body + JsonObject jsonBody = JsonParser.parseString(req.body()).getAsJsonObject(); + if (!jsonBody.has("url") || !jsonBody.has("newName")) { + res.status(400); // Bad Request + return gson.toJson(new ErrorResponse("Missing required parameters.")); + } + + String downloadUrl = jsonBody.get("url").getAsString(); + String newName = jsonBody.get("newName").getAsString(); + Download download = findDownloadByURL(downloadUrl); + if(download == null) { + res.status(404); + return "{\"message\": \"Download not found.\"}"; + } + + String statusText = download.getView().getStatus_label().getText(); + if(!statusText.contains("File successfully downloaded!")) { + res.status(500); + return "{\"message\": \"Download is not completed.\"}"; + } + + boolean renamed = download.setFile_name(newName); + if(renamed) { + DBTools.updateDownloadFilename(newName, downloadUrl); + return "{\"message\": \"Renamed download\"}"; + } else { + res.status(400); + return "{\"message\": \"Unable to rename download\"}"; + } + + } catch (Exception ex){ + res.status(500); + return "{\"message\": \""+ex.toString()+"\"}"; + } + }); + + // Catch-all for 404 Not Found + notFound((req, res) -> { + res.type("text/plain"); + return "404 Not Found"; + }); + } + + static class ErrorResponse { + String message; + + public ErrorResponse(String message) { + this.message = message; + } + } + + private Download findDownloadByURL(String url) + { + Download download = null; + for (Download dl : getDownloads()) { + if (Objects.equals(dl.getUrl(), url)) { + download = dl; + break; + } + } + + return download; + } + + private String getDownloadPath(Download dl) + { + Path defaultDownloadPath = Paths.get(_main_panel.getDefault_download_path()); + Path dlFullPath = Paths.get(dl.getDownload_path()); + + ArrayList dlPathList = new ArrayList<>(); + for (int i = 0; i < dlFullPath.getNameCount(); i++) { + if(i > defaultDownloadPath.getNameCount()-1){ + dlPathList.add(dlFullPath.getName(i).toString()); + } + } + + return String.join("/", dlPathList); + } + + public static boolean isFolderEmpty(Path folderPath) { + try (DirectoryStream dirStream = Files.newDirectoryStream(folderPath)) { + return !dirStream.iterator().hasNext(); // If no entries exist, folder is empty + } catch (IOException e) { + return false; // Return false in case of an error + } + } + + private Set parseUrls(String linksText){ + String link_data = MiscTools.extractMegaLinksFromString(linksText); + Set urls = new HashSet(findAllRegex("(?:https?|mega)://[^\r\n]+(#[^\r\n!]*?)?![^\r\n!]+![^\\?\r\n/]+", link_data, 0)); + + + // remove folder links (not supported yet) + if (!urls.isEmpty()) { + Set folder_file_links = new HashSet(findAllRegex("(?:https?|mega)://[^\r\n]+#F\\*[^\r\n!]*?![^\r\n!]+![^\\?\r\n/]+", link_data, 0)); + if (!folder_file_links.isEmpty()) { + ArrayList nlinks = ma.GENERATE_N_LINKS(folder_file_links, true); + urls.removeAll(folder_file_links); + urls.addAll(nlinks); + } + } + + // get links for folder files + for (String url : urls) { + if (findFirstRegex("#F!", url, 0) != null) { + urls.remove(url); + + FolderLinkDialog fdialog = new FolderLinkDialog(_main_panel.getView(), false, url); + while (!fdialog.isReady) { } + for (var link : fdialog.getDownload_links()) { + urls.add(link.get("url").toString()); + } + fdialog.dispose(); + } + } + + return urls; + } + + private ArrayList getDownloads(){ + ArrayList downloads = new ArrayList<>(); + + // gets all the download components from the list + Component[] components = _download_manager.getScroll_panel().getComponents(); + for (Component component : components) { + try { + if (component instanceof DownloadView) { + downloads.add(((DownloadView) component).getDownload()); + } + } catch (Exception ignored) {} + } + + return downloads; + } + + // Helper method to create a DownloadStatus JSONObject + public static Map createDownloadStatus(String url, String name, String path, long size, long bytesLoaded, long speed, String status, String error, Boolean finished, int workers, int error509Count) { + Map downloadStatus = new HashMap<>(); + downloadStatus.put("url", url); + downloadStatus.put("name", name); + downloadStatus.put("path", path); + downloadStatus.put("bytesTotal", size); + downloadStatus.put("bytesLoaded", finished ? size : bytesLoaded); + downloadStatus.put("speed", speed); + downloadStatus.put("status", status); + downloadStatus.put("finished", finished); + downloadStatus.put("error", error); + downloadStatus.put("workers", workers); + downloadStatus.put("error509Count", error509Count); + return downloadStatus; + } +} diff --git a/src/main/java/com/tonikelope/megabasterd/SettingsDialog.form b/src/main/java/com/tonikelope/megabasterd/SettingsDialog.form index cc1a63c6e..23d8018cd 100644 --- a/src/main/java/com/tonikelope/megabasterd/SettingsDialog.form +++ b/src/main/java/com/tonikelope/megabasterd/SettingsDialog.form @@ -38,7 +38,7 @@ - + @@ -1263,7 +1263,7 @@ - + @@ -1396,6 +1396,7 @@ + @@ -1432,8 +1433,10 @@ + + - + @@ -1578,11 +1581,11 @@ - + - + @@ -1961,6 +1964,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/tonikelope/megabasterd/SettingsDialog.java b/src/main/java/com/tonikelope/megabasterd/SettingsDialog.java index 76bb7d720..06c7ca089 100644 --- a/src/main/java/com/tonikelope/megabasterd/SettingsDialog.java +++ b/src/main/java/com/tonikelope/megabasterd/SettingsDialog.java @@ -699,6 +699,24 @@ public class SettingsDialog extends javax.swing.JDialog { } debug_file_checkbox.setSelected(debug_file); + + boolean enable_remote_api = false; + + String enable_remote_api_val = DBTools.selectSettingValue("enable_remote_api"); + + if (enable_remote_api_val != null) { + enable_remote_api = (enable_remote_api_val.equals("yes")); + } + + enable_remote_api_checkbox.setSelected(enable_remote_api); + + String remote_api_p = DBTools.selectSettingValue("remote_api_port"); + + if (remote_api_p == null) { + remote_api_p = String.valueOf(MainPanel.DEFAULT_REMOTE_API_PORT); + } + + remote_api_port_spinner.setModel(new SpinnerNumberModel(Integer.parseInt(remote_api_p), 1024, 65535, 1)); String font = DBTools.selectSettingValue("font"); @@ -896,6 +914,11 @@ public class SettingsDialog extends javax.swing.JDialog { zoom_spinner = new javax.swing.JSpinner(); dark_mode_checkbox = new javax.swing.JCheckBox(); debug_file_path = new javax.swing.JLabel(); + remote_api_panel = new javax.swing.JPanel(); + enable_remote_api_checkbox = new javax.swing.JCheckBox(); + remote_api_port_label = new javax.swing.JLabel(); + remote_api_port_spinner = new javax.swing.JSpinner(); + remote_api_warning_label = new javax.swing.JLabel(); status = new javax.swing.JLabel(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); @@ -1719,11 +1742,11 @@ public class SettingsDialog extends javax.swing.JDialog { .addContainerGap() .addComponent(proxy_user_label) .addGap(6, 6, 6) - .addComponent(proxy_user_textfield) + .addComponent(proxy_user_textfield, javax.swing.GroupLayout.DEFAULT_SIZE, 459, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(proxy_pass_label) .addGap(6, 6, 6) - .addComponent(proxy_pass_textfield) + .addComponent(proxy_pass_textfield, javax.swing.GroupLayout.DEFAULT_SIZE, 514, Short.MAX_VALUE) .addContainerGap()) ); proxy_auth_panelLayout.setVerticalGroup( @@ -1952,6 +1975,58 @@ public class SettingsDialog extends javax.swing.JDialog { debug_file_path.setFont(new java.awt.Font("Noto Sans", 0, 18)); // NOI18N debug_file_path.setText(MainPanel.MEGABASTERD_HOME_DIR + "/MEGABASTERD_DEBUG.log"); + remote_api_panel.setBorder(javax.swing.BorderFactory.createTitledBorder((String)null)); + + enable_remote_api_checkbox.setFont(new java.awt.Font("Dialog", 1, 18)); // NOI18N + enable_remote_api_checkbox.setText("Enable Remote API"); + enable_remote_api_checkbox.setDoubleBuffered(true); + enable_remote_api_checkbox.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + enable_remote_api_checkboxStateChanged(evt); + } + }); + + remote_api_port_label.setFont(new java.awt.Font("Dialog", 1, 18)); // NOI18N + remote_api_port_label.setText("Port:"); + remote_api_port_label.setDoubleBuffered(true); + remote_api_port_label.setEnabled(false); + + remote_api_port_spinner.setFont(new java.awt.Font("Dialog", 0, 18)); // NOI18N + remote_api_port_spinner.setEnabled(false); + + remote_api_warning_label.setFont(new java.awt.Font("Dialog", 0, 14)); // NOI18N + remote_api_warning_label.setText("Note: you MUST \"OPEN\" this port in your router/firewall."); + remote_api_warning_label.setEnabled(false); + + javax.swing.GroupLayout remote_api_panelLayout = new javax.swing.GroupLayout(remote_api_panel); + remote_api_panel.setLayout(remote_api_panelLayout); + remote_api_panelLayout.setHorizontalGroup( + remote_api_panelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(remote_api_panelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(remote_api_panelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(enable_remote_api_checkbox) + .addGroup(remote_api_panelLayout.createSequentialGroup() + .addComponent(remote_api_port_label) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(remote_api_port_spinner, javax.swing.GroupLayout.PREFERRED_SIZE, 120, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(remote_api_warning_label)) + .addGap(0, 0, Short.MAX_VALUE)) + ); + remote_api_panelLayout.setVerticalGroup( + remote_api_panelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, remote_api_panelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(enable_remote_api_checkbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(remote_api_panelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(remote_api_port_label) + .addComponent(remote_api_port_spinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(remote_api_warning_label) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + javax.swing.GroupLayout advanced_panelLayout = new javax.swing.GroupLayout(advanced_panel); advanced_panel.setLayout(advanced_panelLayout); advanced_panelLayout.setHorizontalGroup( @@ -1980,7 +2055,8 @@ public class SettingsDialog extends javax.swing.JDialog { .addComponent(custom_chunks_dir_button)) .addGroup(advanced_panelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jPanel2, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addComponent(jPanel2, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(remote_api_panel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap()) ); advanced_panelLayout.setVerticalGroup( @@ -2011,8 +2087,10 @@ public class SettingsDialog extends javax.swing.JDialog { .addGap(18, 18, 18) .addComponent(proxy_panel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) + .addComponent(remote_api_panel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(rec_zoom_label) - .addGap(34, 34, 34)) + .addContainerGap()) ); advanced_scrollpane.setViewportView(advanced_panel); @@ -2035,7 +2113,7 @@ public class SettingsDialog extends javax.swing.JDialog { .addComponent(save_button) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(cancel_button)) - .addComponent(panel_tabs, javax.swing.GroupLayout.DEFAULT_SIZE, 1194, Short.MAX_VALUE)) + .addComponent(panel_tabs)) .addContainerGap()) ); layout.setVerticalGroup( @@ -2128,6 +2206,8 @@ public class SettingsDialog extends javax.swing.JDialog { settings.put("smartproxy_ban_time", String.valueOf(bad_proxy_time_spinner.getValue())); settings.put("smartproxy_timeout", String.valueOf(proxy_timeout_spinner.getValue())); settings.put("smartproxy_autorefresh_time", String.valueOf(auto_refresh_proxy_time_spinner.getValue())); + settings.put("enable_remote_api", enable_remote_api_checkbox.isSelected() ? "yes" : "no"); + settings.put("remote_api_port", String.valueOf(remote_api_port_spinner.getValue())); if (upload_log_checkbox.isSelected()) { createUploadLogDir(); @@ -3042,208 +3122,6 @@ public class SettingsDialog extends javax.swing.JDialog { max_up_speed_spinner.setEnabled(limit_upload_speed_checkbox.isSelected()); }//GEN-LAST:event_limit_upload_speed_checkboxStateChanged - private void run_command_test_buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_run_command_test_buttonActionPerformed - // TODO add your handling code here: - - if (run_command_textbox.getText() != null && !"".equals(run_command_textbox.getText().trim())) { - - try { - Runtime.getRuntime().exec(run_command_textbox.getText().trim()); - } catch (IOException ex) { - Logger.getLogger(MiscTools.class.getName()).log(Level.SEVERE, ex.getMessage()); - JOptionPane.showMessageDialog(this, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); - } - } - }//GEN-LAST:event_run_command_test_buttonActionPerformed - - private void run_command_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_run_command_checkboxActionPerformed - // TODO add your handling code here: - - run_command_textbox.setEnabled(run_command_checkbox.isSelected()); - }//GEN-LAST:event_run_command_checkboxActionPerformed - - private void custom_chunks_dir_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_custom_chunks_dir_checkboxActionPerformed - - if (custom_chunks_dir_checkbox.isSelected()) { - - custom_chunks_dir_button.setEnabled(true); - custom_chunks_dir_current_label.setEnabled(true); - - } else { - - custom_chunks_dir_button.setEnabled(false); - custom_chunks_dir_current_label.setEnabled(false); - - } - }//GEN-LAST:event_custom_chunks_dir_checkboxActionPerformed - - private void custom_chunks_dir_buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_custom_chunks_dir_buttonActionPerformed - javax.swing.JFileChooser filechooser = new javax.swing.JFileChooser(); - updateFonts(filechooser, GUI_FONT, (float) (_main_panel.getZoom_factor() * 1.25)); - - filechooser.setCurrentDirectory(new java.io.File(_download_path)); - filechooser.setDialogTitle("Temporary chunks directory"); - filechooser.setFileSelectionMode(javax.swing.JFileChooser.DIRECTORIES_ONLY); - filechooser.setAcceptAllFileFilterUsed(false); - - if (filechooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { - - File file = filechooser.getSelectedFile(); - - _custom_chunks_dir = file.getAbsolutePath(); - - this.custom_chunks_dir_current_label.setText(truncateText(_custom_chunks_dir, 80)); - } - }//GEN-LAST:event_custom_chunks_dir_buttonActionPerformed - - private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed - // TODO add your handling code here: - - Object[] options = {"No", - LabelTranslatorSingleton.getInstance().translate("Yes")}; - - int n = showOptionDialog(this, - LabelTranslatorSingleton.getInstance().translate("ALL YOUR SETTINGS, ACCOUNTS AND TRANSFERENCES WILL BE REMOVED. (THIS CAN'T BE UNDONE)\n\nDo you want to continue?"), - LabelTranslatorSingleton.getInstance().translate("RESET MEGABASTERD"), YES_NO_CANCEL_OPTION, javax.swing.JOptionPane.WARNING_MESSAGE, - null, - options, - options[0]); - - if (n == 1) { - - setVisible(false); - _main_panel.byebyenow(true, true); - - } - }//GEN-LAST:event_jButton1ActionPerformed - - private void export_settings_buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_export_settings_buttonActionPerformed - - Object[] options = {"No", - LabelTranslatorSingleton.getInstance().translate("Yes")}; - - int n = showOptionDialog(this, - LabelTranslatorSingleton.getInstance().translate("Only SAVED settings and accounts will be exported. (If you are unsure, it is better to save your current settings and then export them).\n\nDo you want to continue?"), - LabelTranslatorSingleton.getInstance().translate("EXPORT SETTINGS"), YES_NO_CANCEL_OPTION, javax.swing.JOptionPane.WARNING_MESSAGE, - null, - options, - options[0]); - - if (n == 1) { - JFileChooser filechooser = new JFileChooser(); - updateFonts(filechooser, GUI_FONT, (float) (_main_panel.getZoom_factor() * 1.25)); - filechooser.setCurrentDirectory(new File(_download_path)); - filechooser.setDialogTitle("Save as"); - - if (filechooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) { - - File file = filechooser.getSelectedFile(); - - try { - - if (file.exists()) { - file.delete(); - } - - file.createNewFile(); - - try (BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file)); ObjectOutputStream oos = new ObjectOutputStream(fos)) { - - HashMap settings = new HashMap<>(); - - settings.put("settings", selectSettingsValues()); - - settings.put("mega_accounts", selectMegaAccounts()); - - settings.put("elc_accounts", selectELCAccounts()); - - oos.writeObject(settings); - - JOptionPane.showMessageDialog(this, LabelTranslatorSingleton.getInstance().translate("Settings successfully exported!"), LabelTranslatorSingleton.getInstance().translate("Settings exported"), JOptionPane.INFORMATION_MESSAGE); - - setVisible(false); - - } catch (SQLException ex) { - LOG.log(Level.SEVERE, ex.getMessage()); - } - - } catch (IOException ex) { - LOG.log(Level.SEVERE, ex.getMessage()); - } - } - } - }//GEN-LAST:event_export_settings_buttonActionPerformed - - private void import_settings_buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_import_settings_buttonActionPerformed - - Object[] options = {"No", - LabelTranslatorSingleton.getInstance().translate("Yes")}; - - int n = showOptionDialog(this, - LabelTranslatorSingleton.getInstance().translate("All your current settings and accounts will be deleted after import. (It is recommended to export your current settings before importing). \n\nDo you want to continue?"), - LabelTranslatorSingleton.getInstance().translate("IMPORT SETTINGS"), YES_NO_CANCEL_OPTION, javax.swing.JOptionPane.WARNING_MESSAGE, - null, - options, - options[0]); - - if (n == 1) { - JFileChooser filechooser = new JFileChooser(); - updateFonts(filechooser, GUI_FONT, (float) (_main_panel.getZoom_factor() * 1.25)); - filechooser.setCurrentDirectory(new File(_download_path)); - filechooser.setDialogTitle("Select settings file"); - - if (filechooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { - - File file = filechooser.getSelectedFile(); - - try { - - try (InputStream fis = new BufferedInputStream(new FileInputStream(file)); ObjectInputStream ois = new ObjectInputStream(fis)) { - - HashMap settings = (HashMap) ois.readObject(); - - insertSettingsValues((HashMap) settings.get("settings")); - - insertMegaAccounts((HashMap) settings.get("mega_accounts")); - - insertELCAccounts((HashMap) settings.get("elc_accounts")); - - _main_panel.loadUserSettings(); - - JOptionPane.showMessageDialog(this, LabelTranslatorSingleton.getInstance().translate("Settings successfully imported!"), LabelTranslatorSingleton.getInstance().translate("Settings imported"), JOptionPane.INFORMATION_MESSAGE); - - _settings_ok = true; - - setVisible(false); - - } catch (SQLException | ClassNotFoundException ex) { - LOG.log(Level.SEVERE, ex.getMessage()); - } - - } catch (IOException ex) { - LOG.log(Level.SEVERE, ex.getMessage()); - } - } - } - }//GEN-LAST:event_import_settings_buttonActionPerformed - - private void use_proxy_checkboxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_use_proxy_checkboxStateChanged - - proxy_host_label.setEnabled(use_proxy_checkbox.isSelected()); - proxy_host_textfield.setEnabled(use_proxy_checkbox.isSelected()); - proxy_port_label.setEnabled(use_proxy_checkbox.isSelected()); - proxy_port_textfield.setEnabled(use_proxy_checkbox.isSelected()); - proxy_user_label.setEnabled(use_proxy_checkbox.isSelected()); - proxy_user_textfield.setEnabled(use_proxy_checkbox.isSelected()); - proxy_pass_label.setEnabled(use_proxy_checkbox.isSelected()); - proxy_pass_textfield.setEnabled(use_proxy_checkbox.isSelected()); - proxy_warning_label.setEnabled(use_proxy_checkbox.isSelected()); - - if (use_proxy_checkbox.isSelected()) { - smart_proxy_checkbox.setSelected(false); - } - }//GEN-LAST:event_use_proxy_checkboxStateChanged - private void multi_slot_down_checkboxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_multi_slot_down_checkboxStateChanged if (!multi_slot_down_checkbox.isSelected() && !smart_proxy_checkbox.isSelected()) { @@ -3437,6 +3315,214 @@ public class SettingsDialog extends javax.swing.JDialog { }//GEN-LAST:event_proxy_sequential_radioActionPerformed + private void enable_remote_api_checkboxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_enable_remote_api_checkboxStateChanged + remote_api_port_label.setEnabled(enable_remote_api_checkbox.isSelected()); + remote_api_port_spinner.setEnabled(enable_remote_api_checkbox.isSelected()); + remote_api_warning_label.setEnabled(enable_remote_api_checkbox.isSelected()); + }//GEN-LAST:event_enable_remote_api_checkboxStateChanged + + private void export_settings_buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_export_settings_buttonActionPerformed + + Object[] options = {"No", + LabelTranslatorSingleton.getInstance().translate("Yes")}; + + int n = showOptionDialog(this, + LabelTranslatorSingleton.getInstance().translate("Only SAVED settings and accounts will be exported. (If you are unsure, it is better to save your current settings and then export them).\n\nDo you want to continue?"), + LabelTranslatorSingleton.getInstance().translate("EXPORT SETTINGS"), YES_NO_CANCEL_OPTION, javax.swing.JOptionPane.WARNING_MESSAGE, + null, + options, + options[0]); + + if (n == 1) { + JFileChooser filechooser = new JFileChooser(); + updateFonts(filechooser, GUI_FONT, (float) (_main_panel.getZoom_factor() * 1.25)); + filechooser.setCurrentDirectory(new File(_download_path)); + filechooser.setDialogTitle("Save as"); + + if (filechooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) { + + File file = filechooser.getSelectedFile(); + + try { + + if (file.exists()) { + file.delete(); + } + + file.createNewFile(); + + try (BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file)); ObjectOutputStream oos = new ObjectOutputStream(fos)) { + + HashMap settings = new HashMap<>(); + + settings.put("settings", selectSettingsValues()); + + settings.put("mega_accounts", selectMegaAccounts()); + + settings.put("elc_accounts", selectELCAccounts()); + + oos.writeObject(settings); + + JOptionPane.showMessageDialog(this, LabelTranslatorSingleton.getInstance().translate("Settings successfully exported!"), LabelTranslatorSingleton.getInstance().translate("Settings exported"), JOptionPane.INFORMATION_MESSAGE); + + setVisible(false); + + } catch (SQLException ex) { + LOG.log(Level.SEVERE, ex.getMessage()); + } + + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage()); + } + } + } + }//GEN-LAST:event_export_settings_buttonActionPerformed + + private void import_settings_buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_import_settings_buttonActionPerformed + + Object[] options = {"No", + LabelTranslatorSingleton.getInstance().translate("Yes")}; + + int n = showOptionDialog(this, + LabelTranslatorSingleton.getInstance().translate("All your current settings and accounts will be deleted after import. (It is recommended to export your current settings before importing). \n\nDo you want to continue?"), + LabelTranslatorSingleton.getInstance().translate("IMPORT SETTINGS"), YES_NO_CANCEL_OPTION, javax.swing.JOptionPane.WARNING_MESSAGE, + null, + options, + options[0]); + + if (n == 1) { + JFileChooser filechooser = new JFileChooser(); + updateFonts(filechooser, GUI_FONT, (float) (_main_panel.getZoom_factor() * 1.25)); + filechooser.setCurrentDirectory(new File(_download_path)); + filechooser.setDialogTitle("Select settings file"); + + if (filechooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + + File file = filechooser.getSelectedFile(); + + try { + + try (InputStream fis = new BufferedInputStream(new FileInputStream(file)); ObjectInputStream ois = new ObjectInputStream(fis)) { + + HashMap settings = (HashMap) ois.readObject(); + + insertSettingsValues((HashMap) settings.get("settings")); + + insertMegaAccounts((HashMap) settings.get("mega_accounts")); + + insertELCAccounts((HashMap) settings.get("elc_accounts")); + + _main_panel.loadUserSettings(); + + JOptionPane.showMessageDialog(this, LabelTranslatorSingleton.getInstance().translate("Settings successfully imported!"), LabelTranslatorSingleton.getInstance().translate("Settings imported"), JOptionPane.INFORMATION_MESSAGE); + + _settings_ok = true; + + setVisible(false); + + } catch (SQLException | ClassNotFoundException ex) { + LOG.log(Level.SEVERE, ex.getMessage()); + } + + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage()); + } + } + } + }//GEN-LAST:event_import_settings_buttonActionPerformed + + private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed + // TODO add your handling code here: + + Object[] options = {"No", + LabelTranslatorSingleton.getInstance().translate("Yes")}; + + int n = showOptionDialog(this, + LabelTranslatorSingleton.getInstance().translate("ALL YOUR SETTINGS, ACCOUNTS AND TRANSFERENCES WILL BE REMOVED. (THIS CAN'T BE UNDONE)\n\nDo you want to continue?"), + LabelTranslatorSingleton.getInstance().translate("RESET MEGABASTERD"), YES_NO_CANCEL_OPTION, javax.swing.JOptionPane.WARNING_MESSAGE, + null, + options, + options[0]); + + if (n == 1) { + + setVisible(false); + _main_panel.byebyenow(true, true); + + } + }//GEN-LAST:event_jButton1ActionPerformed + + private void run_command_test_buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_run_command_test_buttonActionPerformed + // TODO add your handling code here: + + if (run_command_textbox.getText() != null && !"".equals(run_command_textbox.getText().trim())) { + + try { + Runtime.getRuntime().exec(run_command_textbox.getText().trim()); + } catch (IOException ex) { + Logger.getLogger(MiscTools.class.getName()).log(Level.SEVERE, ex.getMessage()); + JOptionPane.showMessageDialog(this, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } + } + }//GEN-LAST:event_run_command_test_buttonActionPerformed + + private void run_command_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_run_command_checkboxActionPerformed + // TODO add your handling code here: + + run_command_textbox.setEnabled(run_command_checkbox.isSelected()); + }//GEN-LAST:event_run_command_checkboxActionPerformed + + private void custom_chunks_dir_checkboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_custom_chunks_dir_checkboxActionPerformed + + if (custom_chunks_dir_checkbox.isSelected()) { + + custom_chunks_dir_button.setEnabled(true); + custom_chunks_dir_current_label.setEnabled(true); + + } else { + + custom_chunks_dir_button.setEnabled(false); + custom_chunks_dir_current_label.setEnabled(false); + + } + }//GEN-LAST:event_custom_chunks_dir_checkboxActionPerformed + + private void custom_chunks_dir_buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_custom_chunks_dir_buttonActionPerformed + javax.swing.JFileChooser filechooser = new javax.swing.JFileChooser(); + updateFonts(filechooser, GUI_FONT, (float) (_main_panel.getZoom_factor() * 1.25)); + + filechooser.setCurrentDirectory(new java.io.File(_download_path)); + filechooser.setDialogTitle("Temporary chunks directory"); + filechooser.setFileSelectionMode(javax.swing.JFileChooser.DIRECTORIES_ONLY); + filechooser.setAcceptAllFileFilterUsed(false); + + if (filechooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + + File file = filechooser.getSelectedFile(); + + _custom_chunks_dir = file.getAbsolutePath(); + + this.custom_chunks_dir_current_label.setText(truncateText(_custom_chunks_dir, 80)); + } + }//GEN-LAST:event_custom_chunks_dir_buttonActionPerformed + + private void use_proxy_checkboxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_use_proxy_checkboxStateChanged + + proxy_host_label.setEnabled(use_proxy_checkbox.isSelected()); + proxy_host_textfield.setEnabled(use_proxy_checkbox.isSelected()); + proxy_port_label.setEnabled(use_proxy_checkbox.isSelected()); + proxy_port_textfield.setEnabled(use_proxy_checkbox.isSelected()); + proxy_user_label.setEnabled(use_proxy_checkbox.isSelected()); + proxy_user_textfield.setEnabled(use_proxy_checkbox.isSelected()); + proxy_pass_label.setEnabled(use_proxy_checkbox.isSelected()); + proxy_pass_textfield.setEnabled(use_proxy_checkbox.isSelected()); + proxy_warning_label.setEnabled(use_proxy_checkbox.isSelected()); + + if (use_proxy_checkbox.isSelected()) { + smart_proxy_checkbox.setSelected(false); + } + }//GEN-LAST:event_use_proxy_checkboxStateChanged + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JPanel accounts_panel; private javax.swing.JButton add_elc_account_button; @@ -3468,6 +3554,7 @@ public class SettingsDialog extends javax.swing.JDialog { private javax.swing.JLabel elc_accounts_label; private javax.swing.JScrollPane elc_accounts_scrollpane; private javax.swing.JTable elc_accounts_table; + private javax.swing.JCheckBox enable_remote_api_checkbox; private javax.swing.JCheckBox encrypt_pass_checkbox; private javax.swing.JButton export_settings_button; private javax.swing.JComboBox font_combo; @@ -3533,6 +3620,10 @@ public class SettingsDialog extends javax.swing.JDialog { private javax.swing.JLabel rec_smart_proxy_label1; private javax.swing.JLabel rec_upload_slots_label; private javax.swing.JLabel rec_zoom_label; + private javax.swing.JPanel remote_api_panel; + private javax.swing.JLabel remote_api_port_label; + private javax.swing.JSpinner remote_api_port_spinner; + private javax.swing.JLabel remote_api_warning_label; private javax.swing.JButton remove_elc_account_button; private javax.swing.JButton remove_mega_account_button; private javax.swing.JCheckBox run_command_checkbox; diff --git a/src/main/java/com/tonikelope/megabasterd/SpeedMeter.java b/src/main/java/com/tonikelope/megabasterd/SpeedMeter.java index 7f0446ad0..60b292afe 100644 --- a/src/main/java/com/tonikelope/megabasterd/SpeedMeter.java +++ b/src/main/java/com/tonikelope/megabasterd/SpeedMeter.java @@ -34,6 +34,7 @@ public class SpeedMeter implements Runnable { private long _speed_counter; private long _speed_acumulator; private volatile long _max_avg_global_speed; + private volatile long _current_speed; SpeedMeter(TransferenceManager trans_manager, JLabel sp_label, JLabel rem_label) { _speed_label = sp_label; @@ -43,6 +44,7 @@ public class SpeedMeter implements Runnable { _speed_counter = 0L; _speed_acumulator = 0L; _max_avg_global_speed = 0L; + _current_speed = 0L; } private long _getAvgGlobalSpeed() { @@ -73,6 +75,11 @@ public class SpeedMeter implements Runnable { return _max_avg_global_speed; } + public long getCurrentSpeed() { + + return _max_avg_global_speed; + } + private String calcRemTime(long seconds) { int days = (int) TimeUnit.SECONDS.toDays(seconds); @@ -159,6 +166,8 @@ public class SpeedMeter implements Runnable { long trans_sp = calcTransferenceSpeed(trans_info.getKey(), trans_info.getValue()); + trans_info.getKey().setSpeed(trans_sp); + if (trans_sp >= 0) { global_speed += trans_sp; } @@ -191,6 +200,8 @@ public class SpeedMeter implements Runnable { _speed_label.setText(formatBytes(global_speed) + "/s"); + _current_speed = global_speed; + _rem_label.setText(formatBytes(global_progress) + "/" + formatBytes(global_size) + " @ " + formatBytes(avg_global_speed) + "/s @ " + calcRemTime((long) Math.floor((global_size - global_progress) / avg_global_speed))); } else { diff --git a/src/main/java/com/tonikelope/megabasterd/Transference.java b/src/main/java/com/tonikelope/megabasterd/Transference.java index 0a7abb90d..8eaf490a1 100644 --- a/src/main/java/com/tonikelope/megabasterd/Transference.java +++ b/src/main/java/com/tonikelope/megabasterd/Transference.java @@ -73,6 +73,10 @@ public interface Transference { void setProgress(long progress); + long getSpeed(); + + void setSpeed(long speed); + String getFile_name(); long getFile_size(); diff --git a/src/main/java/com/tonikelope/megabasterd/Upload.java b/src/main/java/com/tonikelope/megabasterd/Upload.java index 39858a9f2..6c3286a86 100644 --- a/src/main/java/com/tonikelope/megabasterd/Upload.java +++ b/src/main/java/com/tonikelope/megabasterd/Upload.java @@ -57,6 +57,7 @@ public class Upload implements Transference, Runnable, SecureSingleThreadNotifia private final Object _chunkid_lock; private byte[] _byte_file_key; private volatile long _progress; + private volatile long _speed; private byte[] _byte_file_iv; private final ConcurrentLinkedQueue _rejectedChunkIds; private long _last_chunk_id_dispatched; @@ -210,6 +211,11 @@ public class Upload implements Transference, Runnable, SecureSingleThreadNotifia return _progress; } + @Override + public long getSpeed() { + return _speed; + } + public byte[] getByte_file_iv() { return _byte_file_iv; } @@ -1297,6 +1303,11 @@ public class Upload implements Transference, Runnable, SecureSingleThreadNotifia } } + @Override + public void setSpeed(long speed) { + _speed = speed; + } + @Override public boolean isStatusError() { return _status_error != null; diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 000000000..eb6ccc772 --- /dev/null +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: com.tonikelope.megabasterd.MainPanel +