Duplicated clipboard input - history
Closed this issue · 3 comments
This issue elaborates about Duplicated Clipboard Input bug, and it's fixing progress so far.
When does it happens?
- This bug occurs when user press
ctrl + c
too long, or copy viaright click - copy link
, or some strange ways.
What problem does it causes?
- It this bug occurs, multiple task that download same file in same destination are generated.
- Since multiple tasks try to access&modify(e.g. while extracting audio, or deleting temp file) same file. It produces I/O error.
- In Windows, following error(s) will occur.
[WinError 2] The system cannot find the file specified
[WinError 32] The process cannot access the file because it is being used by another process: filename -> filename
- In Windows, following error(s) will occur.
It's an annoying bug, and does not die!
- It's very annoying & hard to fix bug, since ultimate problem is responsible to buggy behavior of
Clipboard
class in jvm, and it is hard to find out whether the duplicate inputs were user's intentional(downloading same video twice) or caused by the bug.
Below are commits related to this issue, and fix progress that I tried earlier.
In 1df61d6, isRedundant
method is used to check if two same input was captured in a row.
private static void checkClipBoard() {
clipChecker.submit(() -> { //To avoid overhead in EDT, put task in clipCheckerThread.
try {
Thread.sleep(50);
final String data = (String) Toolkit.getDefaultToolkit().getSystemClipboard()
.getData(DataFlavor.stringFlavor);
// System.out.println("data : " + data + "\nex-clipboard : " + clipboardBefore);
if(isRedundant(data)) return;
clipboardBefore = data;
submitDownload(data);
} catch (InterruptedException | HeadlessException | UnsupportedFlavorException | IOException e1) {
GUI.error("Error! : ", "%e%", e1);
}
});
}
private static boolean isRedundant(String data) throws InterruptedException {
if (data.equals(clipboardBefore)) {
if (isSecondtime) { //second time finding same data
Thread.sleep(50);
clearClipboardBefore();
} else { isSecondtime = true;}
return true;
} else { return false;}
}
...
private static void clearClipboardBefore() { clipboardBefore = ""; }
This prevent jvm bug that invoking FlavorListener twice, but if buggy inputs were more than two, It still accepted and caused error.
In 13725b6, ask user to download or not if a task with same url&destination exists.
TaskData t = new TaskData(num);
t.setVideoName(data); // temporarily set video name as url
t.setDest(Config.getSaveto());
t.setStatus("Validating...");
if(TaskStatusModel.getinstance().isTaskExits(t)) {
if(!GUI.confirm("Download same file in same directory?", data + "is already downloading in " + Config.getSaveto() + ", download anyway?")) {
log("[Task" + num + "] is cancelled because same download exists.");
return;
}
}
t.setFuture( executorService.submit(() -> { // download worker thread exist
t.setVideoName(data); // temporarily set video name as url
t.setDest(Config.getSaveto());
t.setStatus("Validating...");
TaskStatusModel.getinstance().addTask(t);
......
--------------------TaskData.java--------------------
@Override
public int hashCode() {
return Objects.hash(dest, taskNum, videoName, videoNum);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof TaskData)) {
return false;
}
TaskData other = (TaskData) obj;
return Objects.equals(dest, other.dest) && Objects.equals(videoName, other.videoName);
}
This can check if the duplicate inputs were intentional or buggy, but if there were many buggy inputs(like long pressing ctrl + c), lots of confirmation dialog will pop up.
After that in ce24dd8, if existing task is not done yet, consider it as a buggy duplicate input and ignores it without confirm.
if(TaskStatusModel.getinstance().isTaskExists(t)) {
if(!TaskStatusModel.getinstance().isTaskDone(t)) {
System.out.println("skip" + t);
return;
}
if(!GUI.confirm("Download same file in same directory?", data + "\nis already downloaded (by another Task) in\n" Config.getSaveto() + "\ndownload anyway?")) {
log("[Task" + num + "] is cancelled because same download exists.");
System.out.println("killed " + t);
return;
}
}
TaskStatusModel.getinstance().addTask(t);
--------------------TaskStatusModel.java--------------------
public boolean isTaskDone(TaskData t) {
if(!isTaskExists(t)) { return false; }
if (EventQueue.isDispatchThread()) {
return "Done!".equals(rows.get(rows.indexOf(t)).getStatus());
} else {
final AtomicReference<Boolean> result = new AtomicReference<>();
try {
SwingUtilities.invokeAndWait(() -> {
result.set("Done!".equals(rows.get(rows.indexOf(t)).getStatus()));
});
return result.get();
} catch (Exception e) {
Main.log("Exception when checking existing Task(s) is done");
Main.log(e);
}
return false;
}
}
isTaskDone(TaskData t)
determines if a task is done by checking hardcoded String
value of TaskData.status
whose ori displayed on JTable
.
This does not let user re-download same video that failed in previous attempt, since failed task's TaskData.status
is "ERROR"
.
So in that case, user has to remove previous task from the JTable
before re-download same file in same destination.