trying to clear up the "Save As" progress frame and avoid bugs

This commit is contained in:
Ben Fry
2015-09-21 15:53:14 -04:00
parent 2f0641e5c7
commit dc64dbbedf
6 changed files with 229 additions and 240 deletions
+188 -19
View File
@@ -24,21 +24,25 @@
package processing.app;
import processing.app.ui.Editor;
import processing.app.ui.ProgressFrame;
import processing.app.ui.Recent;
import processing.app.ui.Toolkit;
import processing.core.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.FileDialog;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.util.List;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
/**
@@ -792,7 +796,7 @@ public class Sketch {
String newParentDir = null;
String newName = null;
final String oldName2 = folder.getName();
String oldName = folder.getName();
// TODO rewrite this to use shared version from PApplet
final String PROMPT = Language.text("save");
if (Preferences.getBoolean("chooser.files.native")) {
@@ -805,8 +809,8 @@ public class Sketch {
// default to the parent folder of where this was
fd.setDirectory(folder.getParent());
}
String oldName = folder.getName();
fd.setFile(oldName);
String oldFolderName = folder.getName();
fd.setFile(oldFolderName);
fd.setVisible(true);
newParentDir = fd.getDirectory();
newName = fd.getFile();
@@ -868,7 +872,7 @@ public class Sketch {
// just use "save" here instead, because the user will have received a
// message (from the operating system) about "do you want to replace?"
return save();
}
}
// check to see if the user is trying to save this sketch inside itself
try {
@@ -927,21 +931,21 @@ public class Sketch {
}
});
// Kick off a background thread to copy everything *but* the .pde files.
// Due to the poor way (dating back to the late 90s with DBN) that our
// save() and saveAs() methods have been implemented to return booleans,
// there isn't a good way to return a value to the calling thread without
// a good bit of refactoring (that should be done at some point).
// As a result, this method will return 'true' before the full "Save As"
// has completed, which will cause problems in weird cases.
// For instance, saving an untitled sketch that has an enormous data
// folder while quitting. The save thread to move those data folder files
// won't have finished before this returns true, and the PDE may quit
// before the SwingWorker completes its job.
// https://github.com/processing/processing/issues/3843
startSaveAsThread(oldName, newName, newFolder, copyItems);
final File newFolder2 = newFolder;
final File[] copyItems2 = copyItems;
final String newName2 = newName;
// Create a new event dispatch thread- to display ProgressBar
// while Saving As
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ProgressFrame(copyItems2, newFolder2, oldName2, newName2, editor);
}
});
// save the other tabs to their new location
// save the other tabs to their new location (main tab saved below)
for (int i = 1; i < codeCount; i++) {
File newFile = new File(newFolder, code[i].getFileName());
code[i].saveAs(newFile);
@@ -971,6 +975,171 @@ public class Sketch {
}
void startSaveAsThread(final String oldName, final String newName,
final File newFolder, final File[] copyItems) {
EventQueue.invokeLater(new Runnable() {
public void run() {
final JFrame frame = new JFrame("Saving \u201C" + newName + "\u201C...");
// the UI of the progress bar follows
frame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
// frame.setBounds(200, 200, 400, 140);
// frame.setResizable(false);
Box box = Box.createVerticalBox();
box.setBorder(new EmptyBorder(16, 16, 16, 16));
// JPanel panel = new JPanel(null);
// frame.add(panel);
// frame.setContentPane(panel); // uh, both?
if (Platform.isMacOS()) {
frame.setBackground(Color.WHITE);
}
JLabel label = new JLabel("Saving additional files from the sketch folder...");
// JLabel label = new JLabel("Saving " + oldName + " as " + newName + "...");
// label.setBounds(40, 20, 300, 20);
box.add(label);
final JProgressBar progressBar = new JProgressBar(0, 100);
// no luck, stuck with ugly on OS X
//progressBar.putClientProperty("JComponent.sizeVariant", "regular");
progressBar.setValue(0);
// progressBar.setBounds(40, 50, 300, 30);
progressBar.setStringPainted(true);
box.add(progressBar);
// panel.add(progressBar);
// panel.add(label);
frame.getContentPane().add(box);
frame.pack();
frame.setLocationRelativeTo(editor);
Toolkit.setIcon(frame);
frame.setVisible(true);
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
System.out.println(evt.getNewValue());
progressBar.setValue((Integer) evt.getNewValue());
}
}
});
long totalSize = 0;
for (File copyable : copyItems) {
totalSize += Util.calcSize(copyable);
}
long progress = 0;
setProgress(0);
for (File copyable : copyItems) {
if (copyable.isDirectory()) {
copyDir(copyable,
new File(newFolder, copyable.getName()),
progress, totalSize);
progress += Util.calcSize(copyable);
} else {
copyFile(copyable,
new File(newFolder, copyable.getName()),
progress, totalSize);
if (Util.calcSize(copyable) < 512 * 1024) {
// If the file length > 0.5MB, the copyFile() function has
// been redesigned to change progress every 0.5MB so that
// the progress bar doesn't stagnate during that time
progress += Util.calcSize(copyable);
setProgress((int) (progress * 100L / totalSize));
}
}
}
return null;
}
/**
* Overloaded copyFile that is called whenever a Save As is being done,
* so that the ProgressBar is updated for very large files as well.
*/
void copyFile(File sourceFile, File targetFile,
long progress, long totalSize) throws IOException {
BufferedInputStream from =
new BufferedInputStream(new FileInputStream(sourceFile));
BufferedOutputStream to =
new BufferedOutputStream(new FileOutputStream(targetFile));
byte[] buffer = new byte[16 * 1024];
int bytesRead;
int progRead = 0;
while ((bytesRead = from.read(buffer)) != -1) {
to.write(buffer, 0, bytesRead);
progRead += bytesRead;
if (progRead >= 512 * 1024) { // to update progress bar every 0.5MB
progress += progRead;
//progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
setProgress((int) (100L * progress / totalSize));
progRead = 0;
}
}
// Final update to progress bar
setProgress((int) (100L * progress / totalSize));
from.close();
from = null;
to.flush();
to.close();
to = null;
targetFile.setLastModified(sourceFile.lastModified());
targetFile.setExecutable(sourceFile.canExecute());
}
long copyDir(File sourceDir, File targetDir,
long progress, long totalSize) throws IOException {
// Overloaded copyDir so that the Save As progress bar gets updated when the
// files are in folders as well (like in the data folder)
if (sourceDir.equals(targetDir)) {
final String urDum = "source and target directories are identical";
throw new IllegalArgumentException(urDum);
}
targetDir.mkdirs();
String files[] = sourceDir.list();
for (String filename : files) {
// Ignore dot files (.DS_Store), dot folders (.svn) while copying
if (filename.charAt(0) == '.') {
continue;
}
File source = new File(sourceDir, filename);
File target = new File(targetDir, filename);
if (source.isDirectory()) {
progress = copyDir(source, target, progress, totalSize);
//progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
setProgress((int) (100L * progress / totalSize));
target.setLastModified(source.lastModified());
} else {
copyFile(source, target, progress, totalSize);
progress += source.length();
//progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
setProgress((int) (100L * progress / totalSize));
}
}
return progress;
}
@Override
public void done() {
frame.dispose();
}
}.execute();
}
});
}
/**
* Update internal state for new sketch name or folder location.
+11 -1
View File
@@ -340,12 +340,22 @@ public class Util {
}
/**
* Function to return the length of the file, or entire directory, including
* the component files and sub-folders if passed.
* @param file The file or folder to calculate
*/
static public long calcSize(File file) {
return file.isFile() ? file.length() : Util.calcFolderSize(file);
}
/**
* Calculate the size of the contents of a folder.
* Used to determine whether sketches are empty or not.
* Note that the function calls itself recursively.
*/
static public int calcFolderSize(File folder) {
static public long calcFolderSize(File folder) {
int size = 0;
String files[] = folder.list();
+23
View File
@@ -2540,6 +2540,29 @@ public abstract class Editor extends JFrame implements RunnerListener {
}
/*
public void handleSaveAs() {
statusNotice(Language.text("editor.status.saving"));
sketch.saveAs();
}
public void handleSaveAsSuccess() {
statusNotice(Language.text("editor.status.saving.done"));
}
public void handleSaveAsCanceled() {
statusNotice(Language.text("editor.status.saving.canceled"));
}
public void handleSaveAsError(Exception e) {
statusError(e);
}
*/
/**
* Handler for File &rarr; Page Setup.
*/
@@ -15,9 +15,6 @@ import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import processing.app.Language;
import processing.app.Util;
// TODO This code was contributed and needs a lot of work. [fry]
// + It probably shouldn't a "Frame" object at all.
@@ -33,137 +30,7 @@ import processing.app.Util;
public class ProgressFrame extends JFrame implements PropertyChangeListener {
private JProgressBar progressBar;
private JLabel label;
private File[] copyItems;
private File newFolder;
private File addFile, sourceFile;
private Editor editor;
/** Create a new background thread to Save As */
public class TaskSaveAs extends SwingWorker<Void, Void> {
@Override
protected Void doInBackground() throws Exception {
// a large part of the file copying happens in this background
// thread
long totalSize = 0;
for (File copyable : copyItems) {
totalSize += calcSize(copyable);
}
long progress = 0;
setProgress(0);
for (File copyable : ProgressFrame.this.copyItems) {
// loop to copy over the items that make sense, and to set the
// current progress
if (copyable.isDirectory()) {
copyDir(copyable,
new File(ProgressFrame.this.newFolder, copyable.getName()),
progress, totalSize);
progress += calcSize(copyable);
} else {
copyFile(copyable,
new File(ProgressFrame.this.newFolder, copyable.getName()),
progress, totalSize);
if (calcSize(copyable) < 524288) {
// If the file length > 0.5MB, the copyFile() function has
// been redesigned to change progress every 0.5MB so that
// the progress bar doesn't stagnate during that time
progress += calcSize(copyable);
setProgress((int) (progress * 100L / totalSize));
}
}
}
return null;
}
/**
* Overloaded copyFile that is called whenever a Save As is being done,
* so that the ProgressBar is updated for very large files as well.
*/
private void copyFile(File sourceFile, File targetFile,
double progress, long totalSize) throws IOException {
BufferedInputStream from = new BufferedInputStream(new FileInputStream(sourceFile));
BufferedOutputStream to = new BufferedOutputStream(new FileOutputStream(targetFile));
byte[] buffer = new byte[16 * 1024];
int bytesRead;
int totalRead = 0;
while ((bytesRead = from.read(buffer)) != -1) {
to.write(buffer, 0, bytesRead);
totalRead += bytesRead;
if (totalRead >= 512 * 1024) { // to update progress bar every 0.5MB
progress += totalRead;
setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
totalRead = 0;
}
}
if (sourceFile.length() > 512 * 1024) {
// Update the progress bar one final time if file size is more than 0.5MB,
// otherwise, the update is handled either by the copyDir function,
// or directly by ProgressFrame.TaskSaveAs.doInBackground()
progress += totalRead;
setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
}
from.close();
from = null;
to.flush();
to.close();
to = null;
targetFile.setLastModified(sourceFile.lastModified());
targetFile.setExecutable(sourceFile.canExecute());
}
private double copyDir(File sourceDir, File targetDir,
double progress, long totalSize) throws IOException {
// Overloaded copyDir so that the Save As progress bar gets updated when the
// files are in folders as well (like in the data folder)
if (sourceDir.equals(targetDir)) {
final String urDum = "source and target directories are identical";
throw new IllegalArgumentException(urDum);
}
targetDir.mkdirs();
String files[] = sourceDir.list();
for (int i = 0; i < files.length; i++) {
// Ignore dot files (.DS_Store), dot folders (.svn) while copying
if (files[i].charAt(0) == '.')
continue;
//if (files[i].equals(".") || files[i].equals("..")) continue;
File source = new File(sourceDir, files[i]);
File target = new File(targetDir, files[i]);
if (source.isDirectory()) {
//target.mkdirs();
progress = copyDir(source, target, progress, totalSize);
setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
target.setLastModified(source.lastModified());
} else {
copyFile(source, target, progress, totalSize);
// Update SaveAs progress bar
progress += source.length();
setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
}
}
return progress;
}
public void setProgressBarStatus(int status) {
setProgress(status);
}
@Override
public void done() {
// to close the progress bar automatically when done, and to
// print that Saving is done in Message Area
editor.statusNotice(Language.text("editor.status.saving.done"));
dispose();
}
}
/** Create a new background thread to add a file. */
@@ -245,49 +112,10 @@ public class ProgressFrame extends JFrame implements PropertyChangeListener {
}
/** Use for Save As */
public ProgressFrame(File[] c, File nf, String oldName, String newName,
Editor editor) {
// initialize a copyItems and newFolder, which are used for file
// copying in the background thread
copyItems = c;
newFolder = nf;
this.editor = editor;
// the UI of the progress bar follows
setDefaultCloseOperation(HIDE_ON_CLOSE);
setBounds(200, 200, 400, 140);
setResizable(false);
setTitle("Saving As...");
JPanel panel = new JPanel(null);
add(panel);
setContentPane(panel);
label = new JLabel("Saving " + oldName + " as " + newName + "...");
label.setBounds(40, 20, 300, 20);
progressBar = new JProgressBar(0, 100);
progressBar.setValue(0);
progressBar.setBounds(40, 50, 300, 30);
progressBar.setStringPainted(true);
panel.add(progressBar);
panel.add(label);
Toolkit.setIcon(this);
this.setVisible(true);
// create an instance of TaskSaveAs and run execute() on this
// instance to start background thread
TaskSaveAs t = new TaskSaveAs();
t.addPropertyChangeListener(this);
t.execute();
}
/** Used for Add File */
public ProgressFrame(File sf, File add, Editor editor) {
addFile = add;
sourceFile = sf;
this.editor = editor;
// the UI of the progress bar follows
setDefaultCloseOperation(HIDE_ON_CLOSE);
@@ -319,15 +147,6 @@ public class ProgressFrame extends JFrame implements PropertyChangeListener {
}
/**
* Function to return the length of the file, or entire directory, including
* the component files and sub-folders if passed.
*/
long calcSize(File file) {
return file.isFile() ? file.length() : Util.calcFolderSize(file);
}
/**
* Detects a change in the property of the background task,
* i.e., is called when the size of files already copied changes.
@@ -1730,39 +1730,6 @@ public class JavaEditor extends Editor {
@Override
public boolean handleSave(boolean immediately) {
//System.out.println("handleSave " + immediately);
//log("handleSave, viewing autosave? " + viewingAutosaveBackup);
/* If user wants to save a backup, the backup sketch should get
* copied to the main sketch directory, simply reload the main sketch.
*/
if(viewingAutosaveBackup){
/*
File files[] = autosaver.getSketchBackupFolder().listFiles();
File src = autosaver.getSketchBackupFolder(), dst = autosaver
.getActualSketchFolder();
for (File f : files) {
log("Copying " + f.getAbsolutePath() + " to " + dst.getAbsolutePath());
try {
if (f.isFile()) {
f.delete();
Base.copyFile(f, new File(dst + File.separator + f.getName()));
} else {
Base.removeDir(f);
Base.copyDir(f, new File(dst + File.separator + f.getName()));
}
} catch (IOException e) {
e.printStackTrace();
}
}
File sk = autosaver.getActualSketchFolder();
Base.removeDir(autosaver.getAutoSaveDir());
//handleOpenInternal(sk.getAbsolutePath() + File.separator + sk.getName() + ".pde");
getBase().handleOpen(sk.getAbsolutePath() + File.separator + sk.getName() + ".pde");
//viewingAutosaveBackup = false;
*/
}
// note modified tabs
final List<String> modified = new ArrayList<String>();
for (int i = 0; i < getSketch().getCodeCount(); i++) {
@@ -1795,9 +1762,6 @@ public class JavaEditor extends Editor {
}
private boolean viewingAutosaveBackup;
/**
* Set text contents of a specific tab. Updates underlying document and text
* area. Clears Breakpoints.
+7 -3
View File
@@ -73,10 +73,11 @@ X https://github.com/processing/processing/issues/3806
X https://github.com/processing/processing/pull/3817
X CM list should be sortable by status and author name
X https://github.com/processing/processing/issues/3608
X "update all" button appears to do nothing in library manager
X https://github.com/processing/processing/issues/3837
X https://github.com/processing/processing/pull/3842
_ All contributions listed after install button is clicked
_ https://github.com/processing/processing/issues/3826
_ "update all" button appears to do nothing in library manager
_ https://github.com/processing/processing/issues/3837
jakub
X Make preprocessor scope-aware
@@ -232,7 +233,8 @@ _ pass through the source to update licenses
_ add Processing Foundation as 2012-15
_ update license info to state gplv2 not v3
_ run through that online license checker
_ save() and saveAs() need to be refactored
_ https://github.com/processing/processing/issues/3843
breakage
@@ -962,6 +964,8 @@ _ this may already work with SingleInstance stuff
DIST / Mac OS X
_ client properties
_ https://developer.apple.com/library/mac/technotes/tn2007/tn2196.html
_ built-in images: http://nadeausoftware.com/articles/2008/12/mac_java_tip_how_access_mac_specific_nsimage_icons
_ replace appbundler with the Java 8 packager
_ https://github.com/processing/processing/issues/3071