| /* |
| * Copyright (c) 2008, 2009, 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 sun.jkernel; |
| |
| import java.io.*; |
| import java.net.HttpRetryException; |
| import java.util.*; |
| import java.util.concurrent.*; |
| import java.util.jar.*; |
| import java.util.zip.GZIPInputStream; |
| |
| /** |
| * Represents a bundle which may or may not currently be installed. |
| * |
| *@author Ethan Nicholas |
| */ |
| public class Bundle { |
| static { |
| if (!DownloadManager.jkernelLibLoaded) { |
| // This code can be invoked directly by the deploy build. |
| System.loadLibrary("jkernel"); |
| } |
| } |
| /** |
| * Compress file sourcePath with "extra" algorithm (e.g. 7-Zip LZMA) |
| * if available, put the uncompressed data into file destPath and |
| * return true. If not available return false and do nothing with destPath. |
| * |
| * @param srcPath path to existing uncompressed file |
| * @param destPath path for the compressed file to be created |
| * @returns true if extra algorithm used, false if not |
| * @throws IOException if the extra compression code should be available |
| * but cannot be located or linked to, the destination file already |
| * exists or cannot be opened for writing, or the compression fails |
| */ |
| public static native boolean extraCompress(String srcPath, |
| String destPath) throws IOException; |
| |
| /** |
| * Decompress file sourcePath with "extra" algorithm (e.g. 7-Zip LZMA) |
| * if available, put the uncompressed data into file destPath and |
| * return true. If not available return false and do nothing with |
| * destPath. |
| * @param srcPath path to existing compressed file |
| * @param destPath path to uncompressed file to be created |
| * @returns true if extra algorithm used, false if not |
| * @throws IOException if the extra uncompression code should be available |
| * but cannot be located or linked to, the destination file already |
| * exists or cannot be opened for writing, or the uncompression fails |
| */ |
| public static native boolean extraUncompress(String srcPath, |
| String destPath) throws IOException; |
| |
| private static final String BUNDLE_JAR_ENTRY_NAME = "classes.jar"; |
| |
| /** The bundle is not present. */ |
| protected static final int NOT_DOWNLOADED = 0; |
| |
| /** |
| * The bundle is in the download queue but has not finished downloading. |
| */ |
| protected static final int QUEUED = 1; |
| |
| /** The bundle has finished downloading but is not installed. */ |
| protected static final int DOWNLOADED = 2; |
| |
| /** The bundle is fully installed and functional. */ |
| protected static final int INSTALLED = 3; |
| |
| /** Thread pool used to manage dependency downloads. */ |
| private static ExecutorService threadPool; |
| |
| /** Size of thread pool. */ |
| static final int THREADS; |
| |
| static { |
| String downloads = System.getProperty( |
| DownloadManager.KERNEL_SIMULTANEOUS_DOWNLOADS_PROPERTY); |
| if (downloads != null) |
| THREADS = Integer.parseInt(downloads.trim()); |
| else |
| THREADS = 1; |
| } |
| |
| /** Mutex used to safely access receipts file. */ |
| private static Mutex receiptsMutex; |
| |
| /** Maps bundle names to known bundle instances. */ |
| private static Map<String, Bundle> bundles = |
| new HashMap<String, Bundle>(); |
| |
| /** Contains the names of currently-installed bundles. */ |
| static Set<String> receipts = new HashSet<String>(); |
| |
| private static int bytesDownloaded; |
| |
| /** Path where bundle receipts are written. */ |
| private static File receiptPath = new File(DownloadManager.getBundlePath(), |
| "receipts"); |
| |
| /** The size of the receipts file the last time we saw it. */ |
| private static int receiptsSize; |
| |
| /** The bundle name, e.g. "java_awt". */ |
| private String name; |
| |
| /** The path to which we are saving the downloaded bundle file. */ |
| private File localPath; |
| |
| /** |
| * The path of the extracted JAR file containing the bundle's classes. |
| */ |
| private File jarPath; |
| |
| // for vista IE7 protected mode |
| private File lowJarPath; |
| private File lowJavaPath = null; |
| |
| /** The current state (DOWNLOADED, INSTALLED, etc.). */ |
| protected int state; |
| |
| /** |
| * True if we should delete the downloaded bundle after installing it. |
| */ |
| protected boolean deleteOnInstall = true; |
| |
| private static Mutex getReceiptsMutex() { |
| if (receiptsMutex == null) |
| receiptsMutex = Mutex.create(DownloadManager.MUTEX_PREFIX + |
| "receipts"); |
| return receiptsMutex; |
| } |
| |
| |
| /** |
| * Reads the receipts file in order to seed the list of currently |
| * installed bundles. |
| */ |
| static synchronized void loadReceipts() { |
| getReceiptsMutex().acquire(); |
| try { |
| if (receiptPath.exists()) { |
| int size = (int) receiptPath.length(); |
| if (size != receiptsSize) { // ensure that it has actually |
| // been modified |
| DataInputStream in = null; |
| try { |
| receipts.clear(); |
| for (String bundleName : DownloadManager.getBundleNames()) { |
| if ("true".equals(DownloadManager.getBundleProperty(bundleName, |
| DownloadManager.INSTALL_PROPERTY))) |
| receipts.add(bundleName); |
| } |
| if (receiptPath.exists()) { |
| in = new DataInputStream(new BufferedInputStream( |
| new FileInputStream(receiptPath))); |
| String line; |
| while ((line = in.readLine()) != null) { |
| receipts.add(line.trim()); |
| } |
| } |
| receiptsSize = size; |
| } |
| catch (IOException e) { |
| DownloadManager.log(e); |
| // safe to continue, as the worst that happens is |
| // we re-download existing bundles |
| } finally { |
| if (in != null) { |
| try { |
| in.close(); |
| } catch (IOException ioe) { |
| DownloadManager.log(ioe); |
| } |
| } |
| } |
| } |
| } |
| } |
| finally { |
| getReceiptsMutex().release(); |
| } |
| } |
| |
| |
| /** Returns the bundle corresponding to the specified name. */ |
| public static synchronized Bundle getBundle(String bundleId) |
| throws IOException { |
| Bundle result =(Bundle) bundles.get(bundleId); |
| if (result == null && (bundleId.equals("merged") || |
| Arrays.asList(DownloadManager.getBundleNames()).contains(bundleId))) { |
| result = new Bundle(); |
| result.name = bundleId; |
| |
| if (DownloadManager.isWindowsVista()) { |
| result.localPath = |
| new File(DownloadManager.getLocalLowTempBundlePath(), |
| bundleId + ".zip"); |
| result.lowJavaPath = new File( |
| DownloadManager.getLocalLowKernelJava() + bundleId); |
| } else { |
| result.localPath = new File(DownloadManager.getBundlePath(), |
| bundleId + ".zip"); |
| } |
| |
| String jarPath = DownloadManager.getBundleProperty(bundleId, |
| DownloadManager.JAR_PATH_PROPERTY); |
| if (jarPath != null) { |
| if (DownloadManager.isWindowsVista()) { |
| result.lowJarPath = new File( |
| DownloadManager.getLocalLowKernelJava() + bundleId, |
| jarPath); |
| } |
| result.jarPath = new File(DownloadManager.JAVA_HOME, |
| jarPath); |
| |
| } else { |
| |
| if (DownloadManager.isWindowsVista()) { |
| result.lowJarPath = new File( |
| DownloadManager.getLocalLowKernelJava() + bundleId + |
| "\\lib\\bundles", |
| bundleId + ".jar"); |
| } |
| |
| result.jarPath = new File(DownloadManager.getBundlePath(), |
| bundleId + ".jar"); |
| |
| } |
| |
| bundles.put(bundleId, result); |
| } |
| return result; |
| } |
| |
| |
| /** |
| * Returns the name of this bundle. The name is typically defined by |
| * the bundles.xml file. |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| |
| /** |
| * Sets the name of this bundle. |
| */ |
| public void setName(String name) { |
| this.name = name; |
| } |
| |
| |
| /** |
| * Returns the path to the bundle file on the local filesystem. The file |
| * will only exist if the bundle has already been downloaded; otherwise |
| * it will be created when download() is called. |
| */ |
| public File getLocalPath() { |
| return localPath; |
| } |
| |
| |
| /** |
| * Sets the location of the bundle file on the local filesystem. If the |
| * file already exists, the bundle will be considered downloaded; |
| * otherwise the file will be created when download() is called. |
| */ |
| public void setLocalPath(File localPath) { |
| this.localPath = localPath; |
| } |
| |
| |
| /** |
| * Returns the path to the extracted JAR file containing this bundle's |
| * classes. This file should only exist after the bundle has been |
| * installed. |
| */ |
| public File getJarPath() { |
| return jarPath; |
| } |
| |
| |
| /** |
| * Sets the path to the extracted JAR file containing this bundle's |
| * classes. This file will be created as part of installing the bundle. |
| */ |
| public void setJarPath(File jarPath) { |
| this.jarPath = jarPath; |
| } |
| |
| |
| /** |
| * Returns the size of the bundle download in bytes. |
| */ |
| public int getSize() { |
| return Integer.valueOf(DownloadManager.getBundleProperty(getName(), |
| DownloadManager.SIZE_PROPERTY)); |
| } |
| |
| |
| /** |
| * Returns true if the bundle file (getLocalPath()) should be deleted |
| * when the bundle is successfully installed. Defaults to true. |
| */ |
| public boolean getDeleteOnInstall() { |
| return deleteOnInstall; |
| } |
| |
| |
| /** |
| * Sets whether the bundle file (getLocalPath()) should be deleted |
| * when the bundle is successfully installed. Defaults to true. |
| */ |
| public void setDeleteOnInstall(boolean deleteOnInstall) { |
| this.deleteOnInstall = deleteOnInstall; |
| } |
| |
| |
| /** Sets the current state of this bundle to match reality. */ |
| protected void updateState() { |
| synchronized(Bundle.class) { |
| loadReceipts(); |
| if (receipts.contains(name) || |
| "true".equals(DownloadManager.getBundleProperty(name, |
| DownloadManager.INSTALL_PROPERTY))) |
| state = Bundle.INSTALLED; |
| else if (localPath.exists()) |
| state = Bundle.DOWNLOADED; |
| } |
| } |
| |
| |
| private String getURL(boolean showUI) throws IOException { |
| Properties urls = DownloadManager.getBundleURLs(showUI); |
| String result = urls.getProperty(name + ".zip"); |
| if (result == null) { |
| result = urls.getProperty(name); |
| if (result == null) { |
| DownloadManager.log("Unable to determine bundle URL for " + this); |
| DownloadManager.log("Bundle URLs: " + urls); |
| DownloadManager.sendErrorPing(DownloadManager.ERROR_NO_SUCH_BUNDLE); |
| |
| throw new NullPointerException("Unable to determine URL " + |
| "for bundle: " + this); |
| } |
| } |
| return result; |
| } |
| |
| |
| /** |
| * Downloads the bundle. This method blocks until the download is |
| * complete. |
| * |
| *@param showProgress true to display a progress dialog |
| */ |
| private void download(boolean showProgress) { |
| if (DownloadManager.isJREComplete()) |
| return; |
| Mutex mutex = Mutex.create(DownloadManager.MUTEX_PREFIX + name + |
| ".download"); |
| mutex.acquire(); |
| try { |
| long start = System.currentTimeMillis(); |
| |
| boolean retry; |
| |
| do { |
| retry = false; |
| updateState(); |
| if (state == DOWNLOADED || state == INSTALLED) { |
| return; |
| } |
| File tmp = null; |
| try { |
| tmp = new File(localPath + ".tmp"); |
| |
| // tmp.deleteOnExit(); |
| |
| if (DownloadManager.getBaseDownloadURL().equals( |
| DownloadManager.RESOURCE_URL)) { |
| // RESOURCE_URL is used during build process, to |
| // avoid actual network traffic. This is called in |
| // the SplitJRE DownloadTest to determine which |
| // classes are needed to support downloads, but we |
| // bypass the actual HTTP download to simplify the |
| // build process (it's all native code, so from |
| // DownloadTest's standpoint it doesn't matter if we |
| // really call it or not). |
| String path = "/" + name + ".zip"; |
| InputStream in = |
| getClass().getResourceAsStream(path); |
| if (in == null) |
| throw new IOException("could not locate " + |
| "resource: " + path); |
| FileOutputStream out = new FileOutputStream(tmp); |
| DownloadManager.send(in, out); |
| in.close(); |
| out.close(); |
| } |
| else { |
| try { |
| String bundleURL = getURL(showProgress); |
| DownloadManager.log("Downloading from: " + |
| bundleURL); |
| DownloadManager.downloadFromURL(bundleURL, tmp, |
| name.replace('_', '.'), showProgress); |
| } |
| catch (HttpRetryException e) { |
| // Akamai returned a 403, get new URL |
| DownloadManager.flushBundleURLs(); |
| String bundleURL = getURL(showProgress); |
| DownloadManager.log("Retrying at new " + |
| "URL: " + bundleURL); |
| DownloadManager.downloadFromURL(bundleURL, tmp, |
| name.replace('_', '.'), |
| showProgress); |
| // we intentionally don't do a 403 retry |
| // again, to avoid infinite retries |
| } |
| } |
| if (!tmp.exists() || tmp.length() == 0) { |
| if (showProgress) { |
| // since showProgress = true, native code should |
| // have offered to retry. Since we ended up here, |
| // we conclude that download failed & user opted to |
| // cancel. Set complete to true to stop bugging |
| // him in the future (if one bundle fails, the |
| // rest are virtually certain to). |
| DownloadManager.complete = true; |
| } |
| DownloadManager.fatalError(DownloadManager.ERROR_UNSPECIFIED); |
| } |
| |
| /** |
| * Bundle security |
| * |
| * Check for corruption/spoofing |
| */ |
| |
| |
| /* Create a bundle check from the tmp file */ |
| BundleCheck gottenCheck = BundleCheck.getInstance(tmp); |
| |
| /* Get the check expected for the Bundle */ |
| BundleCheck expectedCheck = BundleCheck.getInstance(name); |
| |
| // Do they match? |
| |
| if (expectedCheck.equals(gottenCheck)) { |
| |
| // Security check OK, uncompress the bundle file |
| // into the local path |
| |
| long uncompressedLength = tmp.length(); |
| localPath.delete(); |
| |
| File uncompressedPath = new File(tmp.getPath() + |
| ".jar0"); |
| if (! extraUncompress(tmp.getPath(), |
| uncompressedPath.getPath())) { |
| // Extra uncompression not available, fall |
| // back to alternative if it is enabled. |
| if (DownloadManager.debug) { |
| DownloadManager.log("Uncompressing with GZIP"); |
| } |
| GZIPInputStream in = new GZIPInputStream( new |
| BufferedInputStream(new FileInputStream(tmp), |
| DownloadManager.BUFFER_SIZE)); |
| BufferedOutputStream out = new BufferedOutputStream( |
| new FileOutputStream(uncompressedPath), |
| DownloadManager.BUFFER_SIZE); |
| DownloadManager.send(in,out); |
| in.close(); |
| out.close(); |
| if (! uncompressedPath.renameTo(localPath)) { |
| throw new IOException("unable to rename " + |
| uncompressedPath + " to " + localPath); |
| } |
| } else { |
| if (DownloadManager.debug) { |
| DownloadManager.log("Uncompressing with LZMA"); |
| } |
| if (! uncompressedPath.renameTo(localPath)) { |
| throw new IOException("unable to rename " + |
| uncompressedPath + " to " + localPath); |
| } |
| } |
| state = DOWNLOADED; |
| bytesDownloaded += uncompressedLength; |
| long time = (System.currentTimeMillis() - |
| start); |
| DownloadManager.log("Downloaded " + name + |
| " in " + time + "ms. Downloaded " + |
| bytesDownloaded + " bytes this session."); |
| |
| // Normal completion |
| } else { |
| |
| // Security check not OK: remove the temp file |
| // and consult the user |
| |
| tmp.delete(); |
| |
| DownloadManager.log( |
| "DownloadManager: Security check failed for " + |
| "bundle " + name); |
| |
| // only show dialog if we are not in silent mode |
| if (showProgress) { |
| retry = DownloadManager.askUserToRetryDownloadOrQuit( |
| DownloadManager.ERROR_UNSPECIFIED); |
| } |
| |
| if (!retry) { |
| // User wants to give up |
| throw new RuntimeException( |
| "Failed bundle security check and user " + |
| "canceled"); |
| } |
| } |
| } |
| catch (IOException e) { |
| // Look for "out of space" using File.getUsableSpace() |
| // here when downloadFromURL starts throwing IOException |
| // (or preferably a distinct exception for this case). |
| DownloadManager.log(e); |
| } |
| } while (retry); |
| } finally { |
| mutex.release(); |
| } |
| } |
| |
| |
| /** |
| * Calls {@link #queueDownload()} on all of this bundle's dependencies. |
| */ |
| void queueDependencies(boolean showProgress) { |
| try { |
| String dependencies = |
| DownloadManager.getBundleProperty(name, |
| DownloadManager.DEPENDENCIES_PROPERTY); |
| if (dependencies != null) { |
| StringTokenizer st = new StringTokenizer(dependencies, |
| " ,"); |
| while (st.hasMoreTokens()) { |
| Bundle b = getBundle(st.nextToken()); |
| if (b != null && !b.isInstalled()) { |
| if (DownloadManager.debug) { |
| DownloadManager.log("Queueing " + b.name + |
| " as a dependency of " + name + "..."); |
| } |
| b.install(showProgress, true, false); |
| } |
| } |
| } |
| } catch (IOException e) { |
| // shouldn't happen |
| DownloadManager.log(e); |
| } |
| } |
| |
| |
| static synchronized ExecutorService getThreadPool() { |
| if (threadPool == null) { |
| threadPool = Executors.newFixedThreadPool(THREADS, |
| new ThreadFactory () { |
| public Thread newThread(Runnable r) { |
| Thread result = new Thread(r); |
| result.setDaemon(true); |
| return result; |
| } |
| } |
| ); |
| } |
| return threadPool; |
| } |
| |
| |
| private void unpackBundle() throws IOException { |
| File useJarPath = null; |
| if (DownloadManager.isWindowsVista()) { |
| useJarPath = lowJarPath; |
| File jarDir = useJarPath.getParentFile(); |
| if (jarDir != null) { |
| jarDir.mkdirs(); |
| } |
| } else { |
| useJarPath = jarPath; |
| } |
| |
| DownloadManager.log("Unpacking " + this + " to " + useJarPath); |
| |
| InputStream rawStream = new FileInputStream(localPath); |
| JarInputStream in = new JarInputStream(rawStream) { |
| public void close() throws IOException { |
| // prevent any sub-processes here from actually closing the |
| // input stream; we'll use rawsStream.close() when we're |
| // done with it |
| } |
| }; |
| |
| try { |
| File jarTmp = null; |
| JarEntry entry; |
| while ((entry = in.getNextJarEntry()) != null) { |
| String entryName = entry.getName(); |
| if (entryName.equals("classes.pack")) { |
| File packTmp = new File(useJarPath + ".pack"); |
| packTmp.getParentFile().mkdirs(); |
| DownloadManager.log("Writing temporary .pack file " + packTmp); |
| OutputStream tmpOut = new FileOutputStream(packTmp); |
| try { |
| DownloadManager.send(in, tmpOut); |
| } finally { |
| tmpOut.close(); |
| } |
| // we unpack to a temporary file and then, towards the end |
| // of this method, use a (hopefully atomic) rename to put it |
| // into its final location; this should avoid the problem of |
| // partially-completed downloads. Doing the rename last |
| // allows us to check for the presence of the JAR file to |
| // see whether the bundle has in fact been downloaded. |
| jarTmp = new File(useJarPath + ".tmp"); |
| DownloadManager.log("Writing temporary .jar file " + jarTmp); |
| unpack(packTmp, jarTmp); |
| packTmp.delete(); |
| } else if (!entryName.startsWith("META-INF")) { |
| File dest; |
| if (DownloadManager.isWindowsVista()) { |
| dest = new File(lowJavaPath, |
| entryName.replace('/', File.separatorChar)); |
| } else { |
| dest = new File(DownloadManager.JAVA_HOME, |
| entryName.replace('/', File.separatorChar)); |
| } |
| if (entryName.equals(BUNDLE_JAR_ENTRY_NAME)) |
| dest = useJarPath; |
| File destTmp = new File(dest + ".tmp"); |
| boolean exists = dest.exists(); |
| if (!exists) { |
| DownloadManager.log(dest + ".mkdirs()"); |
| dest.getParentFile().mkdirs(); |
| } |
| try { |
| DownloadManager.log("Using temporary file " + destTmp); |
| FileOutputStream out = |
| new FileOutputStream(destTmp); |
| try { |
| byte[] buffer = new byte[2048]; |
| int c; |
| while ((c = in.read(buffer)) > 0) |
| out.write(buffer, 0, c); |
| } finally { |
| out.close(); |
| } |
| if (exists) |
| dest.delete(); |
| DownloadManager.log("Renaming from " + destTmp + " to " + dest); |
| if (!destTmp.renameTo(dest)) { |
| throw new IOException("unable to rename " + |
| destTmp + " to " + dest); |
| } |
| |
| } catch (IOException e) { |
| if (!exists) |
| throw e; |
| // otherwise the file already existed and the fact |
| // that we failed to re-write it probably just |
| // means that it was in use |
| } |
| } |
| } |
| |
| // rename the temporary jar into its final location |
| if (jarTmp != null) { |
| if (useJarPath.exists()) |
| jarTmp.delete(); |
| else if (!jarTmp.renameTo(useJarPath)) { |
| throw new IOException("unable to rename " + jarTmp + |
| " to " + useJarPath); |
| } |
| } |
| if (DownloadManager.isWindowsVista()) { |
| // move bundle to real location |
| DownloadManager.log("Using broker to move " + name); |
| if (!DownloadManager.moveDirWithBroker( |
| DownloadManager.getKernelJREDir() + name)) { |
| throw new IOException("unable to create " + name); |
| } |
| DownloadManager.log("Broker finished " + name); |
| } |
| DownloadManager.log("Finished unpacking " + this); |
| } finally { |
| rawStream.close(); |
| } |
| if (deleteOnInstall) { |
| localPath.delete(); |
| } |
| |
| } |
| |
| |
| public static void unpack(File pack, File jar) throws IOException { |
| Process p = Runtime.getRuntime().exec(DownloadManager.JAVA_HOME + File.separator + |
| "bin" + File.separator + "unpack200 -Hoff \"" + pack + "\" \"" + jar + "\""); |
| try { |
| p.waitFor(); |
| } |
| catch (InterruptedException e) { |
| } |
| } |
| |
| |
| /** |
| * Unpacks and installs the bundle. The bundle's classes are not |
| * immediately added to the boot class path; this happens when the VM |
| * attempts to load a class and calls getBootClassPathEntryForClass(). |
| */ |
| public void install() throws IOException { |
| install(true, false, true); |
| } |
| |
| |
| /** |
| * Unpacks and installs the bundle, optionally hiding the progress |
| * indicator. The bundle's classes are not immediately added to the |
| * boot class path; this happens when the VM attempts to load a class |
| * and calls getBootClassPathEntryForClass(). |
| * |
| *@param showProgress true to display a progress dialog |
| *@param downloadOnly true to download but not install |
| *@param block true to wait until the operation is complete before returning |
| */ |
| public synchronized void install(final boolean showProgress, |
| final boolean downloadOnly, boolean block) throws IOException { |
| if (DownloadManager.isJREComplete()) |
| return; |
| if (state == NOT_DOWNLOADED || state == QUEUED) { |
| // we allow an already-queued bundle to be placed into the queue |
| // again, to handle the case where the bundle is queued with |
| // downloadOnly true and then we try to queue it again with |
| // downloadOnly false -- the second queue entry will actually |
| // install it. |
| if (state != QUEUED) { |
| DownloadManager.addToTotalDownloadSize(getSize()); |
| state = QUEUED; |
| } |
| if (getThreadPool().isShutdown()) { |
| if (state == NOT_DOWNLOADED || state == QUEUED) |
| doInstall(showProgress, downloadOnly); |
| } |
| else { |
| Future task = getThreadPool().submit(new Runnable() { |
| public void run() { |
| try { |
| if (state == NOT_DOWNLOADED || state == QUEUED || |
| (!downloadOnly && state == DOWNLOADED)) { |
| doInstall(showProgress, downloadOnly); |
| } |
| } |
| catch (IOException e) { |
| // ignore |
| } |
| } |
| }); |
| queueDependencies(showProgress); |
| if (block) { |
| try { |
| task.get(); |
| } |
| catch (Exception e) { |
| throw new Error(e); |
| } |
| } |
| } |
| } |
| else if (state == DOWNLOADED && !downloadOnly) |
| doInstall(showProgress, false); |
| } |
| |
| |
| private void doInstall(boolean showProgress, boolean downloadOnly) |
| throws IOException { |
| Mutex mutex = Mutex.create(DownloadManager.MUTEX_PREFIX + name + |
| ".install"); |
| DownloadManager.bundleInstallStart(); |
| try { |
| mutex.acquire(); |
| updateState(); |
| if (state == NOT_DOWNLOADED || state == QUEUED) { |
| download(showProgress); |
| } |
| |
| if (state == DOWNLOADED && downloadOnly) { |
| return; |
| } |
| |
| if (state == INSTALLED) { |
| return; |
| } |
| if (state != DOWNLOADED) { |
| DownloadManager.fatalError(DownloadManager.ERROR_UNSPECIFIED); |
| } |
| |
| DownloadManager.log("Calling unpackBundle for " + this); |
| unpackBundle(); |
| DownloadManager.log("Writing receipt for " + this); |
| writeReceipt(); |
| updateState(); |
| DownloadManager.log("Finished installing " + this + ", state=" + state); |
| } finally { |
| if (lowJavaPath != null) { |
| lowJavaPath.delete(); |
| } |
| mutex.release(); |
| DownloadManager.bundleInstallComplete(); |
| } |
| } |
| |
| |
| synchronized void setState(int state) { |
| this.state = state; |
| } |
| |
| |
| /** Returns <code>true</code> if this bundle has been installed. */ |
| public boolean isInstalled() { |
| synchronized (Bundle.class) { |
| updateState(); |
| return state == INSTALLED; |
| } |
| } |
| |
| |
| /** |
| * Adds an entry to the receipts file indicating that this bundle has |
| * been successfully downloaded. |
| */ |
| private void writeReceipt() { |
| getReceiptsMutex().acquire(); |
| File useReceiptPath = null; |
| try { |
| |
| try { |
| |
| receipts.add(name); |
| |
| if (DownloadManager.isWindowsVista()) { |
| // write out receipts to locallow |
| useReceiptPath = new File( |
| DownloadManager.getLocalLowTempBundlePath(), |
| "receipts"); |
| |
| if (receiptPath.exists()) { |
| // copy original file to locallow location |
| DownloadManager.copyReceiptFile(receiptPath, |
| useReceiptPath); |
| } |
| |
| // update receipt in locallow path |
| // only append if original receipt path exists |
| FileOutputStream out = new FileOutputStream(useReceiptPath, |
| receiptPath.exists()); |
| out.write((name + System.getProperty("line.separator")).getBytes("utf-8")); |
| out.close(); |
| |
| // use broker to move back to real path |
| if (!DownloadManager.moveFileWithBroker( |
| DownloadManager.getKernelJREDir() |
| + "-bundles" + File.separator + "receipts")) { |
| throw new IOException("failed to write receipts"); |
| } |
| } else { |
| useReceiptPath = receiptPath; |
| FileOutputStream out = new FileOutputStream(useReceiptPath, |
| true); |
| out.write((name + System.getProperty("line.separator")).getBytes("utf-8")); |
| out.close(); |
| } |
| |
| |
| } catch (IOException e) { |
| DownloadManager.log(e); |
| // safe to continue, as the worst that happens is we |
| // re-download existing bundles |
| } |
| } |
| finally { |
| getReceiptsMutex().release(); |
| } |
| } |
| |
| |
| public String toString() { |
| return "Bundle[" + name + "]"; |
| } |
| } |