diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index c930cc85f96..ca352c3a584 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -771,7 +771,8 @@ public void handleOpenPrompt() throws Exception { fd.setFilenameFilter(new FilenameFilter() { public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".ino") - || name.toLowerCase().endsWith(".pde"); + || name.toLowerCase().endsWith(".pde") + || name.toLowerCase().endsWith(".inz"); } }); diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 7d26271953b..2e96d3bc0f3 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -157,7 +157,7 @@ public boolean test(SketchController sketch) { static volatile AbstractMonitor serialMonitor; static AbstractMonitor serialPlotter; - + final EditorHeader header; EditorStatus status; EditorConsole console; @@ -247,7 +247,7 @@ public void windowDeactivated(WindowEvent e) { //PdeKeywords keywords = new PdeKeywords(); //sketchbook = new Sketchbook(this); - + buildMenuBar(); // For rev 0120, placing things inside a JPanel @@ -1862,7 +1862,9 @@ protected boolean handleOpenInternal(File sketchFile) { File file = Sketch.checkSketchFile(sketchFile); if (file == null) { - if (!fileName.endsWith(".ino") && !fileName.endsWith(".pde")) { + if (fileName.endsWith(".inz")) { + file = sketchFile; + } else if (!fileName.endsWith(".ino") && !fileName.endsWith(".pde")) { Base.showWarning(tr("Bad file selected"), tr("Arduino can only open its own sketches\n" + "and other files ending in .ino or .pde"), null); @@ -2288,7 +2290,7 @@ public void handleSerial() { return; } } - + if (serialMonitor != null) { // The serial monitor already exists @@ -2385,7 +2387,7 @@ public void handleSerial() { } while (serialMonitor.requiresAuthorization() && !success); } - + public void handlePlotter() { if(serialMonitor != null) { if(serialMonitor.isClosed()) { @@ -2395,7 +2397,7 @@ public void handlePlotter() { return; } } - + if (serialPlotter != null) { // The serial plotter already exists diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index 6c417403ec9..1410902f9cc 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -2,13 +2,20 @@ import java.io.File; import java.io.IOException; +import java.io.FileInputStream; import java.nio.file.Files; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import java.io.FileOutputStream; import cc.arduino.files.DeleteFilesOnShutdown; import processing.app.helpers.FileUtils; +import processing.app.tools.ZipDeflater; + +import org.apache.commons.compress.utils.IOUtils; import static processing.app.I18n.tr; @@ -17,8 +24,10 @@ */ public class Sketch { public static final String DEFAULT_SKETCH_EXTENSION = "ino"; + public static final String DEFAULT_SKETCH_EXTENSION_COMPRESSED = "inoz"; public static final List OLD_SKETCH_EXTENSIONS = Arrays.asList("pde"); - public static final List SKETCH_EXTENSIONS = Stream.concat(Stream.of(DEFAULT_SKETCH_EXTENSION), OLD_SKETCH_EXTENSIONS.stream()).collect(Collectors.toList()); + public static final List DEFAULT_SKETCH_EXTENSIONS = Stream.concat(Stream.of(DEFAULT_SKETCH_EXTENSION), Stream.of(DEFAULT_SKETCH_EXTENSION_COMPRESSED)).collect(Collectors.toList()); + public static final List SKETCH_EXTENSIONS = Stream.concat(DEFAULT_SKETCH_EXTENSIONS.stream(), OLD_SKETCH_EXTENSIONS.stream()).collect(Collectors.toList()); public static final List OTHER_ALLOWED_EXTENSIONS = Arrays.asList("c", "cpp", "h", "hh", "hpp", "s"); public static final List EXTENSIONS = Stream.concat(SKETCH_EXTENSIONS.stream(), OTHER_ALLOWED_EXTENSIONS.stream()).collect(Collectors.toList()); @@ -27,6 +36,8 @@ public class Sketch { */ private File folder; + private File compressedSketch = null; + private List files = new ArrayList<>(); private File buildPath; @@ -50,10 +61,70 @@ public int compare(SketchFile x, SketchFile y) { * Any file inside the sketch directory. */ Sketch(File file) throws IOException { - folder = file.getParentFile(); + if (file.getName().endsWith("inz")) { + //extract it in a temp folder and assign the + compressedSketch = file; + File tmpFolder = FileUtils.createTempFolder(); + ZipDeflater zipDeflater = new ZipDeflater(file, tmpFolder); + zipDeflater.deflate(); + String basename = FileUtils.splitFilename(file.getName()).basename; + folder = new File(tmpFolder, basename); + } else { + folder = file.getParentFile(); + } files = listSketchFiles(true); } + public void buildZip(File dir, String sofar, + ZipOutputStream zos) throws IOException { + String files[] = dir.list(); + if (files == null) { + throw new IOException("Unable to list files from " + dir); + } + for (int i = 0; i < files.length; i++) { + if (files[i].equals(".") || + files[i].equals("..")) continue; + + File sub = new File(dir, files[i]); + String nowfar = (sofar == null) ? + files[i] : (sofar + "/" + files[i]); + + if (sub.isDirectory()) { + // directories are empty entries and have / at the end + ZipEntry entry = new ZipEntry(nowfar + "/"); + //System.out.println(entry); + zos.putNextEntry(entry); + zos.closeEntry(); + buildZip(sub, nowfar, zos); + + } else { + ZipEntry entry = new ZipEntry(nowfar); + entry.setTime(sub.lastModified()); + zos.putNextEntry(entry); + zos.write(loadBytesRaw(sub)); + zos.closeEntry(); + } + } + } + + static public byte[] loadBytesRaw(File file) throws IOException { + int size = (int) file.length(); + FileInputStream input = null; + try { + input = new FileInputStream(file); + byte buffer[] = new byte[size]; + int offset = 0; + int bytesRead; + while ((bytesRead = input.read(buffer, offset, size - offset)) != -1) { + offset += bytesRead; + if (bytesRead == 0) break; + } + return buffer; + } finally { + IOUtils.closeQuietly(input); + } + } + static public File checkSketchFile(File file) { // check to make sure that this .pde file is // in a folder of the same name @@ -137,6 +208,13 @@ public void save() throws IOException { if (file.isModified()) file.save(); } + String basename = files.get(0).getPrettyName(); + if (compressedSketch != null) { + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(compressedSketch)); + buildZip(folder, basename, zos); + zos.flush(); + IOUtils.closeQuietly(zos); + } } public int getCodeCount() { @@ -151,6 +229,9 @@ public SketchFile[] getFiles() { * Returns a file object for the primary .pde of this sketch. */ public SketchFile getPrimaryFile() { + if (compressedSketch != null) { + return new SketchFile(this, compressedSketch); + } return files.get(0); } diff --git a/arduino-core/src/processing/app/tools/ZipDeflater.java b/arduino-core/src/processing/app/tools/ZipDeflater.java new file mode 100644 index 00000000000..1425d880247 --- /dev/null +++ b/arduino-core/src/processing/app/tools/ZipDeflater.java @@ -0,0 +1,109 @@ +package processing.app.tools; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Random; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +import org.apache.commons.compress.utils.IOUtils; +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 tmpFolderName = folderNameFromZip() + random.nextInt(1000000); + + File tmpFolder = new File(destFolder, tmpFolderName); + + if (!tmpFolder.mkdir()) { + throw new IOException("Unable to create folder " + tmpFolderName); + } + + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + ensureFoldersOfEntryExist(tmpFolder, entry); + File entryFile = new File(tmpFolder, entry.getName()); + if (entry.isDirectory()) { + entryFile.mkdir(); + } else { + FileOutputStream fos = null; + InputStream zipInputStream = null; + try { + fos = new FileOutputStream(entryFile); + zipInputStream = zipFile.getInputStream(entry); + byte[] buffer = new byte[1024 * 4]; + int len = -1; + while ((len = zipInputStream.read(buffer)) != -1) { + fos.write(buffer, 0, len); + } + } finally { + IOUtils.closeQuietly(fos); + IOUtils.closeQuietly(zipInputStream); + } + } + } + + 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) { + String[] parts = entry.getName().split("/"); + File current = folder; + for (int i = 0; i < parts.length - 1; i++) { + current = new File(current, parts[i]); + current.mkdir(); + } + } + + private void ensureOneLevelFolder(File folder) { + File[] files = folder.listFiles(); + + 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 folderNameFromZip() { + String filename = file.getName(); + if (filename.lastIndexOf(".") != -1) { + filename = filename.substring(0, filename.lastIndexOf(".")); + } + return filename; + } + +}