sync updater with changes from android studio updater.

This commit is contained in:
Vladimir.Orlov
2014-10-20 15:02:37 +04:00
parent 53d6d28f6c
commit 8923b4c0ee
17 changed files with 152 additions and 319 deletions
@@ -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);
}
@@ -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;
}
@@ -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);
}
}
@@ -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);
+14 -2
View File
@@ -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<String, Long> 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<String, Long> 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);
}
@@ -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<String, ValidationResult.Option> 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<NativeFileManager.Process> processes = NativeFileManager.getProcessesUsing(toFile);
if (processes.size() > 0) {
Iterator<NativeFileManager.Process> 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));
@@ -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);
}
}
+32 -71
View File
@@ -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<String> ignoredFiles = extractFiles(args, "ignored");
List<String> criticalFiles = extractFiles(args, "critical");
List<String> optionalFiles = extractFiles(args, "optional");
create(oldVersionDesc, newVersionDesc, oldFolder, newFolder, patchFile, ignoredFiles, criticalFiles, optionalFiles);
}
else if (args.length >= 2 && "install".equals(args[0])) {
// install [--exit0] <destination_folder>
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 <old_version_description> <new_version_description> <old_version_folder> <new_version_folder>" +
" <patch_file_name> <log_directory> [ignored=file1;file2;...] [critical=file1;file2;...] [optional=file1;file2;...]\n" +
"install [--exit0] <destination_folder> [log_directory]\n");
" <patch_file_name> [ignored=file1;file2;...] [critical=file1;file2;...] [optional=file1;file2;...]\n" +
"install <destination_folder>\n");
}
private static void create(String oldBuildDesc,
@@ -142,31 +136,9 @@ public class Runner {
List<String> ignoredFiles,
List<String> criticalFiles,
List<String> 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<String> ignoredFiles,
List<String> criticalFiles,
List<String> 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");
@@ -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<UpdateRequest> myQueue = new ConcurrentLinkedQueue<UpdateRequest>();
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("<html>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<String, ValidationResult.Option> askUser(final List<ValidationResult> validationResults) throws OperationCancelledException {
if (validationResults.isEmpty()) return Collections.emptyMap();
final Map<String, ValidationResult.Option> result = new HashMap<String, ValidationResult.Option>();
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) {
@@ -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);
@@ -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);
+2 -31
View File
@@ -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. <br/>
* 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. <br/>
* 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 {
@@ -24,7 +24,7 @@ public class ValidationResult implements Comparable<ValidationResult> {
}
public enum Option {
IGNORE, KEEP, REPLACE, DELETE
IGNORE, KEEP, REPLACE, DELETE, KILL_PROCESS
}
public static final String ABSENT_MESSAGE = "Absent";
@@ -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<String, ValidationResult.Option>());
}
@Test
public void testRevertedWhenFileToDeleteIsProcessLocked() throws Exception {
if (!UtilsTest.mIsWindows) return;
PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
Collections.<String>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<String, Long> original = Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI);
File backup = getTempFile("backup");
PatchFileCreator.apply(preparationResult, new HashMap<String, ValidationResult.Option>(), backup, TEST_UI);
assertEquals(original, Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI));
}
finally {
raf.close();
}
}
@Test
public void testApplyingWithAbsentFileToDelete() throws Exception {
PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>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();
@@ -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<ValidationResult> result = myPatch.validate(myOlderDir, TEST_UI);
assertEquals(
new HashSet<ValidationResult>(Arrays.asList(
new ValidationResult(ValidationResult.Kind.ERROR,
"Readme.txt",
ValidationResult.Action.UPDATE,
ValidationResult.ACCESS_DENIED_MESSAGE,
ValidationResult.Option.IGNORE))),
message,
option))),
new HashSet<ValidationResult>(result));
}
finally {
@@ -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 {
+1
View File
@@ -11,6 +11,7 @@
<orderEntry type="library" name="Log4J" level="project" />
<orderEntry type="module" module-name="testFramework" scope="TEST" />
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
<orderEntry type="library" name="jna" level="project" />
</component>
</module>