diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 33e8ff410..4e40f8c34 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -28,9 +28,12 @@ import java.io.*; import java.util.*; import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; import processing.app.debug.Compiler; import processing.app.debug.Target; +import processing.app.helpers.FileUtils; +import processing.app.helpers.filefilters.OnlyDirs; import processing.app.tools.ZipDeflater; import processing.core.*; import static processing.app.I18n._; @@ -949,9 +952,10 @@ public class Base { JMenuItem addLibraryMenuItem = new JMenuItem(_("Add Library...")); addLibraryMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - Base.this.handleAddZipLibrary(editor); + Base.this.handleAddLibrary(editor); Base.this.onBoardOrPortChange(); Base.this.rebuildImportMenu(Editor.importMenu, editor); + Base.this.rebuildExamplesMenu(Editor.examplesMenu); } }); importMenu.add(addLibraryMenuItem); @@ -2374,24 +2378,70 @@ public class Base { } } + public void handleAddLibrary(Editor editor) { + JFileChooser fileChooser = new JFileChooser(System.getProperty("user.home")); + fileChooser.setDialogTitle(_("Select a zip file or a folder containing the library you'd like to add")); + fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + fileChooser.setFileFilter(new FileNameExtensionFilter(_("ZIP files or folders"), "zip")); - public void handleAddZipLibrary(Editor editor) { - String prompt = _("Select a zip file containing the library you'd like to add"); - FileDialog fd = new FileDialog(editor, prompt, FileDialog.LOAD); - fd.setDirectory(System.getProperty("user.home")); - fd.setVisible(true); + Dimension preferredSize = fileChooser.getPreferredSize(); + fileChooser.setPreferredSize(new Dimension(preferredSize.width + 200, preferredSize.height + 200)); - String directory = fd.getDirectory(); - String filename = fd.getFile(); - if (filename == null) return; + int returnVal = fileChooser.showOpenDialog(editor); + + if (returnVal != JFileChooser.APPROVE_OPTION) { + return; + } + + File sourceFile = fileChooser.getSelectedFile(); + File tmpFolder = null; - File sourceFile = new File(directory, filename); try { - ZipDeflater zipDeflater = new ZipDeflater(sourceFile, getSketchbookLibrariesFolder()); - zipDeflater.deflate(); + // unpack ZIP + if (!sourceFile.isDirectory()) { + try { + tmpFolder = FileUtils.createTempFolder(); + ZipDeflater zipDeflater = new ZipDeflater(sourceFile, tmpFolder); + zipDeflater.deflate(); + File[] foldersInTmpFolder = tmpFolder.listFiles(new OnlyDirs()); + if (foldersInTmpFolder.length != 1) { + throw new IOException(_("Zip doesn't contain a library")); + } + sourceFile = foldersInTmpFolder[0]; + } catch (IOException e) { + editor.statusError(e); + return; + } + } + + // is there a valid library? + File libFolder = sourceFile; + String libName = libFolder.getName(); + if (!Sketch.isSanitaryName(libName)) { + String mess = I18n.format(_("The library \"{0}\" cannot be used.\n" + + "Library names must contain only basic letters and numbers.\n" + + "(ASCII only and no spaces, and it cannot start with a number)"), + libName); + editor.statusError(mess); + return; + } + + // copy folder + File destinationFolder = new File(getSketchbookLibrariesFolder(), sourceFile.getName()); + if (!destinationFolder.mkdir()) { + editor.statusError(I18n.format(_("A library named {0} already exists"), sourceFile.getName())); + return; + } + try { + FileUtils.copy(sourceFile, destinationFolder); + } catch (IOException e) { + editor.statusError(e); + return; + } editor.statusNotice(_("Library added to your libraries. Check \"Import library\" menu")); - } catch (IOException e) { - editor.statusError(e); + } finally { + // delete zip created temp folder, if exists + FileUtils.recursiveDelete(tmpFolder); } } } diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 4fc2d13cd..774d20f74 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -177,7 +177,7 @@ public class Editor extends JFrame implements RunnerListener { // re-add the sub-menus that are shared by all windows fileMenu.insert(sketchbookMenu, 2); fileMenu.insert(examplesMenu, 3); - //sketchMenu.insert(importMenu, 4); + sketchMenu.insert(importMenu, 4); toolsMenu.insert(boardsMenu, numTools); toolsMenu.insert(serialMenu, numTools + 1); } @@ -188,7 +188,7 @@ public class Editor extends JFrame implements RunnerListener { // System.err.println("deactivate"); // not coming through fileMenu.remove(sketchbookMenu); fileMenu.remove(examplesMenu); - //sketchMenu.remove(importMenu); + sketchMenu.remove(importMenu); toolsMenu.remove(boardsMenu); toolsMenu.remove(serialMenu); } diff --git a/app/src/processing/app/helpers/FileUtils.java b/app/src/processing/app/helpers/FileUtils.java new file mode 100644 index 000000000..47c5b0a32 --- /dev/null +++ b/app/src/processing/app/helpers/FileUtils.java @@ -0,0 +1,93 @@ +package processing.app.helpers; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Random; + +public class FileUtils { + + /** + * Checks, whether the child directory is a subdirectory of the base directory. + * + * @param base + * the base directory. + * @param child + * the suspected child directory. + * @return true, if the child is a subdirectory of the base directory. + */ + public static boolean isSubDirectory(File base, File child) { + try { + base = base.getCanonicalFile(); + child = child.getCanonicalFile(); + } catch (IOException e) { + return false; + } + + File parentFile = child; + while (parentFile != null) { + if (base.equals(parentFile)) { + return true; + } + parentFile = parentFile.getParentFile(); + } + return false; + } + + public static void copy(File sourceFolder, File destFolder) throws IOException { + for (File file : sourceFolder.listFiles()) { + File destFile = new File(destFolder, file.getName()); + if (file.isDirectory()) { + if (!destFile.mkdir()) { + throw new IOException("Unable to create folder: " + destFile); + } + copy(file, destFile); + } else { + FileInputStream fis = null; + FileOutputStream fos = null; + try { + fis = new FileInputStream(file); + fos = new FileOutputStream(destFile); + byte[] buf = new byte[4096]; + int readBytes = -1; + while ((readBytes = fis.read(buf, 0, buf.length)) != -1) { + fos.write(buf, 0, readBytes); + } + } finally { + if (fis != null) { + fis.close(); + } + if (fos != null) { + fos.close(); + } + } + } + } + } + + public static void recursiveDelete(File file) { + if (file == null) { + return; + } + if (file.isDirectory()) { + for (File current : file.listFiles()) { + if (current.isDirectory()) { + recursiveDelete(current); + } else { + current.delete(); + } + } + } + file.delete(); + } + + public static File createTempFolder() throws IOException { + File tmpFolder = new File(System.getProperty("java.io.tmpdir"), "arduino_" + new Random().nextInt(1000000)); + if (!tmpFolder.mkdir()) { + throw new IOException("Unable to create temp folder " + tmpFolder); + } + return tmpFolder; + } + +} diff --git a/app/src/processing/app/helpers/filefilters/OnlyDirs.java b/app/src/processing/app/helpers/filefilters/OnlyDirs.java new file mode 100644 index 000000000..46f407248 --- /dev/null +++ b/app/src/processing/app/helpers/filefilters/OnlyDirs.java @@ -0,0 +1,42 @@ +/* + OnlyDirs - FilenameFilter that accepts only directories (CVS, .svn, + .DS_Store files are excluded as well) + Part of the Arduino project - http://www.arduino.cc/ + + Copyright (c) 2011 Cristian Maglie + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package processing.app.helpers.filefilters; + +import java.io.File; +import java.io.FilenameFilter; + +/** + * This filter accepts only directories (excluding .DS_Store files, .svn + * folders, etc) + * + * @author Cristian Maglie + */ +public class OnlyDirs implements FilenameFilter { + + public boolean accept(File dir, String name) { + if (name.charAt(0) == '.') + return false; + if (name.equals("CVS")) + return false; + return new File(dir, name).isDirectory(); + } +} diff --git a/app/src/processing/app/tools/ZipDeflater.java b/app/src/processing/app/tools/ZipDeflater.java index 651ff37ba..55f0c0c8b 100644 --- a/app/src/processing/app/tools/ZipDeflater.java +++ b/app/src/processing/app/tools/ZipDeflater.java @@ -10,30 +10,36 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; +import processing.app.helpers.FileUtils; + public class ZipDeflater { private final ZipFile zipFile; private final File destFolder; + private final Random random; + private final File file; public ZipDeflater(File file, File destFolder) throws ZipException, IOException { + this.file = file; this.destFolder = destFolder; this.zipFile = new ZipFile(file); + this.random = new Random(); } public void deflate() throws IOException { - String folderName = tempFolderNameFromZip(); + String tmpFolderName = folderNameFromZip() + random.nextInt(1000000); - File folder = new File(destFolder, folderName); + File tmpFolder = new File(destFolder, tmpFolderName); - if (!folder.mkdir()) { - throw new IOException("Unable to create folder " + folderName); + if (!tmpFolder.mkdir()) { + throw new IOException("Unable to create folder " + tmpFolderName); } Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); - ensureFoldersOfEntryExist(folder, entry); - File entryFile = new File(folder, entry.getName()); + ensureFoldersOfEntryExist(tmpFolder, entry); + File entryFile = new File(tmpFolder, entry.getName()); if (entry.isDirectory()) { entryFile.mkdir(); } else { @@ -58,8 +64,20 @@ public class ZipDeflater { } } - // Test.zip may or may not contain Test folder. We use zip name to create libraries folder. Therefore, a contained Test folder is useless and must be removed - ensureOneLevelFolder(folder); + deleteUndesiredFoldersAndFiles(tmpFolder); + + // Test.zip may or may not contain Test folder. If it does, we keep it. If not, we use zip name. + ensureOneLevelFolder(tmpFolder); + } + + private void deleteUndesiredFoldersAndFiles(File folder) { + for (File file : folder.listFiles()) { + if (file.isDirectory() && "__MACOSX".equals(file.getName())) { + FileUtils.recursiveDelete(file); + } else if (file.getName().startsWith(".")) { + FileUtils.recursiveDelete(file); + } + } } private void ensureFoldersOfEntryExist(File folder, ZipEntry entry) { @@ -73,25 +91,22 @@ public class ZipDeflater { private void ensureOneLevelFolder(File folder) { File[] files = folder.listFiles(); - if (files.length == 1 && files[0].isDirectory()) { - File tempFile = new File(files[0].getPath() + new Random().nextInt(1000)); - files[0].renameTo(tempFile); - for (File file : tempFile.listFiles()) { - file.renameTo(new File(folder, file.getName())); - } - tempFile.delete(); + + if (files.length != 1) { + folder.renameTo(new File(folder.getParentFile(), folderNameFromZip())); + return; } + + files[0].renameTo(new File(folder.getParentFile(), files[0].getName())); + FileUtils.recursiveDelete(folder); } - private String tempFolderNameFromZip() { - String folderName = zipFile.getName(); - if (folderName.lastIndexOf(".") != -1) { - folderName = folderName.substring(0, folderName.lastIndexOf(".")); + private String folderNameFromZip() { + String filename = file.getName(); + if (filename.lastIndexOf(".") != -1) { + filename = filename.substring(0, filename.lastIndexOf(".")); } - if (folderName.lastIndexOf(File.separator) != -1) { - folderName = folderName.substring(folderName.lastIndexOf(File.separator) + 1); - } - return folderName; + return filename; } } diff --git a/build/shared/revisions.txt b/build/shared/revisions.txt index 4fd135592..e0a6922c1 100644 --- a/build/shared/revisions.txt +++ b/build/shared/revisions.txt @@ -1,15 +1,18 @@ -ARDUINO 1.0.5 - 2013.04.08 +ARDUINO 1.0.5 - 2013.05.15 [core] * [avr] malloc bug: backported avr-libc 1.8.0 implementation * [avr] removed deprecated interrupt handlers causing compiler issues with newer avr-gcc. +* [avr] added c_str() method to String +* [avr] Stream "_timeout" field and related methods are now protected [libraries] * Upgrades to WiFi library +* Fixed a bunch of examples [firmwares] @@ -17,7 +20,10 @@ ARDUINO 1.0.5 - 2013.04.08 [ide] -* Backport from 1.5: install Library from file +* Backport from 1.5: install Library from .zip file or folder +* Added button "Copy error to clipboard" (Paul Stoffregen) +* Updated windows drivers +* Added Windows installer ARDUINO 1.0.4 - 2013.03.11