diff --git a/updater/src/com/intellij/updater/BaseUpdateAction.java b/updater/src/com/intellij/updater/BaseUpdateAction.java index 67a16f8eb4ca..1d5e5a1cc78f 100644 --- a/updater/src/com/intellij/updater/BaseUpdateAction.java +++ b/updater/src/com/intellij/updater/BaseUpdateAction.java @@ -35,13 +35,7 @@ public abstract class BaseUpdateAction extends PatchAction { protected void replaceUpdated(File from, File dest) throws IOException { // on OS X code signing caches seem to be associated with specific file ids, so we need to remove the original file. - if (!dest.delete()) { - if (Utils.isWindows()) { - throw new RetryException("Cannot delete file " + dest); - } else { - throw new IOException("Cannot delete file " + dest); - } - } + if (!dest.delete()) throw new IOException("Cannot delete file " + dest); Utils.copy(from, dest); } diff --git a/updater/src/com/intellij/updater/CreateAction.java b/updater/src/com/intellij/updater/CreateAction.java index 5b0f8df595a5..dd44c35f5df1 100644 --- a/updater/src/com/intellij/updater/CreateAction.java +++ b/updater/src/com/intellij/updater/CreateAction.java @@ -42,6 +42,11 @@ public class CreateAction extends PatchAction { return null; } + @Override + protected boolean isModified(File toFile) throws IOException { + return false; + } + @Override protected void doApply(ZipFile patchFile, File toFile) throws IOException { prepareToWriteFile(toFile); @@ -59,15 +64,7 @@ public class CreateAction extends PatchAction { private static void prepareToWriteFile(File file) throws IOException { if (file.exists()) { - try { - Utils.delete(file); - } catch (IOException e) { - if (Utils.isWindows() && file.exists()) { - throw new RetryException(e); - } else { - throw e; - } - } + Utils.delete(file); return; } diff --git a/updater/src/com/intellij/updater/DeleteAction.java b/updater/src/com/intellij/updater/DeleteAction.java index e47588e150ee..e197cbded8e2 100644 --- a/updater/src/com/intellij/updater/DeleteAction.java +++ b/updater/src/com/intellij/updater/DeleteAction.java @@ -3,10 +3,8 @@ package com.intellij.updater; import java.io.DataInputStream; import java.io.File; import java.io.IOException; -import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; -public class DeleteAction extends PatchAction { +public class DeleteAction extends BaseDeleteAction { public DeleteAction(String path, long checksum) { super(path, checksum); } @@ -16,54 +14,7 @@ public class DeleteAction extends PatchAction { } @Override - public void doBuildPatchFile(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException { - // do nothing - } - - @Override - protected ValidationResult doValidate(File toFile) throws IOException { - ValidationResult result = doValidateAccess(toFile, ValidationResult.Action.DELETE); - if (result != null) return result; - - if (toFile.exists() && isModified(toFile)) { - return new ValidationResult(ValidationResult.Kind.CONFLICT, - myPath, - ValidationResult.Action.DELETE, - "Modified", - ValidationResult.Option.DELETE, - ValidationResult.Option.KEEP); - } - return null; - } - - @Override - protected boolean shouldApplyOn(File toFile) { - return toFile.exists(); - } - - @Override - protected void doApply(ZipFile patchFile, File toFile) throws IOException { - try { - Utils.delete(toFile); - } catch (IOException e) { - if (Utils.isWindows() && toFile.exists()) { - throw new RetryException(e); - } else { - throw e; - } - } - } - - @Override - protected void doBackup(File toFile, File backupFile) throws IOException { - Utils.copy(toFile, backupFile); - } - - @Override - protected void doRevert(File toFile, File backupFile) throws IOException { - if (!toFile.exists() || isModified(toFile)) { - Utils.delete(toFile); // make sure there is no directory remained on this path (may remain from previous 'create' actions - Utils.copy(backupFile, toFile); - } + protected boolean isModified(File toFile) throws IOException { + return myChecksum != Digester.digestRegularFile(toFile); } } diff --git a/updater/src/com/intellij/updater/Digester.java b/updater/src/com/intellij/updater/Digester.java index ba6a4703e241..b84c33e3f6d5 100644 --- a/updater/src/com/intellij/updater/Digester.java +++ b/updater/src/com/intellij/updater/Digester.java @@ -22,15 +22,14 @@ public class Digester { } public static long digestFile(File file) throws IOException { - if (Utils.isZipFile(file.getName())) { + if (!Runner.ZIP_AS_BINARY && Utils.isZipFile(file.getName())) { ZipFile zipFile; try { zipFile = new ZipFile(file); } catch (IOException e) { - // If this isn't a zip file, this isn't really an error, merely an info. - Runner.infoStackTrace("Can't open file as zip file: " + file.getPath() + "\n", e); - return doDigestRegularFile(file); + Runner.printStackTrace(e); + return digestRegularFile(file); } try { @@ -40,10 +39,10 @@ public class Digester { zipFile.close(); } } - return doDigestRegularFile(file); + return digestRegularFile(file); } - private static long doDigestRegularFile(File file) throws IOException { + public static long digestRegularFile(File file) throws IOException { InputStream in = new BufferedInputStream(new FileInputStream(file)); try { return digestStream(in); diff --git a/updater/src/com/intellij/updater/Patch.java b/updater/src/com/intellij/updater/Patch.java index 9b66535b2449..d9a33a6df24d 100644 --- a/updater/src/com/intellij/updater/Patch.java +++ b/updater/src/com/intellij/updater/Patch.java @@ -11,6 +11,7 @@ public class Patch { private static final int UPDATE_ACTION_KEY = 2; private static final int UPDATE_ZIP_ACTION_KEY = 3; private static final int DELETE_ACTION_KEY = 4; + private static final int DELETE_ZIP_ACTION_KEY = 5; public Patch(File olderDir, File newerDir, @@ -45,7 +46,12 @@ public class Patch { // 'delete' actions before 'create' actions to prevent newly created files to be deleted if the names differ only on case. for (Map.Entry each : diff.filesToDelete.entrySet()) { - tempActions.add(new DeleteAction(each.getKey(), each.getValue())); + if (!Runner.ZIP_AS_BINARY && Utils.isZipFile(each.getKey())) { + tempActions.add(new DeleteZipAction(each.getKey(), each.getValue())); + } else + { + tempActions.add(new DeleteAction(each.getKey(), each.getValue())); + } } for (String each : diff.filesToCreate) { @@ -53,7 +59,7 @@ public class Patch { } for (Map.Entry each : diff.filesToUpdate.entrySet()) { - if (Utils.isZipFile(each.getKey())) { + if (!Runner.ZIP_AS_BINARY && Utils.isZipFile(each.getKey())) { tempActions.add(new UpdateZipAction(each.getKey(), each.getValue())); } else { @@ -98,6 +104,9 @@ public class Patch { else if (clazz == UpdateZipAction.class) { key = UPDATE_ZIP_ACTION_KEY; } + else if (clazz == DeleteZipAction.class) { + key = DELETE_ZIP_ACTION_KEY; + } else if (clazz == DeleteAction.class) { key = DELETE_ACTION_KEY; } @@ -135,6 +144,9 @@ public class Patch { case DELETE_ACTION_KEY: a = new DeleteAction(in); break; + case DELETE_ZIP_ACTION_KEY: + a = new DeleteZipAction(in); + break; default: throw new RuntimeException("Unknown action type " + key); } diff --git a/updater/src/com/intellij/updater/PatchAction.java b/updater/src/com/intellij/updater/PatchAction.java index 42afb50b989d..e91330eae3bd 100644 --- a/updater/src/com/intellij/updater/PatchAction.java +++ b/updater/src/com/intellij/updater/PatchAction.java @@ -4,6 +4,8 @@ import java.io.*; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; @@ -60,9 +62,15 @@ public abstract class PatchAction { protected abstract void doBuildPatchFile(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException; public boolean shouldApply(File toDir, Map options) { + File file = getFile(toDir); ValidationResult.Option option = options.get(myPath); if (option == ValidationResult.Option.KEEP || option == ValidationResult.Option.IGNORE) return false; - return shouldApplyOn(getFile(toDir)); + if (option == ValidationResult.Option.KILL_PROCESS) { + for (NativeFileManager.Process process : NativeFileManager.getProcessesUsing(file)) { + process.terminate(); + } + } + return shouldApplyOn(file); } protected boolean shouldApplyOn(File toFile) { @@ -78,6 +86,10 @@ public abstract class PatchAction { protected ValidationResult doValidateAccess(File toFile, ValidationResult.Action action) { if (!toFile.exists()) return null; if (toFile.isDirectory()) return null; + ValidationResult result = validateProcessLock(toFile, action); + if (result != null) { + return result; + } if (toFile.canRead() && toFile.canWrite() && isWritable(toFile)) return null; return new ValidationResult(ValidationResult.Kind.ERROR, myPath, @@ -111,6 +123,23 @@ public abstract class PatchAction { } } + private ValidationResult validateProcessLock(File toFile, ValidationResult.Action action) { + List processes = NativeFileManager.getProcessesUsing(toFile); + if (processes.size() > 0) { + Iterator it = processes.iterator(); + String message = "Locked by: " + it.next().name; + while (it.hasNext()) { + message += ", " + it.next().name; + } + return new ValidationResult(ValidationResult.Kind.ERROR, + myPath, + action, + message, + ValidationResult.Option.KILL_PROCESS); + } + return null; + } + protected ValidationResult doValidateNotChanged(File toFile, ValidationResult.Kind kind, ValidationResult.Action action) throws IOException { if (toFile.exists()) { @@ -132,9 +161,7 @@ public abstract class PatchAction { return null; } - protected boolean isModified(File toFile) throws IOException { - return myChecksum != Digester.digestFile(toFile); - } + abstract protected boolean isModified(File toFile) throws IOException; public void apply(ZipFile patchFile, File toDir) throws IOException { doApply(patchFile, getFile(toDir)); diff --git a/updater/src/com/intellij/updater/RetryException.java b/updater/src/com/intellij/updater/RetryException.java deleted file mode 100644 index e13c4eb0f2c8..000000000000 --- a/updater/src/com/intellij/updater/RetryException.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.updater; - -import java.io.IOException; - -/** - * Exception thrown when an IOException arises when performing a patch - * action and it's likely that retrying will be successful. - */ -public class RetryException extends IOException { - public RetryException() { - } - - public RetryException(String message) { - super(message); - } - - public RetryException(String message, Throwable cause) { - super(message, cause); - } - - public RetryException(Throwable cause) { - super(cause); - } -} diff --git a/updater/src/com/intellij/updater/Runner.java b/updater/src/com/intellij/updater/Runner.java index 56a331b5ebc8..354dba99713a 100644 --- a/updater/src/com/intellij/updater/Runner.java +++ b/updater/src/com/intellij/updater/Runner.java @@ -18,6 +18,13 @@ import java.util.zip.ZipInputStream; public class Runner { public static Logger logger = null; + /** + * Treats zip files as regular binary files. When false, zip/jar files are unzipped and diffed file by file. + * When true, the entire zip file is diffed as a single file. Set to true if preserving the timestamps of + * the files inside the zip is important. This variable can change via a command line option. + */ + public static boolean ZIP_AS_BINARY = false; + private static final String PATCH_FILE_NAME = "patch-file.zip"; private static final String PATCH_PROPERTIES_ENTRY = "patch.properties"; private static final String OLD_BUILD_DESCRIPTION = "old.build.description"; @@ -32,28 +39,19 @@ public class Runner { String patchFile = args[5]; initLogger(); + ZIP_AS_BINARY = Arrays.asList(args).contains("--zip_as_binary"); + List ignoredFiles = extractFiles(args, "ignored"); List criticalFiles = extractFiles(args, "critical"); List optionalFiles = extractFiles(args, "optional"); create(oldVersionDesc, newVersionDesc, oldFolder, newFolder, patchFile, ignoredFiles, criticalFiles, optionalFiles); } else if (args.length >= 2 && "install".equals(args[0])) { - // install [--exit0] - int nextArg = 1; - - // Default install exit code is SwingUpdaterUI.RESULT_REQUIRES_RESTART (42) unless overridden to be 0. - // This is used by testUI/build.gradle as gradle expects a javaexec to exit with code 0. - boolean useExitCode0 = false; - if (args[nextArg].equals("--exit0")) { - useExitCode0 = true; - nextArg++; - } - - String destFolder = args[nextArg++]; + String destFolder = args[1]; initLogger(); logger.info("destFolder: " + destFolder); - install(useExitCode0, destFolder); + install(destFolder); } else { printUsage(); @@ -104,10 +102,6 @@ public class Runner { } } - public static void infoStackTrace(String msg, Throwable e){ - logger.info(msg, e); - } - public static void printStackTrace(Throwable e){ logger.error(e.getMessage(), e); } @@ -130,8 +124,8 @@ public class Runner { private static void printUsage() { System.err.println("Usage:\n" + "create " + - " [ignored=file1;file2;...] [critical=file1;file2;...] [optional=file1;file2;...]\n" + - "install [--exit0] [log_directory]\n"); + " [ignored=file1;file2;...] [critical=file1;file2;...] [optional=file1;file2;...]\n" + + "install \n"); } private static void create(String oldBuildDesc, @@ -142,31 +136,9 @@ public class Runner { List ignoredFiles, List criticalFiles, List optionalFiles) throws IOException, OperationCancelledException { - File tempPatchFile = Utils.createTempFile(); - createImpl(oldBuildDesc, - newBuildDesc, - oldFolder, - newFolder, - patchFile, - tempPatchFile, - ignoredFiles, - criticalFiles, - optionalFiles, - new ConsoleUpdaterUI(), resolveJarFile()); - } - - static void createImpl(String oldBuildDesc, - String newBuildDesc, - String oldFolder, - String newFolder, - String outPatchJar, - File tempPatchFile, - List ignoredFiles, - List criticalFiles, - List optionalFiles, - UpdaterUI ui, - File resolvedJar) throws IOException, OperationCancelledException { + UpdaterUI ui = new ConsoleUpdaterUI(); try { + File tempPatchFile = Utils.createTempFile(); PatchFileCreator.create(new File(oldFolder), new File(newFolder), tempPatchFile, @@ -175,13 +147,13 @@ public class Runner { optionalFiles, ui); - logger.info("Packing JAR file: " + outPatchJar ); - ui.startProcess("Packing JAR file '" + outPatchJar + "'..."); + logger.info("Packing JAR file: " + patchFile ); + ui.startProcess("Packing JAR file '" + patchFile + "'..."); - FileOutputStream fileOut = new FileOutputStream(outPatchJar); + FileOutputStream fileOut = new FileOutputStream(patchFile); try { ZipOutputWrapper out = new ZipOutputWrapper(fileOut); - ZipInputStream in = new ZipInputStream(new FileInputStream(resolvedJar)); + ZipInputStream in = new ZipInputStream(new FileInputStream(resolveJarFile())); try { ZipEntry e; while ((e = in.getNextEntry()) != null) { @@ -223,14 +195,16 @@ public class Runner { Utils.cleanup(); } - private static void install(final boolean useExitCode0, final String destFolder) throws Exception { + private static void install(final String destFolder) throws Exception { InputStream in = Runner.class.getResourceAsStream("/" + PATCH_PROPERTIES_ENTRY); Properties props = new Properties(); - try { - props.load(in); - } - finally { - in.close(); + if (in != null) { + try { + props.load(in); + } + finally { + in.close(); + } } // todo[r.sh] to delete in IDEA 14 (after a full circle of platform updates) @@ -250,9 +224,7 @@ public class Runner { new SwingUpdaterUI(props.getProperty(OLD_BUILD_DESCRIPTION), props.getProperty(NEW_BUILD_DESCRIPTION), - useExitCode0 ? 0 : SwingUpdaterUI.RESULT_REQUIRES_RESTART, new SwingUpdaterUI.InstallOperation() { - @Override public boolean execute(UpdaterUI ui) throws OperationCancelledException { logger.info("installing patch to the " + destFolder); return doInstall(ui, destFolder); @@ -260,26 +232,11 @@ public class Runner { }); } - interface IJarResolver { - File resolveJar() throws IOException; - } - private static boolean doInstall(UpdaterUI ui, String destFolder) throws OperationCancelledException { - return doInstallImpl(ui, destFolder, new IJarResolver() { - @Override - public File resolveJar() throws IOException { - return resolveJarFile(); - } - }); - } - - static boolean doInstallImpl(UpdaterUI ui, - String destFolder, - IJarResolver jarResolver) throws OperationCancelledException { try { try { File patchFile = Utils.createTempFile(); - ZipFile jarFile = new ZipFile(jarResolver.resolveJar()); + ZipFile jarFile = new ZipFile(resolveJarFile()); logger.info("Extracting patch file..."); ui.startProcess("Extracting patch file..."); @@ -325,6 +282,10 @@ public class Runner { } private static File resolveJarFile() throws IOException { + String jar = System.getProperty("JAR_FILE"); + if (jar != null) { + return new File(jar); + } URL url = Runner.class.getResource(""); if (url == null) throw new IOException("Cannot resolve JAR file path"); if (!"jar".equals(url.getProtocol())) throw new IOException("Patch file is not a JAR file"); diff --git a/updater/src/com/intellij/updater/SwingUpdaterUI.java b/updater/src/com/intellij/updater/SwingUpdaterUI.java index 749dcc2fc350..d44fc2e094f1 100644 --- a/updater/src/com/intellij/updater/SwingUpdaterUI.java +++ b/updater/src/com/intellij/updater/SwingUpdaterUI.java @@ -10,7 +10,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; @@ -20,7 +19,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; public class SwingUpdaterUI implements UpdaterUI { - static final int RESULT_REQUIRES_RESTART = 42; + private static final int RESULT_REQUIRES_RESTART = 42; private static final EmptyBorder FRAME_BORDER = new EmptyBorder(8, 8, 8, 8); private static final EmptyBorder LABEL_BORDER = new EmptyBorder(0, 0, 5, 0); @@ -33,7 +32,6 @@ public class SwingUpdaterUI implements UpdaterUI { private static final String PROCEED_BUTTON_TITLE = "Proceed"; - private final int mySuccessExitCode; private final InstallOperation myOperation; private final JLabel myProcessTitle; @@ -42,30 +40,16 @@ public class SwingUpdaterUI implements UpdaterUI { private final JTextArea myConsole; private final JPanel myConsolePane; - private final JButton myRetryButton; private final JButton myCancelButton; private final ConcurrentLinkedQueue myQueue = new ConcurrentLinkedQueue(); private final AtomicBoolean isCancelled = new AtomicBoolean(false); private final AtomicBoolean isRunning = new AtomicBoolean(false); private final AtomicBoolean hasError = new AtomicBoolean(false); - private final AtomicBoolean hasRetry = new AtomicBoolean(false); private final JFrame myFrame; private boolean myApplied; - /** - * Displays the updater UI and asynchronously runs the operation list. - * - * @param oldBuildDesc The old build description, for display purposes. - * @param newBuildDesc The new build description, for display purposes. - * @param successExitCode The desired exit code on success. Default is {@link #RESULT_REQUIRES_RESTART}. - * @param operation The install operations to perform. - */ - public SwingUpdaterUI(String oldBuildDesc, - String newBuildDesc, - int successExitCode, - InstallOperation operation) { - mySuccessExitCode = successExitCode; + public SwingUpdaterUI(String oldBuildDesc, String newBuildDesc, InstallOperation operation) { myOperation = operation; myProcessTitle = new JLabel(" "); @@ -74,10 +58,6 @@ public class SwingUpdaterUI implements UpdaterUI { myCancelButton = new JButton(CANCEL_BUTTON_TITLE); - myRetryButton = new JButton("Retry"); - myRetryButton.setEnabled(false); - myRetryButton.setVisible(false); - myConsole = new JTextArea(); myConsole.setLineWrap(true); myConsole.setWrapStyleWord(true); @@ -90,19 +70,11 @@ public class SwingUpdaterUI implements UpdaterUI { myConsolePane.setVisible(false); myCancelButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { doCancel(); } }); - myRetryButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - doRetry(); - } - }); - myFrame = new JFrame(); myFrame.setTitle(TITLE); @@ -132,7 +104,6 @@ public class SwingUpdaterUI implements UpdaterUI { buttonsPanel.setBorder(BUTTONS_BORDER); buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.X_AXIS)); buttonsPanel.add(Box.createHorizontalGlue()); - buttonsPanel.add(myRetryButton); buttonsPanel.add(myCancelButton); myProcessTitle.setText("Updating " + oldBuildDesc + " to " + newBuildDesc + "..."); @@ -158,7 +129,6 @@ public class SwingUpdaterUI implements UpdaterUI { private void startRequestDispatching() { new Thread(new Runnable() { - @Override public void run() { while (true) { try { @@ -176,7 +146,6 @@ public class SwingUpdaterUI implements UpdaterUI { } SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { for (UpdateRequest each : pendingRequests) { each.perform(); @@ -203,28 +172,10 @@ public class SwingUpdaterUI implements UpdaterUI { } } - private void doRetry() { - hasError.set(false); - hasRetry.set(false); - isCancelled.set(false); - myQueue.add(new UpdateRequest() { - @Override - public void perform() { - myConsole.setText(""); - myConsolePane.setVisible(false); - myConsolePane.setPreferredSize(new Dimension(10, 200)); - myRetryButton.setEnabled(false); - myCancelButton.setEnabled(true); - } - }); - doPerform(); - } - private void doPerform() { isRunning.set(true); new Thread(new Runnable() { - @Override public void run() { try { myApplied = myOperation.execute(SwingUpdaterUI.this); @@ -239,10 +190,6 @@ public class SwingUpdaterUI implements UpdaterUI { finally { isRunning.set(false); - if (hasRetry.get()) { - myRetryButton.setVisible(true); - myRetryButton.setEnabled(true); - } if (hasError.get()) { startProcess("Failed to apply patch"); setProgress(100); @@ -257,17 +204,15 @@ public class SwingUpdaterUI implements UpdaterUI { } private void exit() { - System.exit(myApplied ? mySuccessExitCode : 0); + System.exit(myApplied ? RESULT_REQUIRES_RESTART : 0); } - @Override public Map askUser(final List validationResults) throws OperationCancelledException { if (validationResults.isEmpty()) return Collections.emptyMap(); final Map result = new HashMap(); try { SwingUtilities.invokeAndWait(new Runnable() { - @Override public void run() { final JDialog dialog = new JDialog(myFrame, TITLE, true); dialog.setLayout(new BorderLayout()); @@ -279,7 +224,6 @@ public class SwingUpdaterUI implements UpdaterUI { buttonsPanel.add(Box.createHorizontalGlue()); JButton proceedButton = new JButton(PROCEED_BUTTON_TITLE); proceedButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { dialog.setVisible(false); } @@ -287,7 +231,6 @@ public class SwingUpdaterUI implements UpdaterUI { JButton cancelButton = new JButton(CANCEL_BUTTON_TITLE); cancelButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { isCancelled.set(true); myCancelButton.setEnabled(false); @@ -344,10 +287,8 @@ public class SwingUpdaterUI implements UpdaterUI { return result; } - @Override public void startProcess(final String title) { myQueue.add(new UpdateRequest() { - @Override public void perform() { myProcessStatus.setText(title); myProcessProgress.setIndeterminate(false); @@ -356,10 +297,8 @@ public class SwingUpdaterUI implements UpdaterUI { }); } - @Override public void setProgress(final int percentage) { myQueue.add(new UpdateRequest() { - @Override public void perform() { myProcessProgress.setIndeterminate(false); myProcessProgress.setValue(percentage); @@ -367,43 +306,21 @@ public class SwingUpdaterUI implements UpdaterUI { }); } - @Override public void setProgressIndeterminate() { myQueue.add(new UpdateRequest() { - @Override public void perform() { myProcessProgress.setIndeterminate(true); } }); } - @Override public void setStatus(final String status) { } - @Override public void showError(final Throwable e) { hasError.set(true); - StringWriter w = new StringWriter(); - - if (e instanceof RetryException) { - hasRetry.set(true); - - w.write("+----------------\n"); - w.write("| A file operation failed.\n"); - w.write("| This might be due to a file being locked by another\n"); - w.write("| application. Please try closing any application\n"); - w.write("| that uses the files being updated then press 'Retry'.\n"); - w.write("+----------------\n"); - w.write("\n\n"); - } - - e.printStackTrace(new PrintWriter(w)); - - final String content = w.getBuffer().toString(); myQueue.add(new UpdateRequest() { - @Override public void perform() { StringWriter w = new StringWriter(); if (!myConsolePane.isVisible()) { @@ -411,8 +328,9 @@ public class SwingUpdaterUI implements UpdaterUI { w.write(System.getProperty("java.io.tmpdir")); w.write("\n\n"); } + e.printStackTrace(new PrintWriter(w)); + w.append("\n"); myConsole.append(w.getBuffer().toString()); - myConsole.append(content); if (!myConsolePane.isVisible()) { myConsole.setCaretPosition(0); myConsolePane.setVisible(true); @@ -423,7 +341,6 @@ public class SwingUpdaterUI implements UpdaterUI { }); } - @Override public void checkCancelled() throws OperationCancelledException { if (isCancelled.get()) throw new OperationCancelledException(); } @@ -437,8 +354,7 @@ public class SwingUpdaterUI implements UpdaterUI { } public static void main(String[] args) { - new SwingUpdaterUI("xxx", "yyy", RESULT_REQUIRES_RESTART, new InstallOperation() { - @Override + new SwingUpdaterUI("xxx", "yyy", new InstallOperation() { public boolean execute(UpdaterUI ui) throws OperationCancelledException { ui.startProcess("Process1"); ui.checkCancelled(); @@ -522,7 +438,6 @@ public class SwingUpdaterUI implements UpdaterUI { } } - @Override public int getColumnCount() { return COLUMNS.length; } @@ -549,7 +464,6 @@ public class SwingUpdaterUI implements UpdaterUI { return super.getColumnClass(columnIndex); } - @Override public int getRowCount() { return myItems.size(); } @@ -566,7 +480,6 @@ public class SwingUpdaterUI implements UpdaterUI { } } - @Override public Object getValueAt(int rowIndex, int columnIndex) { Item item = myItems.get(rowIndex); switch (columnIndex) { diff --git a/updater/src/com/intellij/updater/UpdateAction.java b/updater/src/com/intellij/updater/UpdateAction.java index 6c79c44cbc4b..08cf7a188831 100644 --- a/updater/src/com/intellij/updater/UpdateAction.java +++ b/updater/src/com/intellij/updater/UpdateAction.java @@ -22,6 +22,11 @@ public class UpdateAction extends BaseUpdateAction { patchOutput.closeEntry(); } + @Override + protected boolean isModified(File toFile) throws IOException { + return myChecksum != Digester.digestRegularFile(toFile); + } + @Override protected void doApply(ZipFile patchFile, File toFile) throws IOException { InputStream in = Utils.findEntryInputStream(patchFile, myPath); diff --git a/updater/src/com/intellij/updater/UpdateZipAction.java b/updater/src/com/intellij/updater/UpdateZipAction.java index 7741b813318a..d808a735f0cd 100644 --- a/updater/src/com/intellij/updater/UpdateZipAction.java +++ b/updater/src/com/intellij/updater/UpdateZipAction.java @@ -153,6 +153,11 @@ public class UpdateZipAction extends BaseUpdateAction { } } + @Override + protected boolean isModified(File toFile) throws IOException { + return myChecksum != Digester.digestFile(toFile); + } + protected void doApply(final ZipFile patchFile, File toFile) throws IOException { File temp = Utils.createTempFile(); FileOutputStream fileOut = new FileOutputStream(temp); diff --git a/updater/src/com/intellij/updater/Utils.java b/updater/src/com/intellij/updater/Utils.java index b5ad212ac1a9..bce3333e106e 100644 --- a/updater/src/com/intellij/updater/Utils.java +++ b/updater/src/com/intellij/updater/Utils.java @@ -10,19 +10,10 @@ public class Utils { private static final byte[] BUFFER = new byte[64 * 1024]; private static File myTempDir; - public static boolean isWindows() { - return System.getProperty("os.name").startsWith("Windows"); - } - public static boolean isZipFile(String fileName) { return fileName.endsWith(".zip") || fileName.endsWith(".jar"); } - /** - * Creates a new temp file.
- * All the temp files created here are located in a unique root temp directory - * that is automatically deleted by {@link #cleanup()}. - */ @SuppressWarnings({"SSBasedInspection"}) public static File createTempFile() throws IOException { if (myTempDir == null) { @@ -35,12 +26,6 @@ public class Utils { return File.createTempFile("temp.", ".tmp", myTempDir); } - - /** - * Creates a new temp directory.
- * All the temp directories created here are located in a unique root temp directory - * that is automatically deleted by {@link #cleanup()}. - */ public static File createTempDir() throws IOException { File result = createTempFile(); delete(result); @@ -57,15 +42,6 @@ public class Utils { myTempDir = null; } - /** - * Deletes a file or directory with a default timeout of 100 milliseconds. - * Directories are deleted recursively. The timeout occurs on each file. - * If one of the files fails to be deleted, the recursive directory deletion - * is aborted and not retried. - * - * @param file The file or directory to delete. - * @throws IOException - */ public static void delete(File file) throws IOException { if (file.isDirectory()) { File[] files = file.listFiles(); @@ -76,20 +52,15 @@ public class Utils { } } } - for (int i = 0; i < 10; i++) { - if (file.delete() || !file.exists()) { - return; - } + if (file.delete() || !file.exists()) return; try { Thread.sleep(10); } catch (InterruptedException ignore) { Runner.printStackTrace(ignore); } } - if (file.exists()) { - throw new IOException("Cannot delete file " + file); - } + if (file.exists()) throw new IOException("Cannot delete file " + file); } public static void setExecutable(File file, boolean executable) throws IOException { diff --git a/updater/src/com/intellij/updater/ValidationResult.java b/updater/src/com/intellij/updater/ValidationResult.java index 02c142446152..6f56fd3bc0c3 100644 --- a/updater/src/com/intellij/updater/ValidationResult.java +++ b/updater/src/com/intellij/updater/ValidationResult.java @@ -24,7 +24,7 @@ public class ValidationResult implements Comparable { } public enum Option { - IGNORE, KEEP, REPLACE, DELETE + IGNORE, KEEP, REPLACE, DELETE, KILL_PROCESS } public static final String ABSENT_MESSAGE = "Absent"; diff --git a/updater/testSrc/com/intellij/updater/PatchFileCreatorTest.java b/updater/testSrc/com/intellij/updater/PatchFileCreatorTest.java index 4e87db2a8eb0..2cffb224c60f 100644 --- a/updater/testSrc/com/intellij/updater/PatchFileCreatorTest.java +++ b/updater/testSrc/com/intellij/updater/PatchFileCreatorTest.java @@ -6,6 +6,7 @@ import org.junit.Test; import java.io.File; import java.io.IOException; +import java.io.RandomAccessFile; import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -94,6 +95,35 @@ public class PatchFileCreatorTest extends PatchTestCase { assertNothingHasChanged(preparationResult, new HashMap()); } + @Test + public void testRevertedWhenFileToDeleteIsProcessLocked() throws Exception { + if (!UtilsTest.mIsWindows) return; + + PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), TEST_UI); + + + RandomAccessFile raf = new RandomAccessFile(new File(myOlderDir, "bin/idea.bat"),"rw"); + // Lock the file. FileLock is not good here, because we need to prevent deletion. + int b = raf.read(); + raf.seek(0); + raf.write(b); + + try { + PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI); + + Map original = Digester.digestFiles(myOlderDir, Collections.emptyList(), TEST_UI); + + File backup = getTempFile("backup"); + PatchFileCreator.apply(preparationResult, new HashMap(), backup, TEST_UI); + + assertEquals(original, Digester.digestFiles(myOlderDir, Collections.emptyList(), TEST_UI)); + } + finally { + raf.close(); + } + } + @Test public void testApplyingWithAbsentFileToDelete() throws Exception { PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.emptyList(), Collections.emptyList(), @@ -307,6 +337,11 @@ public class PatchFileCreatorTest extends PatchTestCase { super("_dummy_file_", -1); } + @Override + protected boolean isModified(File toFile) throws IOException { + return false; + } + @Override protected void doBuildPatchFile(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException { throw new UnsupportedOperationException(); diff --git a/updater/testSrc/com/intellij/updater/PatchTest.java b/updater/testSrc/com/intellij/updater/PatchTest.java index 14ff6c0abb18..b64ad9f51406 100644 --- a/updater/testSrc/com/intellij/updater/PatchTest.java +++ b/updater/testSrc/com/intellij/updater/PatchTest.java @@ -139,14 +139,16 @@ public class PatchTest extends PatchTestCase { try { FileLock lock = s.getChannel().lock(); try { + String message = UtilsTest.mIsWindows ? "Locked by: Java(TM) Platform SE binary" : ValidationResult.ACCESS_DENIED_MESSAGE; + ValidationResult.Option option = UtilsTest.mIsWindows ? ValidationResult.Option.KILL_PROCESS : ValidationResult.Option.IGNORE; List result = myPatch.validate(myOlderDir, TEST_UI); assertEquals( new HashSet(Arrays.asList( new ValidationResult(ValidationResult.Kind.ERROR, "Readme.txt", ValidationResult.Action.UPDATE, - ValidationResult.ACCESS_DENIED_MESSAGE, - ValidationResult.Option.IGNORE))), + message, + option))), new HashSet(result)); } finally { diff --git a/updater/testSrc/com/intellij/updater/UtilsTest.java b/updater/testSrc/com/intellij/updater/UtilsTest.java index 6e9e4ebf673b..ead979fd4055 100644 --- a/updater/testSrc/com/intellij/updater/UtilsTest.java +++ b/updater/testSrc/com/intellij/updater/UtilsTest.java @@ -23,12 +23,11 @@ import java.io.IOException; public class UtilsTest extends TestCase { - private boolean mIsWindows; + public static boolean mIsWindows = System.getProperty("os.name").startsWith("Windows"); @Override public void setUp() throws Exception { super.setUp(); - mIsWindows = Utils.isWindows(); } public void testDelete() throws Exception { diff --git a/updater/updater.iml b/updater/updater.iml index e55b58d17cd0..e05a4de5185c 100644 --- a/updater/updater.iml +++ b/updater/updater.iml @@ -11,6 +11,7 @@ +