/* * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.netbeans.nbbuild; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.Get; import org.apache.tools.ant.types.FileSet; import org.netbeans.nbbuild.AutoUpdateCatalogParser.ModuleItem; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * * @author Jaroslav Tulach <jtulach@netbeans.org> */ public class AutoUpdate extends Task { private List<Modules> modules = new ArrayList<Modules>(); private FileSet nbmSet; private File dir; private File cluster; private URL catalog; private boolean force; public void setUpdateCenter(URL u) { catalog = u; } public FileSet createNbms() { if (nbmSet != null) { throw new BuildException("Just one nbms set allowed"); } nbmSet = new FileSet(); return nbmSet; } public void setInstallDir(File dir) { this.dir = dir; } public void setToDir(File dir) { this.cluster = dir; } /** Forces rewrite even the version of a module is not newer */ public void setForce(boolean force) { this.force = force; } public Modules createModules() { final Modules m = new Modules(); modules.add(m); return m; } @Override public void execute() throws BuildException { if ((dir != null) == (cluster != null)) { throw new BuildException("Specify either todir or installdir"); } Map<String, ModuleItem> units; if (catalog != null) { try { units = AutoUpdateCatalogParser.getUpdateItems(catalog, catalog, this); } catch (IOException ex) { throw new BuildException(ex.getMessage(), ex); } } else { if (nbmSet == null) { throw new BuildException("Specify updatecenter or list of NBMs"); } DirectoryScanner s = nbmSet.getDirectoryScanner(getProject()); File basedir = s.getBasedir(); units = new HashMap<String, ModuleItem>(); for (String incl : s.getIncludedFiles()) { File nbm = new File(basedir, incl); try { URL u = new URL("jar:" + nbm.toURI() + "!/Info/info.xml"); Map<String, ModuleItem> map; final URL url = nbm.toURI().toURL(); map = AutoUpdateCatalogParser.getUpdateItems(u, url, this); assert map.size() == 1; Map.Entry<String,ModuleItem> entry = map.entrySet().iterator().next(); units.put(entry.getKey(), entry.getValue().changeDistribution(url)); } catch (IOException ex) { throw new BuildException(ex); } } } Map<String,List<String>> installed; if (dir != null) { File[] arr = dir.listFiles(); if (arr == null) { throw new BuildException("installdir must be existing directory: " + dir); } installed = findExistingModules(arr); } else { installed = findExistingModules(cluster); } for (ModuleItem uu : units.values()) { if (!matches(uu.getCodeName(), uu.targetcluster)) { continue; } log("found module: " + uu, Project.MSG_VERBOSE); List<String> info = installed.get(uu.getCodeName()); if (info != null && !uu.isNewerThan(info.get(0))) { log("Version " + info.get(0) + " of " + uu.getCodeName() + " is up to date", Project.MSG_VERBOSE); if (!force) { continue; } } if (info == null) { log(uu.getCodeName() + " is not present, downloading version " + uu.getSpecVersion(), Project.MSG_INFO); } else { log("Version " + info.get(0) + " of " + uu.getCodeName() + " needs update to " + uu.getSpecVersion(), Project.MSG_INFO); } byte[] bytes = new byte[4096]; File tmp = null; boolean delete = false; File lastM = null; try { if (uu.getURL().getProtocol().equals("file")) { try { tmp = new File(uu.getURL().toURI()); } catch (URISyntaxException ex) { tmp = null; } if (!tmp.exists()) { tmp = null; } } final String dash = uu.getCodeName().replace('.', '-'); if (tmp == null) { tmp = File.createTempFile(dash, ".nbm"); tmp.deleteOnExit(); delete = true; Get get = new Get(); get.setProject(getProject()); get.setTaskName("get:" + uu.getCodeName()); get.setSrc(uu.getURL()); get.setDest(tmp); get.setVerbose(true); get.execute(); } File whereTo = dir != null ? new File(dir, uu.targetcluster) : cluster; whereTo.mkdirs(); lastM = new File(whereTo, ".lastModified"); lastM.createNewFile(); if (info != null) { for (int i = 1; i < info.size(); i++) { File oldFile = new File(whereTo, info.get(i).replace('/', File.separatorChar)); oldFile.delete(); } } File tracking = new File(new File(whereTo, "update_tracking"), dash + ".xml"); log("Writing tracking file " + tracking, Project.MSG_VERBOSE); tracking.getParentFile().mkdirs(); OutputStream config = new BufferedOutputStream(new FileOutputStream(tracking)); config.write(("<?xml version='1.0' encoding='UTF-8'?>\n" + "<module codename='" + uu.getCodeName() + "'>\n").getBytes("UTF-8")); config.write((" <module_version install_time='" + System.currentTimeMillis() + "' last='true' origin='Ant'" + " specification_version='" + uu.getSpecVersion() + "'>\n").getBytes("UTF-8")); ZipFile zf = new ZipFile(tmp); Enumeration<? extends ZipEntry> en = zf.entries(); while (en.hasMoreElements()) { ZipEntry zipEntry = en.nextElement(); if (!zipEntry.getName().startsWith("netbeans/")) { continue; } if (zipEntry.getName().endsWith("/")) { continue; } String relName = zipEntry.getName().substring(9); File trgt = new File(whereTo, relName.replace('/', File.separatorChar)); trgt.getParentFile().mkdirs(); log("Writing " + trgt, Project.MSG_VERBOSE); InputStream is = zf.getInputStream(zipEntry); OutputStream os = new FileOutputStream(trgt); boolean doUnpack200 = false; if(relName.endsWith(".jar.pack.gz") && zf.getEntry(zipEntry.getName().substring(0, zipEntry.getName().length() - 8))==null) { doUnpack200 = true; } CRC32 crc = new CRC32(); for (;;) { int len = is.read(bytes); if (len == -1) { break; } if(!doUnpack200) { crc.update(bytes, 0, len); } os.write(bytes, 0, len); } is.close(); os.close(); long crcValue = crc.getValue(); if(doUnpack200) { File dest = new File(trgt.getParentFile(), trgt.getName().substring(0, trgt.getName().length() - 8)); log("Unpacking " + trgt + " to " + dest, Project.MSG_VERBOSE); unpack200(trgt, dest); trgt.delete(); crcValue = getFileCRC(dest); relName = relName.substring(0, relName.length() - 8); } config.write((" <file crc='" + crcValue + "' name='" + relName + "'/>\n").getBytes("UTF-8")); } config.write(" </module_version>\n</module>\n".getBytes("UTF-8")); config.close(); } catch (IOException ex) { throw new BuildException(ex); } finally { if (delete && tmp != null) { tmp.delete(); } if (lastM != null) { lastM.setLastModified(System.currentTimeMillis()); } } } } public static boolean unpack200(File src, File dest) { // Copy of ModuleUpdater.unpack200 String unpack200Executable = new File(System.getProperty("java.home"), "bin/unpack200" + (isWindows() ? ".exe" : "")).getAbsolutePath(); ProcessBuilder pb = new ProcessBuilder(unpack200Executable, src.getAbsolutePath(), dest.getAbsolutePath()); pb.directory(src.getParentFile()); int result = 1; try { //maybe reuse start() method here? Process process = pb.start(); //TODO: Need to think of unpack200/lvprcsrv.exe issues //https://netbeans.org/bugzilla/show_bug.cgi?id=117334 //https://netbeans.org/bugzilla/show_bug.cgi?id=119861 result = process.waitFor(); process.destroy(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return result == 0; } private static boolean isWindows() { String os = System.getProperty("os.name"); // NOI18N return (os != null && os.toLowerCase().startsWith("windows"));//NOI18N } private static long getFileCRC(File file) throws IOException { BufferedInputStream bsrc = null; CRC32 crc = new CRC32(); try { bsrc = new BufferedInputStream( new FileInputStream( file ) ); byte[] bytes = new byte[1024]; int i; while( (i = bsrc.read(bytes)) != -1 ) { crc.update(bytes, 0, i ); } } finally { if ( bsrc != null ) bsrc.close(); } return crc.getValue(); } private boolean matches(String cnb, String targetCluster) { for (Modules ps : modules) { if (ps.clusters != null) { if (targetCluster == null) { continue; } if (!ps.clusters.matcher(targetCluster).matches()) { continue; } } if (ps.pattern.matcher(cnb).matches()) { return true; } } return false; } private Map<String,List<String>> findExistingModules(File... clusters) { Map<String,List<String>> all = new HashMap<String, List<String>>(); for (File c : clusters) { File mc = new File(c, "update_tracking"); final File[] arr = mc.listFiles(); if (arr == null) { continue; } for (File m : arr) { try { parseVersion(m, all); } catch (Exception ex) { log("Cannot parse " + m, ex, Project.MSG_WARN); } } } return all; } private void parseVersion(final File config, final Map<String,List<String>> toAdd) throws Exception { class P extends DefaultHandler { String name; List<String> arr; @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if ("module".equals(qName)) { name = attributes.getValue("codename"); int slash = name.indexOf('/'); if (slash > 0) { name = name.substring(0, slash); } return; } if ("module_version".equals(qName)) { String version = attributes.getValue("specification_version"); if (name == null || version == null) { throw new BuildException("Cannot find version in " + config); } arr = new ArrayList<String>(); arr.add(version); toAdd.put(name, arr); return; } if ("file".equals(qName)) { arr.add(attributes.getValue("name")); } } @Override public InputSource resolveEntity(String string, String string1) throws IOException, SAXException { return new InputSource(new ByteArrayInputStream(new byte[0])); } } P p = new P(); SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); parser.parse(config, p); } public static final class Modules { Pattern pattern; Pattern clusters; public void setIncludes(String regExp) { pattern = Pattern.compile(regExp); } public void setClusters(String regExp) { clusters = Pattern.compile(regExp); } } }