mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
sync updater with changes from android studio updater.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user