mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:10:43 +07:00
synchronized Updater with the new version from google.
Changes: - Strict patches - Directories as files - Support for moving files - Making use of critical files - Zip file normalization for binary patches - Mac patches from the application folder
This commit is contained in:
@@ -1,59 +0,0 @@
|
||||
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 abstract class BaseDeleteAction extends PatchAction {
|
||||
public BaseDeleteAction(String path, long checksum) {
|
||||
super(path, checksum);
|
||||
}
|
||||
|
||||
public BaseDeleteAction(DataInputStream in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@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 {
|
||||
Utils.delete(toFile);
|
||||
}
|
||||
|
||||
protected void doBackup(File toFile, File backupFile) throws IOException {
|
||||
Utils.copy(toFile, backupFile);
|
||||
}
|
||||
|
||||
protected void doRevert(File toFile, File backupFile) throws IOException {
|
||||
if (!toFile.exists() || toFile.isDirectory() || 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,47 +7,79 @@ import java.io.*;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public abstract class BaseUpdateAction extends PatchAction {
|
||||
public BaseUpdateAction(String path, long checksum) {
|
||||
super(path, checksum);
|
||||
private final String mySource;
|
||||
protected final boolean myIsMove;
|
||||
|
||||
public BaseUpdateAction(Patch patch, String path, String source, long checksum, boolean move) {
|
||||
super(patch, path, checksum);
|
||||
myIsMove = move;
|
||||
mySource = source;
|
||||
}
|
||||
|
||||
public BaseUpdateAction(DataInputStream in) throws IOException {
|
||||
super(in);
|
||||
public BaseUpdateAction(Patch patch, DataInputStream in) throws IOException {
|
||||
super(patch, in);
|
||||
mySource = in.readUTF();
|
||||
myIsMove = in.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValidationResult doValidate(File toFile) throws IOException {
|
||||
ValidationResult result = doValidateAccess(toFile, ValidationResult.Action.UPDATE);
|
||||
if (result != null) return result;
|
||||
return doValidateNotChanged(toFile, ValidationResult.Kind.ERROR, ValidationResult.Action.UPDATE);
|
||||
public void write(DataOutputStream out) throws IOException {
|
||||
super.write(out);
|
||||
out.writeUTF(mySource);
|
||||
out.writeBoolean(myIsMove);
|
||||
}
|
||||
|
||||
protected File getSource(File toDir) {
|
||||
return new File(toDir, mySource);
|
||||
}
|
||||
|
||||
public String getSourcePath() {
|
||||
return mySource;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldApplyOn(File toFile) {
|
||||
protected boolean doShouldApply(File toDir) {
|
||||
// if the file is optional in may not exist
|
||||
return toFile.exists();
|
||||
return getSource(toDir).exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildPatchFile(File olderDir, File newerDir, ZipOutputStream patchOutput) throws IOException {
|
||||
doBuildPatchFile(getSource(olderDir), getFile(newerDir), patchOutput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult validate(File toDir) throws IOException {
|
||||
File fromFile = getSource(toDir);
|
||||
ValidationResult result = doValidateAccess(fromFile, ValidationResult.Action.UPDATE);
|
||||
if (result != null) return result;
|
||||
if (!mySource.isEmpty()) {
|
||||
result = doValidateAccess(getFile(toDir), ValidationResult.Action.UPDATE);
|
||||
if (result != null) return result;
|
||||
}
|
||||
return doValidateNotChanged(fromFile, ValidationResult.Kind.ERROR, ValidationResult.Action.UPDATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBackup(File toFile, File backupFile) throws IOException {
|
||||
Utils.copy(toFile, backupFile);
|
||||
Utils.mirror(toFile, backupFile);
|
||||
}
|
||||
|
||||
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()) throw new IOException("Cannot delete file " + dest);
|
||||
if (dest.exists() && !dest.delete()) throw new IOException("Cannot delete file " + dest);
|
||||
Utils.copy(from, dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRevert(File toFile, File backupFile) throws IOException {
|
||||
if (!toFile.exists() || isModified(toFile)) {
|
||||
Utils.copy(backupFile, toFile);
|
||||
Utils.mirror(backupFile, toFile);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeDiff(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException {
|
||||
BufferedInputStream olderFileIn = new BufferedInputStream(new FileInputStream(olderFile));
|
||||
protected void writeDiff(File olderFile, File newerFile, OutputStream patchOutput) throws IOException {
|
||||
BufferedInputStream olderFileIn = new BufferedInputStream(Utils.newFileInputStream(olderFile, myPatch.isNormalized()));
|
||||
BufferedInputStream newerFileIn = new BufferedInputStream(new FileInputStream(newerFile));
|
||||
try {
|
||||
writeDiff(olderFileIn, newerFileIn, patchOutput);
|
||||
@@ -58,14 +90,14 @@ public abstract class BaseUpdateAction extends PatchAction {
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeDiff(InputStream olderFileIn, InputStream newerFileIn, ZipOutputStream patchOutput)
|
||||
protected void writeDiff(InputStream olderFileIn, InputStream newerFileIn, OutputStream patchOutput)
|
||||
throws IOException {
|
||||
Runner.logger.info("writing diff");
|
||||
ByteArrayOutputStream diffOutput = new ByteArrayOutputStream();
|
||||
byte[] newerFileBuffer = JBDiff.bsdiff(olderFileIn, newerFileIn, diffOutput);
|
||||
diffOutput.close();
|
||||
|
||||
if (diffOutput.size() < newerFileBuffer.length) {
|
||||
if (!isCritical() && diffOutput.size() < newerFileBuffer.length) {
|
||||
patchOutput.write(1);
|
||||
Utils.copyBytesToStream(diffOutput, patchOutput);
|
||||
}
|
||||
@@ -83,4 +115,17 @@ public abstract class BaseUpdateAction extends PatchAction {
|
||||
Utils.copyStream(patchInput, toFileOut);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String moveInfo = "";
|
||||
if (!mySource.equals(myPath)) {
|
||||
moveInfo = "[" + (myIsMove ? "= " : "~ ") + mySource + "]";
|
||||
}
|
||||
return super.toString() + moveInfo;
|
||||
}
|
||||
|
||||
public boolean isMove() {
|
||||
return myIsMove;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,17 @@ public class ConsoleUpdaterUI implements UpdaterUI {
|
||||
public void checkCancelled() throws OperationCancelledException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String oldBuildDesc, String newBuildDesc) {
|
||||
System.out.println("From " + oldBuildDesc + " to " + newBuildDesc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showWarning(String message) {
|
||||
System.out.println("Warning: " + message);
|
||||
return false;
|
||||
}
|
||||
|
||||
public Map<String, ValidationResult.Option> askUser(List<ValidationResult> validationResults) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@@ -9,35 +9,40 @@ import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class CreateAction extends PatchAction {
|
||||
public CreateAction(String path) {
|
||||
super(path, -1);
|
||||
public CreateAction(Patch patch, String path) {
|
||||
super(patch, path, Digester.INVALID);
|
||||
}
|
||||
|
||||
public CreateAction(DataInputStream in) throws IOException {
|
||||
super(in);
|
||||
public CreateAction(Patch patch, DataInputStream in) throws IOException {
|
||||
super(patch, in);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBuildPatchFile(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException {
|
||||
Runner.logger.info("building PatchFile");
|
||||
patchOutput.putNextEntry(new ZipEntry(myPath));
|
||||
|
||||
writeExecutableFlag(patchOutput, newerFile);
|
||||
Utils.copyFileToStream(newerFile, patchOutput);
|
||||
if (!newerFile.isDirectory()) {
|
||||
writeExecutableFlag(patchOutput, newerFile);
|
||||
Utils.copyFileToStream(newerFile, patchOutput);
|
||||
}
|
||||
|
||||
patchOutput.closeEntry();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValidationResult doValidate(File toFile) {
|
||||
public ValidationResult validate(File toDir) {
|
||||
File toFile = getFile(toDir);
|
||||
ValidationResult result = doValidateAccess(toFile, ValidationResult.Action.CREATE);
|
||||
if (result != null) return result;
|
||||
|
||||
if (toFile.exists()) {
|
||||
return new ValidationResult(ValidationResult.Kind.CONFLICT,
|
||||
myPath,
|
||||
ValidationResult.Option[] options = myPatch.isStrict()
|
||||
? new ValidationResult.Option[]{ValidationResult.Option.REPLACE}
|
||||
: new ValidationResult.Option[]{ValidationResult.Option.REPLACE, ValidationResult.Option.KEEP};
|
||||
return new ValidationResult(ValidationResult.Kind.CONFLICT, myPath,
|
||||
ValidationResult.Action.CREATE,
|
||||
ValidationResult.ALREADY_EXISTS_MESSAGE,
|
||||
ValidationResult.Option.REPLACE, ValidationResult.Option.KEEP);
|
||||
options);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -48,17 +53,24 @@ public class CreateAction extends PatchAction {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doApply(ZipFile patchFile, File toFile) throws IOException {
|
||||
protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException {
|
||||
prepareToWriteFile(toFile);
|
||||
|
||||
InputStream in = Utils.getEntryInputStream(patchFile, myPath);
|
||||
try {
|
||||
boolean executable = readExecutableFlag(in);
|
||||
Utils.copyStreamToFile(in, toFile);
|
||||
Utils.setExecutable(toFile, executable);
|
||||
}
|
||||
finally {
|
||||
in.close();
|
||||
ZipEntry entry = Utils.getZipEntry(patchFile, myPath);
|
||||
if (entry.isDirectory()) {
|
||||
if (!toFile.mkdir()) {
|
||||
throw new IOException("Unable to create directory " + myPath);
|
||||
}
|
||||
} else {
|
||||
InputStream in = Utils.findEntryInputStreamForEntry(patchFile, entry);
|
||||
try {
|
||||
boolean executable = readExecutableFlag(in);
|
||||
Utils.copyStreamToFile(in, toFile);
|
||||
Utils.setExecutable(toFile, executable);
|
||||
}
|
||||
finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,18 +3,60 @@ 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 BaseDeleteAction {
|
||||
public DeleteAction(String path, long checksum) {
|
||||
super(path, checksum);
|
||||
public class DeleteAction extends PatchAction {
|
||||
public DeleteAction(Patch patch, String path, long checksum) {
|
||||
super(patch, path, checksum);
|
||||
}
|
||||
|
||||
public DeleteAction(DataInputStream in) throws IOException {
|
||||
super(in);
|
||||
public DeleteAction(Patch patch, DataInputStream in) throws IOException {
|
||||
super(patch, in);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isModified(File toFile) throws IOException {
|
||||
return myChecksum != Digester.digestRegularFile(toFile);
|
||||
public void doBuildPatchFile(File olderDir, File newerFile, ZipOutputStream patchOutput) throws IOException {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult validate(File toDir) throws IOException {
|
||||
File toFile = getFile(toDir);
|
||||
ValidationResult result = doValidateAccess(toFile, ValidationResult.Action.DELETE);
|
||||
if (result != null) return result;
|
||||
|
||||
if (myPatch.validateDeletion(myPath) && toFile.exists() && isModified(toFile)) {
|
||||
ValidationResult.Option[] options = myPatch.isStrict()
|
||||
? new ValidationResult.Option[]{ValidationResult.Option.DELETE}
|
||||
: new ValidationResult.Option[]{ValidationResult.Option.DELETE, ValidationResult.Option.KEEP};
|
||||
ValidationResult.Action action = myChecksum == Digester.INVALID ? ValidationResult.Action.VALIDATE : ValidationResult.Action.DELETE;
|
||||
String message = myChecksum == Digester.INVALID ? "Unexpected file" : "Modified";
|
||||
return new ValidationResult(ValidationResult.Kind.CONFLICT, myPath, action, message, options);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doShouldApply(File toDir) {
|
||||
return getFile(toDir).exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException {
|
||||
Utils.delete(toFile);
|
||||
}
|
||||
|
||||
@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() || toFile.isDirectory() || 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.intellij.updater;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class DeleteZipAction extends BaseDeleteAction {
|
||||
public DeleteZipAction(String path, long checksum) {
|
||||
super(path, checksum);
|
||||
}
|
||||
|
||||
public DeleteZipAction(DataInputStream in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isModified(File toFile) throws IOException {
|
||||
return myChecksum != Digester.digestFile(toFile);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,148 @@
|
||||
package com.intellij.updater;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
public class DiffCalculator {
|
||||
public static Result calculate(Map<String, Long> oldChecksums, Map<String, Long> newChecksums) {
|
||||
public static Result calculate(Map<String, Long> oldChecksums, Map<String, Long> newChecksums, List<String> critical, boolean move) {
|
||||
Result result = new Result();
|
||||
result.commonFiles = collect(oldChecksums, newChecksums, critical, true);
|
||||
result.filesToDelete = withAllRemoved(oldChecksums, newChecksums);
|
||||
result.filesToCreate = withAllRemoved(newChecksums, oldChecksums).keySet();
|
||||
result.filesToUpdate = collect(oldChecksums, newChecksums, false);
|
||||
|
||||
Map<String, Long> toUpdate = collect(oldChecksums, newChecksums, critical, false);
|
||||
Map<String, Long> toCreate = withAllRemoved(newChecksums, oldChecksums);
|
||||
|
||||
// Some creates become updates if found in different directories.
|
||||
result.filesToCreate = new LinkedHashMap<String, Long>();
|
||||
result.filesToUpdate = new LinkedHashMap<String, Update>();
|
||||
|
||||
for (Map.Entry<String, Long> update : toUpdate.entrySet()) {
|
||||
result.filesToUpdate.put(update.getKey(), new Update(update.getKey(), update.getValue(), false));
|
||||
}
|
||||
|
||||
if (move) {
|
||||
Map<Long, String> byContent = inverse(result.filesToDelete);
|
||||
Map<String, List<String>> byName = groupFilesByName(result.filesToDelete);
|
||||
|
||||
// Find first by content
|
||||
for (Map.Entry<String, Long> create : toCreate.entrySet()) {
|
||||
boolean isDir = create.getKey().endsWith("/");
|
||||
String source = byContent.get(create.getValue());
|
||||
boolean found = false;
|
||||
if (source != null && !isDir) {
|
||||
// Found a file with the same content use it, unless it's critical
|
||||
if (!critical.contains(source)) {
|
||||
result.filesToUpdate.put(create.getKey(), new Update(source, result.filesToDelete.get(source), true));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
File fileToCreate = new File(create.getKey());
|
||||
List<String> sameName = byName.get(fileToCreate.getName());
|
||||
if (sameName != null && !isDir) {
|
||||
String best = findBestCandidateForMove(sameName, create.getKey());
|
||||
// Found a file with the same name, if it's not critical use it, worst case as big as a create.
|
||||
if (!critical.contains(best)) {
|
||||
result.filesToUpdate.put(create.getKey(), new Update(best, result.filesToDelete.get(best), false));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// Fine, just create it.
|
||||
result.filesToCreate.put(create.getKey(), create.getValue());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.filesToCreate = toCreate;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String findBestCandidateForMove(List<String> paths, String path) {
|
||||
int common = 0;
|
||||
String best = "";
|
||||
String[] dirs = path.split("/");
|
||||
for (String other : paths) {
|
||||
String[] others = other.split("/");
|
||||
for (int i = 0; i < dirs.length && i < others.length; i++) {
|
||||
if (dirs[dirs.length - i - 1].equals(others[others.length - i - 1])) {
|
||||
if (i + 1 > common) {
|
||||
best = other;
|
||||
common = i + 1;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private static Map<String, List<String>> groupFilesByName(Map<String, Long> toDelete) {
|
||||
Map<String, List<String>> result = new HashMap<String, List<String>>();
|
||||
for (String path : toDelete.keySet()) {
|
||||
if (!path.endsWith("/")) {
|
||||
String name = new File(path).getName();
|
||||
List<String> paths = result.get(name);
|
||||
if (paths == null) {
|
||||
paths = new LinkedList<String>();
|
||||
result.put(name, paths);
|
||||
}
|
||||
paths.add(path);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Map<Long,String> inverse(Map<String, Long> map) {
|
||||
Map<Long, String> inv = new LinkedHashMap<Long, String>();
|
||||
for (Map.Entry<String, Long> entry : map.entrySet()) {
|
||||
inv.put(entry.getValue(), entry.getKey());
|
||||
}
|
||||
return inv;
|
||||
}
|
||||
|
||||
private static Map<String, Long> withAllRemoved(Map<String, Long> from, Map<String, Long> toRemove) {
|
||||
Map<String, Long> result = new HashMap<String, Long>(from);
|
||||
Map<String, Long> result = new LinkedHashMap<String, Long>(from);
|
||||
for (String each : toRemove.keySet()) {
|
||||
result.remove(each);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map<String, Long> collect(Map<String, Long> older, Map<String, Long> newer, boolean equal) {
|
||||
Map<String, Long> result = new HashMap<String, Long>();
|
||||
private static Map<String, Long> collect(Map<String, Long> older, Map<String, Long> newer, List<String> critical, boolean equal) {
|
||||
Map<String, Long> result = new LinkedHashMap<String, Long>();
|
||||
for (Map.Entry<String, Long> each : newer.entrySet()) {
|
||||
String file = each.getKey();
|
||||
Long oldChecksum = older.get(file);
|
||||
Long newChecksum = newer.get(file);
|
||||
if (oldChecksum != null && newChecksum != null && oldChecksum.equals(newChecksum) == equal) {
|
||||
result.put(file, oldChecksum);
|
||||
if (oldChecksum != null && newChecksum != null) {
|
||||
if ((oldChecksum.equals(newChecksum) && !critical.contains(file)) == equal) {
|
||||
result.put(file, oldChecksum);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class Update {
|
||||
public final String source;
|
||||
public final long checksum;
|
||||
public final boolean move;
|
||||
|
||||
public Update(String source, long checksum, boolean move) {
|
||||
this.checksum = checksum;
|
||||
this.source = source;
|
||||
this.move = move;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
public Map<String, Long> filesToDelete;
|
||||
public Set<String> filesToCreate;
|
||||
public Map<String, Long> filesToUpdate;
|
||||
public Map<String, Long> filesToCreate;
|
||||
public Map<String, Update> filesToUpdate;
|
||||
public Map<String, Long> commonFiles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,46 +4,19 @@ import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipException;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public class Digester {
|
||||
public static Map<String, Long> digestFiles(File dir, List<String> ignoredFiles, UpdaterUI ui)
|
||||
throws IOException, OperationCancelledException {
|
||||
Map<String, Long> result = new HashMap<String, Long>();
|
||||
// CRC32 will only use the lower 32bits of long, never returning negative values.
|
||||
public static long INVALID = -1;
|
||||
public static long DIRECTORY = -2;
|
||||
|
||||
LinkedHashSet<String> paths = Utils.collectRelativePaths(dir);
|
||||
for (String each : paths) {
|
||||
if (ignoredFiles.contains(each)) continue;
|
||||
ui.setStatus(each);
|
||||
ui.checkCancelled();
|
||||
result.put(each, digestFile(new File(dir, each)));
|
||||
public static long digestRegularFile(File file, boolean normalize) throws IOException {
|
||||
if (file.isDirectory()) {
|
||||
return DIRECTORY;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static long digestFile(File file) throws IOException {
|
||||
if (!Runner.ZIP_AS_BINARY && Utils.isZipFile(file.getName())) {
|
||||
ZipFile zipFile;
|
||||
try {
|
||||
zipFile = new ZipFile(file);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Runner.printStackTrace(e);
|
||||
return digestRegularFile(file);
|
||||
}
|
||||
|
||||
try {
|
||||
return doDigestZipFile(zipFile);
|
||||
}
|
||||
finally {
|
||||
zipFile.close();
|
||||
}
|
||||
}
|
||||
return digestRegularFile(file);
|
||||
}
|
||||
|
||||
public static long digestRegularFile(File file) throws IOException {
|
||||
InputStream in = new BufferedInputStream(new FileInputStream(file));
|
||||
InputStream in = new BufferedInputStream(Utils.newFileInputStream(file, normalize));
|
||||
try {
|
||||
return digestStream(in);
|
||||
}
|
||||
@@ -52,32 +25,43 @@ public class Digester {
|
||||
}
|
||||
}
|
||||
|
||||
private static long doDigestZipFile(ZipFile zipFile) throws IOException {
|
||||
List<ZipEntry> sorted = new ArrayList<ZipEntry>();
|
||||
|
||||
Enumeration<? extends ZipEntry> temp = zipFile.entries();
|
||||
while (temp.hasMoreElements()) {
|
||||
ZipEntry each = temp.nextElement();
|
||||
if (!each.isDirectory()) sorted.add(each);
|
||||
public static long digestZipFile(File file) throws IOException {
|
||||
ZipFile zipFile;
|
||||
try {
|
||||
zipFile = new ZipFile(file);
|
||||
} catch (ZipException e) {
|
||||
// This was not a zip file...
|
||||
return digestRegularFile(file, false);
|
||||
}
|
||||
try {
|
||||
List<ZipEntry> sorted = new ArrayList<ZipEntry>();
|
||||
|
||||
Collections.sort(sorted, new Comparator<ZipEntry>() {
|
||||
public int compare(ZipEntry o1, ZipEntry o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
Enumeration<? extends ZipEntry> temp = zipFile.entries();
|
||||
while (temp.hasMoreElements()) {
|
||||
ZipEntry each = temp.nextElement();
|
||||
if (!each.isDirectory()) sorted.add(each);
|
||||
}
|
||||
});
|
||||
|
||||
CRC32 crc = new CRC32();
|
||||
for (ZipEntry each : sorted) {
|
||||
InputStream in = zipFile.getInputStream(each);
|
||||
try {
|
||||
doDigestStream(in, crc);
|
||||
}
|
||||
finally {
|
||||
in.close();
|
||||
Collections.sort(sorted, new Comparator<ZipEntry>() {
|
||||
public int compare(ZipEntry o1, ZipEntry o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
|
||||
CRC32 crc = new CRC32();
|
||||
for (ZipEntry each : sorted) {
|
||||
InputStream in = zipFile.getInputStream(each);
|
||||
try {
|
||||
doDigestStream(in, crc);
|
||||
}
|
||||
finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
return crc.getValue();
|
||||
} finally {
|
||||
zipFile.close();
|
||||
}
|
||||
return crc.getValue();
|
||||
}
|
||||
|
||||
public static long digestStream(InputStream in) throws IOException {
|
||||
|
||||
@@ -6,64 +6,75 @@ import java.util.zip.ZipFile;
|
||||
|
||||
public class Patch {
|
||||
private List<PatchAction> myActions = new ArrayList<PatchAction>();
|
||||
private boolean myIsBinary;
|
||||
private boolean myIsStrict;
|
||||
private boolean myIsNormalized;
|
||||
private String myOldBuild;
|
||||
private String myNewBuild;
|
||||
private String myRoot;
|
||||
private Map<String, String> myWarnings;
|
||||
private List<String> myDeleteFiles;
|
||||
|
||||
private static final int CREATE_ACTION_KEY = 1;
|
||||
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;
|
||||
private static final int VALIDATE_ACTION_KEY = 5;
|
||||
|
||||
public Patch(File olderDir,
|
||||
File newerDir,
|
||||
List<String> ignoredFiles,
|
||||
List<String> criticalFiles,
|
||||
List<String> optionalFiles,
|
||||
UpdaterUI ui) throws IOException, OperationCancelledException {
|
||||
calculateActions(olderDir, newerDir, ignoredFiles, criticalFiles, optionalFiles, ui);
|
||||
public Patch(PatchSpec spec, UpdaterUI ui) throws IOException, OperationCancelledException {
|
||||
myIsBinary = spec.isBinary();
|
||||
myIsStrict = spec.isStrict();
|
||||
myIsNormalized = spec.isNormalized();
|
||||
myOldBuild = spec.getOldVersionDescription();
|
||||
myNewBuild = spec.getNewVersionDescription();
|
||||
myWarnings = spec.getWarnings();
|
||||
myDeleteFiles = spec.getDeleteFiles();
|
||||
myRoot = spec.getRoot();
|
||||
|
||||
calculateActions(spec, ui);
|
||||
}
|
||||
|
||||
public Patch(InputStream patchIn) throws IOException {
|
||||
read(patchIn);
|
||||
}
|
||||
|
||||
private void calculateActions(File olderDir,
|
||||
File newerDir,
|
||||
List<String> ignoredFiles,
|
||||
List<String> criticalFiles,
|
||||
List<String> optionalFiles,
|
||||
UpdaterUI ui)
|
||||
throws IOException, OperationCancelledException {
|
||||
DiffCalculator.Result diff;
|
||||
|
||||
private void calculateActions(PatchSpec spec, UpdaterUI ui) throws IOException, OperationCancelledException {
|
||||
Runner.logger.info("Calculating difference...");
|
||||
ui.startProcess("Calculating difference...");
|
||||
ui.checkCancelled();
|
||||
|
||||
diff = DiffCalculator.calculate(Digester.digestFiles(olderDir, ignoredFiles, ui),
|
||||
Digester.digestFiles(newerDir, ignoredFiles, ui));
|
||||
File olderDir = new File(spec.getOldFolder());
|
||||
File newerDir = new File(spec.getNewFolder());
|
||||
DiffCalculator.Result diff;
|
||||
diff = DiffCalculator.calculate(digestFiles(olderDir, spec.getIgnoredFiles(), isNormalized(), ui),
|
||||
digestFiles(newerDir, spec.getIgnoredFiles(), false, ui),
|
||||
spec.getCriticalFiles(), true);
|
||||
|
||||
List<PatchAction> tempActions = new ArrayList<PatchAction>();
|
||||
|
||||
// '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()) {
|
||||
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()));
|
||||
}
|
||||
// Add them in reverse order so directory structures start deleting the files before the directory itself.
|
||||
tempActions.add(0, new DeleteAction(this, each.getKey(), each.getValue()));
|
||||
}
|
||||
|
||||
for (String each : diff.filesToCreate) {
|
||||
tempActions.add(new CreateAction(each));
|
||||
for (String each : diff.filesToCreate.keySet()) {
|
||||
tempActions.add(new CreateAction(this, each));
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Long> each : diff.filesToUpdate.entrySet()) {
|
||||
if (!Runner.ZIP_AS_BINARY && Utils.isZipFile(each.getKey())) {
|
||||
tempActions.add(new UpdateZipAction(each.getKey(), each.getValue()));
|
||||
for (Map.Entry<String, DiffCalculator.Update> each : diff.filesToUpdate.entrySet()) {
|
||||
DiffCalculator.Update update = each.getValue();
|
||||
if (!spec.isBinary() && Utils.isZipFile(each.getKey())) {
|
||||
tempActions.add(new UpdateZipAction(this, each.getKey(), update.source, update.checksum, update.move));
|
||||
}
|
||||
else {
|
||||
tempActions.add(new UpdateAction(each.getKey(), each.getValue()));
|
||||
tempActions.add(new UpdateAction(this, each.getKey(), update.source, update.checksum, update.move));
|
||||
}
|
||||
}
|
||||
|
||||
if (spec.isStrict()) {
|
||||
for (Map.Entry<String, Long> each : diff.commonFiles.entrySet()) {
|
||||
tempActions.add(new ValidateAction(this, each.getKey(), each.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,8 +88,8 @@ public class Patch {
|
||||
|
||||
if (!each.calculate(olderDir, newerDir)) continue;
|
||||
myActions.add(each);
|
||||
each.setCritical(criticalFiles.contains(each.getPath()));
|
||||
each.setOptional(optionalFiles.contains(each.getPath()));
|
||||
each.setCritical(spec.getCriticalFiles().contains(each.getPath()));
|
||||
each.setOptional(spec.getOptionalFiles().contains(each.getPath()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,104 +100,189 @@ public class Patch {
|
||||
public void write(OutputStream out) throws IOException {
|
||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed") DataOutputStream dataOut = new DataOutputStream(out);
|
||||
try {
|
||||
dataOut.writeInt(myActions.size());
|
||||
|
||||
for (PatchAction each : myActions) {
|
||||
int key;
|
||||
Class clazz = each.getClass();
|
||||
|
||||
if (clazz == CreateAction.class) {
|
||||
key = CREATE_ACTION_KEY;
|
||||
}
|
||||
else if (clazz == UpdateAction.class) {
|
||||
key = UPDATE_ACTION_KEY;
|
||||
}
|
||||
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;
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException("Unknown action " + each);
|
||||
}
|
||||
dataOut.writeInt(key);
|
||||
each.write(dataOut);
|
||||
}
|
||||
dataOut.writeUTF(myOldBuild);
|
||||
dataOut.writeUTF(myNewBuild);
|
||||
dataOut.writeUTF(myRoot);
|
||||
dataOut.writeBoolean(myIsBinary);
|
||||
dataOut.writeBoolean(myIsStrict);
|
||||
dataOut.writeBoolean(myIsNormalized);
|
||||
writeMap(dataOut, myWarnings);
|
||||
writeList(dataOut, myDeleteFiles);
|
||||
writeActions(dataOut, myActions);
|
||||
}
|
||||
finally {
|
||||
dataOut.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeList(DataOutputStream dataOut, List<String> list) throws IOException {
|
||||
dataOut.writeInt(list.size());
|
||||
for (String string : list) {
|
||||
dataOut.writeUTF(string);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeMap(DataOutputStream dataOut, Map<String, String> map) throws IOException {
|
||||
dataOut.writeInt(map.size());
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
dataOut.writeUTF(entry.getKey());
|
||||
dataOut.writeUTF(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeActions(DataOutputStream dataOut, List<PatchAction> actions) throws IOException {
|
||||
dataOut.writeInt(actions.size());
|
||||
|
||||
for (PatchAction each : actions) {
|
||||
int key;
|
||||
Class clazz = each.getClass();
|
||||
|
||||
if (clazz == CreateAction.class) {
|
||||
key = CREATE_ACTION_KEY;
|
||||
}
|
||||
else if (clazz == UpdateAction.class) {
|
||||
key = UPDATE_ACTION_KEY;
|
||||
}
|
||||
else if (clazz == UpdateZipAction.class) {
|
||||
key = UPDATE_ZIP_ACTION_KEY;
|
||||
}
|
||||
else if (clazz == DeleteAction.class) {
|
||||
key = DELETE_ACTION_KEY;
|
||||
}
|
||||
else if (clazz == ValidateAction.class) {
|
||||
key = VALIDATE_ACTION_KEY;
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException("Unknown action " + each);
|
||||
}
|
||||
dataOut.writeInt(key);
|
||||
each.write(dataOut);
|
||||
}
|
||||
}
|
||||
|
||||
private void read(InputStream patchIn) throws IOException {
|
||||
List<PatchAction> newActions = new ArrayList<PatchAction>();
|
||||
|
||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed") DataInputStream in = new DataInputStream(patchIn);
|
||||
int size = in.readInt();
|
||||
|
||||
myOldBuild = in.readUTF();
|
||||
myNewBuild = in.readUTF();
|
||||
myRoot = in.readUTF();
|
||||
myIsBinary = in.readBoolean();
|
||||
myIsStrict = in.readBoolean();
|
||||
myIsNormalized = in.readBoolean();
|
||||
myWarnings = readMap(in);
|
||||
myDeleteFiles = readList(in);
|
||||
myActions = readActions(in);
|
||||
}
|
||||
|
||||
private static List<String> readList(DataInputStream in) throws IOException {
|
||||
int size = in.readInt();
|
||||
List<String> list = new ArrayList<String>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
list.add(in.readUTF());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static Map<String, String> readMap(DataInputStream in) throws IOException {
|
||||
int size = in.readInt();
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
String key = in.readUTF();
|
||||
map.put(key, in.readUTF());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private List<PatchAction> readActions(DataInputStream in) throws IOException {
|
||||
List<PatchAction> actions = new ArrayList<PatchAction>();
|
||||
int size = in.readInt();
|
||||
while (size-- > 0) {
|
||||
int key = in.readInt();
|
||||
PatchAction a;
|
||||
switch (key) {
|
||||
case CREATE_ACTION_KEY:
|
||||
a = new CreateAction(in);
|
||||
a = new CreateAction(this, in);
|
||||
break;
|
||||
case UPDATE_ACTION_KEY:
|
||||
a = new UpdateAction(in);
|
||||
a = new UpdateAction(this, in);
|
||||
break;
|
||||
case UPDATE_ZIP_ACTION_KEY:
|
||||
a = new UpdateZipAction(in);
|
||||
a = new UpdateZipAction(this, in);
|
||||
break;
|
||||
case DELETE_ACTION_KEY:
|
||||
a = new DeleteAction(in);
|
||||
a = new DeleteAction(this, in);
|
||||
break;
|
||||
case DELETE_ZIP_ACTION_KEY:
|
||||
a = new DeleteZipAction(in);
|
||||
case VALIDATE_ACTION_KEY:
|
||||
a = new ValidateAction(this, in);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unknown action type " + key);
|
||||
}
|
||||
newActions.add(a);
|
||||
actions.add(a);
|
||||
}
|
||||
|
||||
myActions = newActions;
|
||||
return actions;
|
||||
}
|
||||
|
||||
public List<ValidationResult> validate(final File toDir, UpdaterUI ui) throws IOException, OperationCancelledException {
|
||||
final LinkedHashSet<String> files = Utils.collectRelativePaths(toDir);
|
||||
private File toBaseDir(File toDir) throws IOException {
|
||||
// This removes myRoot from the end of toDir. myRoot is expressed with '/' so converting to URI to normalize separators.
|
||||
String path = toDir.toURI().getPath();
|
||||
if (!path.endsWith(myRoot)) {
|
||||
throw new IOException("The patch must be applied to the root folder " + myRoot);
|
||||
}
|
||||
return new File(path.substring(0, path.length() - myRoot.length()));
|
||||
}
|
||||
|
||||
public List<ValidationResult> validate(final File rootDir, UpdaterUI ui) throws IOException, OperationCancelledException {
|
||||
LinkedHashSet<String> files = null;
|
||||
final File toDir = toBaseDir(rootDir);
|
||||
boolean checkWarnings = true;
|
||||
while (checkWarnings) {
|
||||
files = Utils.collectRelativePaths(toDir, myIsStrict);
|
||||
checkWarnings = false;
|
||||
for (String file : files) {
|
||||
String warning = myWarnings.get(file);
|
||||
if (warning != null) {
|
||||
if (!ui.showWarning(warning)) {
|
||||
throw new OperationCancelledException();
|
||||
}
|
||||
checkWarnings = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<ValidationResult> result = new ArrayList<ValidationResult>();
|
||||
|
||||
if (myIsStrict) {
|
||||
// In strict mode add delete actions for unknown files.
|
||||
for (PatchAction action : myActions) {
|
||||
files.remove(action.getPath());
|
||||
}
|
||||
for (String file : files) {
|
||||
myActions.add(0, new DeleteAction(this, file, Digester.INVALID));
|
||||
}
|
||||
}
|
||||
Runner.logger.info("Validating installation...");
|
||||
forEach(myActions, "Validating installation...", ui, true,
|
||||
new ActionsProcessor() {
|
||||
@Override
|
||||
public void forEach(PatchAction each) throws IOException {
|
||||
ValidationResult validationResult = each.validate(toDir);
|
||||
if (validationResult != null) result.add(validationResult);
|
||||
files.remove(each.getPath());
|
||||
}
|
||||
});
|
||||
|
||||
//for (String each : files) {
|
||||
// result.add(new ValidationResult(ValidationResult.Kind.INFO,
|
||||
// each,
|
||||
// ValidationResult.Action.NO_ACTION,
|
||||
// ValidationResult.MANUALLY_ADDED_MESSAGE,
|
||||
// ValidationResult.Option.KEEP, ValidationResult.Option.DELETE));
|
||||
//}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public ApplicationResult apply(final ZipFile patchFile,
|
||||
final File toDir,
|
||||
final File rootDir,
|
||||
final File backupDir,
|
||||
final Map<String, ValidationResult.Option> options,
|
||||
UpdaterUI ui) throws IOException, OperationCancelledException {
|
||||
|
||||
final File toDir = toBaseDir(rootDir);
|
||||
List<PatchAction> actionsToProcess = new ArrayList<PatchAction>();
|
||||
for (PatchAction each : myActions) {
|
||||
if (each.shouldApply(toDir, options)) actionsToProcess.add(each);
|
||||
@@ -194,6 +290,7 @@ public class Patch {
|
||||
|
||||
forEach(actionsToProcess, "Backing up files...", ui, true,
|
||||
new ActionsProcessor() {
|
||||
@Override
|
||||
public void forEach(PatchAction each) throws IOException {
|
||||
each.backup(toDir, backupDir);
|
||||
}
|
||||
@@ -205,9 +302,10 @@ public class Patch {
|
||||
try {
|
||||
forEach(actionsToProcess, "Applying patch...", ui, true,
|
||||
new ActionsProcessor() {
|
||||
@Override
|
||||
public void forEach(PatchAction each) throws IOException {
|
||||
appliedActions.add(each);
|
||||
each.apply(patchFile, toDir);
|
||||
each.apply(patchFile, backupDir, toDir);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -223,7 +321,7 @@ public class Patch {
|
||||
}
|
||||
|
||||
if (shouldRevert) {
|
||||
revert(appliedActions, backupDir, toDir, ui);
|
||||
revert(appliedActions, backupDir, rootDir, ui);
|
||||
appliedActions.clear();
|
||||
|
||||
if (cancelled) throw new OperationCancelledException();
|
||||
@@ -235,11 +333,13 @@ public class Patch {
|
||||
return new ApplicationResult(appliedActions);
|
||||
}
|
||||
|
||||
public void revert(List<PatchAction> actions, final File backupDir, final File toDir, UpdaterUI ui)
|
||||
public void revert(List<PatchAction> actions, final File backupDir, final File rootDir, UpdaterUI ui)
|
||||
throws OperationCancelledException, IOException {
|
||||
Collections.reverse(actions);
|
||||
final File toDir = toBaseDir(rootDir);
|
||||
forEach(actions, "Reverting...", ui, false,
|
||||
new ActionsProcessor() {
|
||||
@Override
|
||||
public void forEach(PatchAction each) throws IOException {
|
||||
each.revert(toDir, backupDir);
|
||||
}
|
||||
@@ -263,6 +363,54 @@ public class Patch {
|
||||
}
|
||||
}
|
||||
|
||||
public long digestFile(File toFile, boolean normalize) throws IOException {
|
||||
if (!myIsBinary && Utils.isZipFile(toFile.getName())) {
|
||||
return Digester.digestZipFile(toFile);
|
||||
}
|
||||
else {
|
||||
return Digester.digestRegularFile(toFile, normalize);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Long> digestFiles(File dir, List<String> ignoredFiles, boolean normalize, UpdaterUI ui)
|
||||
throws IOException, OperationCancelledException {
|
||||
Map<String, Long> result = new LinkedHashMap<String, Long>();
|
||||
|
||||
LinkedHashSet<String> paths = Utils.collectRelativePaths(dir, myIsStrict);
|
||||
for (String each : paths) {
|
||||
if (ignoredFiles.contains(each)) continue;
|
||||
ui.setStatus(each);
|
||||
ui.checkCancelled();
|
||||
result.put(each, digestFile(new File(dir, each), normalize));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getOldBuild() {
|
||||
return myOldBuild;
|
||||
}
|
||||
|
||||
public String getNewBuild() {
|
||||
return myNewBuild;
|
||||
}
|
||||
|
||||
public boolean isStrict() {
|
||||
return myIsStrict;
|
||||
}
|
||||
|
||||
public boolean isNormalized() {
|
||||
return myIsNormalized;
|
||||
}
|
||||
|
||||
public boolean validateDeletion(String path) {
|
||||
for (String delete : myDeleteFiles) {
|
||||
if (path.matches(delete)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public interface ActionsProcessor {
|
||||
void forEach(PatchAction each) throws IOException;
|
||||
}
|
||||
|
||||
@@ -15,13 +15,16 @@ public abstract class PatchAction {
|
||||
protected long myChecksum;
|
||||
private boolean isCritical;
|
||||
private boolean isOptional;
|
||||
protected transient Patch myPatch;
|
||||
|
||||
public PatchAction(String path, long checksum) {
|
||||
myPath = path;
|
||||
public PatchAction(Patch patch, String path, long checksum) {
|
||||
myPatch = patch;
|
||||
myChecksum = checksum;
|
||||
myPath = path;
|
||||
}
|
||||
|
||||
public PatchAction(DataInputStream in) throws IOException {
|
||||
public PatchAction(Patch patch, DataInputStream in) throws IOException {
|
||||
myPatch = patch;
|
||||
myPath = in.readUTF();
|
||||
myChecksum = in.readLong();
|
||||
isCritical = in.readBoolean();
|
||||
@@ -70,18 +73,14 @@ public abstract class PatchAction {
|
||||
process.terminate();
|
||||
}
|
||||
}
|
||||
return shouldApplyOn(file);
|
||||
return doShouldApply(toDir);
|
||||
}
|
||||
|
||||
protected boolean shouldApplyOn(File toFile) {
|
||||
protected boolean doShouldApply(File toDir) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public ValidationResult validate(File toDir) throws IOException {
|
||||
return doValidate(getFile(toDir));
|
||||
}
|
||||
|
||||
protected abstract ValidationResult doValidate(final File toFile) throws IOException;
|
||||
protected abstract ValidationResult validate(File toDir) throws IOException;
|
||||
|
||||
protected ValidationResult doValidateAccess(File toFile, ValidationResult.Action action) {
|
||||
if (!toFile.exists()) return null;
|
||||
@@ -95,7 +94,7 @@ public abstract class PatchAction {
|
||||
myPath,
|
||||
action,
|
||||
ValidationResult.ACCESS_DENIED_MESSAGE,
|
||||
ValidationResult.Option.IGNORE);
|
||||
myPatch.isStrict() ? ValidationResult.Option.NONE : ValidationResult.Option.IGNORE);
|
||||
}
|
||||
|
||||
private boolean isWritable(File toFile) {
|
||||
@@ -144,11 +143,27 @@ public abstract class PatchAction {
|
||||
throws IOException {
|
||||
if (toFile.exists()) {
|
||||
if (isModified(toFile)) {
|
||||
ValidationResult.Option[] options;
|
||||
if (myPatch.isStrict()) {
|
||||
if (isCritical) {
|
||||
options = new ValidationResult.Option[]{ ValidationResult.Option.REPLACE };
|
||||
}
|
||||
else {
|
||||
options = new ValidationResult.Option[]{ ValidationResult.Option.NONE };
|
||||
}
|
||||
} else {
|
||||
if (isCritical) {
|
||||
options = new ValidationResult.Option[]{ ValidationResult.Option.REPLACE, ValidationResult.Option.IGNORE };
|
||||
}
|
||||
else {
|
||||
options = new ValidationResult.Option[]{ ValidationResult.Option.IGNORE };
|
||||
}
|
||||
}
|
||||
return new ValidationResult(kind,
|
||||
myPath,
|
||||
action,
|
||||
ValidationResult.MODIFIED_MESSAGE,
|
||||
ValidationResult.Option.IGNORE);
|
||||
options);
|
||||
}
|
||||
}
|
||||
else if (!isOptional) {
|
||||
@@ -156,18 +171,20 @@ public abstract class PatchAction {
|
||||
myPath,
|
||||
action,
|
||||
ValidationResult.ABSENT_MESSAGE,
|
||||
ValidationResult.Option.IGNORE);
|
||||
myPatch.isStrict() ? ValidationResult.Option.NONE : ValidationResult.Option.IGNORE);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
abstract protected boolean isModified(File toFile) throws IOException;
|
||||
|
||||
public void apply(ZipFile patchFile, File toDir) throws IOException {
|
||||
doApply(patchFile, getFile(toDir));
|
||||
protected boolean isModified(File toFile) throws IOException {
|
||||
return myChecksum == Digester.INVALID || myChecksum != myPatch.digestFile(toFile, myPatch.isNormalized());
|
||||
}
|
||||
|
||||
protected abstract void doApply(ZipFile patchFile, File toFile) throws IOException;
|
||||
public void apply(ZipFile patchFile, File backupDir, File toDir) throws IOException {
|
||||
doApply(patchFile, backupDir, getFile(toDir));
|
||||
}
|
||||
|
||||
protected abstract void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException;
|
||||
|
||||
public void backup(File toDir, File backupDir) throws IOException {
|
||||
doBackup(getFile(toDir), getFile(backupDir));
|
||||
@@ -181,7 +198,7 @@ public abstract class PatchAction {
|
||||
|
||||
protected abstract void doRevert(File toFile, File backupFile) throws IOException;
|
||||
|
||||
private File getFile(File baseDir) {
|
||||
protected File getFile(File baseDir) {
|
||||
return new File(baseDir, myPath);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package com.intellij.updater;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
@@ -13,15 +10,9 @@ import java.util.zip.ZipOutputStream;
|
||||
public class PatchFileCreator {
|
||||
private static final String PATCH_INFO_FILE_NAME = ".patch-info";
|
||||
|
||||
public static void create(File olderDir,
|
||||
File newerDir,
|
||||
File patchFile,
|
||||
List<String> ignoredFiles,
|
||||
List<String> criticalFiles,
|
||||
List<String> optionalFiles,
|
||||
UpdaterUI ui) throws IOException, OperationCancelledException {
|
||||
public static Patch create(PatchSpec spec, File patchFile, UpdaterUI ui) throws IOException, OperationCancelledException {
|
||||
|
||||
Patch patchInfo = new Patch(olderDir, newerDir, ignoredFiles, criticalFiles, optionalFiles, ui);
|
||||
Patch patchInfo = new Patch(spec, ui);
|
||||
Runner.logger.info("Creating the patch file '" + patchFile + "'...");
|
||||
ui.startProcess("Creating the patch file '" + patchFile + "'...");
|
||||
ui.checkCancelled();
|
||||
@@ -34,6 +25,8 @@ public class PatchFileCreator {
|
||||
patchInfo.write(out);
|
||||
out.closeEntry();
|
||||
|
||||
File olderDir = new File(spec.getOldFolder());
|
||||
File newerDir = new File(spec.getNewFolder());
|
||||
List<PatchAction> actions = patchInfo.getActions();
|
||||
for (PatchAction each : actions) {
|
||||
|
||||
@@ -46,6 +39,8 @@ public class PatchFileCreator {
|
||||
finally {
|
||||
out.close();
|
||||
}
|
||||
|
||||
return patchInfo;
|
||||
}
|
||||
|
||||
public static PreparationResult prepareAndValidate(File patchFile,
|
||||
@@ -55,6 +50,7 @@ public class PatchFileCreator {
|
||||
|
||||
ZipFile zipFile = new ZipFile(patchFile);
|
||||
try {
|
||||
|
||||
InputStream in = Utils.getEntryInputStream(zipFile, PATCH_INFO_FILE_NAME);
|
||||
try {
|
||||
patch = new Patch(in);
|
||||
@@ -67,6 +63,8 @@ public class PatchFileCreator {
|
||||
zipFile.close();
|
||||
}
|
||||
|
||||
ui.setDescription(patch.getOldBuild(), patch.getNewBuild());
|
||||
|
||||
List<ValidationResult> validationResults = patch.validate(toDir, ui);
|
||||
return new PreparationResult(patch, patchFile, toDir, validationResults);
|
||||
}
|
||||
|
||||
173
updater/src/com/intellij/updater/PatchSpec.java
Normal file
173
updater/src/com/intellij/updater/PatchSpec.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PatchSpec {
|
||||
private String myOldVersionDescription = "";
|
||||
private String myNewVersionDescription = "";
|
||||
private String myOldFolder;
|
||||
private String myNewFolder;
|
||||
private String myPatchFile;
|
||||
private String myJarFile;
|
||||
private boolean myIsBinary;
|
||||
private boolean myIsStrict;
|
||||
private List<String> myIgnoredFiles = Collections.emptyList();
|
||||
private List<String> myCriticalFiles = Collections.emptyList();
|
||||
private List<String> myOptionalFiles = Collections.emptyList();
|
||||
private boolean myIsNormalized;
|
||||
private Map<String, String> myWarnings = Collections.emptyMap();
|
||||
private List<String> myDeleteFiles = Collections.emptyList();
|
||||
private String myRoot = "";
|
||||
|
||||
public String getOldVersionDescription() {
|
||||
return myOldVersionDescription;
|
||||
}
|
||||
|
||||
public PatchSpec setOldVersionDescription(String oldVersionDescription) {
|
||||
myOldVersionDescription = oldVersionDescription;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getNewVersionDescription() {
|
||||
return myNewVersionDescription;
|
||||
}
|
||||
|
||||
public PatchSpec setNewVersionDescription(String newVersionDescription) {
|
||||
myNewVersionDescription = newVersionDescription;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getOldFolder() {
|
||||
return myOldFolder;
|
||||
}
|
||||
|
||||
public PatchSpec setOldFolder(String oldFolder) {
|
||||
myOldFolder = oldFolder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getNewFolder() {
|
||||
return myNewFolder;
|
||||
}
|
||||
|
||||
public PatchSpec setNewFolder(String newFolder) {
|
||||
myNewFolder = newFolder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getPatchFile() {
|
||||
return myPatchFile;
|
||||
}
|
||||
|
||||
public PatchSpec setPatchFile(String patchFile) {
|
||||
myPatchFile = patchFile;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getJarFile() {
|
||||
return myJarFile;
|
||||
}
|
||||
|
||||
public PatchSpec setJarFile(String jarFile) {
|
||||
myJarFile = jarFile;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isStrict() {
|
||||
return myIsStrict;
|
||||
}
|
||||
|
||||
public PatchSpec setStrict(boolean strict) {
|
||||
myIsStrict = strict;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getIgnoredFiles() {
|
||||
return myIgnoredFiles;
|
||||
}
|
||||
|
||||
public PatchSpec setIgnoredFiles(List<String> ignoredFiles) {
|
||||
myIgnoredFiles = ignoredFiles;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getCriticalFiles() {
|
||||
return myCriticalFiles;
|
||||
}
|
||||
|
||||
public PatchSpec setCriticalFiles(List<String> criticalFiles) {
|
||||
myCriticalFiles = criticalFiles;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getOptionalFiles() {
|
||||
return myOptionalFiles;
|
||||
}
|
||||
|
||||
public PatchSpec setOptionalFiles(List<String> optionalFiles) {
|
||||
myOptionalFiles = optionalFiles;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PatchSpec setBinary(boolean binary) {
|
||||
myIsBinary = binary;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isBinary() {
|
||||
return myIsBinary;
|
||||
}
|
||||
|
||||
public boolean isNormalized() {
|
||||
return myIsNormalized;
|
||||
}
|
||||
|
||||
public PatchSpec setNormalized(boolean normalized) {
|
||||
myIsNormalized = normalized;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PatchSpec setWarnings(Map<String, String> warnings) {
|
||||
myWarnings = warnings;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getWarnings() {
|
||||
return myWarnings;
|
||||
}
|
||||
|
||||
public PatchSpec setDeleteFiles(List<String> deleteFiles) {
|
||||
myDeleteFiles = deleteFiles;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getDeleteFiles() {
|
||||
return myDeleteFiles;
|
||||
}
|
||||
|
||||
public PatchSpec setRoot(String root) {
|
||||
myRoot = root;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getRoot() {
|
||||
return myRoot;
|
||||
}
|
||||
}
|
||||
39
updater/src/com/intellij/updater/RetryException.java
Normal file
39
updater/src/com/intellij/updater/RetryException.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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,19 +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";
|
||||
private static final String NEW_BUILD_DESCRIPTION = "new.build.description";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
String jarFile = getArgument(args, "jar");
|
||||
jarFile = jarFile == null ? resolveJarFile() : jarFile;
|
||||
|
||||
if (args.length >= 6 && "create".equals(args[0])) {
|
||||
String oldVersionDesc = args[1];
|
||||
String newVersionDesc = args[2];
|
||||
@@ -39,25 +33,64 @@ public class Runner {
|
||||
String patchFile = args[5];
|
||||
initLogger();
|
||||
|
||||
ZIP_AS_BINARY = Arrays.asList(args).contains("--zip_as_binary");
|
||||
// See usage for an explanation of these flags
|
||||
boolean binary = Arrays.asList(args).contains("--zip_as_binary");
|
||||
boolean strict = Arrays.asList(args).contains("--strict");
|
||||
boolean normalized = Arrays.asList(args).contains("--normalized");
|
||||
|
||||
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);
|
||||
String root = getArgument(args, "root");
|
||||
root = root == null ? "" : (root.endsWith("/") ? root : root + "/");
|
||||
|
||||
List<String> ignoredFiles = extractArguments(args, "ignored");
|
||||
List<String> criticalFiles = extractArguments(args, "critical");
|
||||
List<String> optionalFiles = extractArguments(args, "optional");
|
||||
List<String> deleteFiles = extractArguments(args, "delete");
|
||||
Map<String, String> warnings = buildWarningMap(extractArguments(args, "warning"));
|
||||
|
||||
PatchSpec spec = new PatchSpec()
|
||||
.setOldVersionDescription(oldVersionDesc)
|
||||
.setNewVersionDescription(newVersionDesc)
|
||||
.setRoot(root)
|
||||
.setOldFolder(oldFolder)
|
||||
.setNewFolder(newFolder)
|
||||
.setPatchFile(patchFile)
|
||||
.setJarFile(jarFile)
|
||||
.setStrict(strict)
|
||||
.setBinary(binary)
|
||||
.setNormalized(normalized)
|
||||
.setIgnoredFiles(ignoredFiles)
|
||||
.setCriticalFiles(criticalFiles)
|
||||
.setOptionalFiles(optionalFiles)
|
||||
.setDeleteFiles(deleteFiles)
|
||||
.setWarnings(warnings);
|
||||
|
||||
create(spec);
|
||||
}
|
||||
else if (args.length >= 2 && "install".equals(args[0])) {
|
||||
String destFolder = args[1];
|
||||
initLogger();
|
||||
logger.info("destFolder: " + destFolder);
|
||||
|
||||
install(destFolder);
|
||||
install(jarFile, destFolder);
|
||||
}
|
||||
else {
|
||||
printUsage();
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, String> buildWarningMap(List<String> warnings) {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
for (String warning : warnings) {
|
||||
int ix = warning.indexOf(":");
|
||||
if (ix != -1) {
|
||||
String path = warning.substring(0, ix);
|
||||
String message = warning.substring(ix + 1).replace("\\n","\n");
|
||||
map.put(path, message);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// checks that log directory 1)exists 2)has write perm. and 3)has 1MB+ free space
|
||||
private static boolean isValidLogDir(String logFolder) {
|
||||
File fileLogDir = new File(logFolder);
|
||||
@@ -106,7 +139,17 @@ public class Runner {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
public static List<String> extractFiles(String[] args, String paramName) {
|
||||
public static String getArgument(String[] args, String name) {
|
||||
String flag = "--" + name + "=";
|
||||
for (String param : args) {
|
||||
if (param.startsWith(flag)) {
|
||||
return param.substring(flag.length());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<String> extractArguments(String[] args, String paramName) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (String param : args) {
|
||||
if (param.startsWith(paramName + "=")) {
|
||||
@@ -122,38 +165,51 @@ public class Runner {
|
||||
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||
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> [ignored=file1;file2;...] [critical=file1;file2;...] [optional=file1;file2;...]\n" +
|
||||
"install <destination_folder>\n");
|
||||
System.err.println(
|
||||
"Usage:\n" +
|
||||
" Runner create <old_version> <new_version> <old_folder> <new_folder> <patch_file> [<file_set>=file1;file2;...] [<flags>]\n" +
|
||||
" Runner install <folder>\n" +
|
||||
"\n" +
|
||||
"Where:\n" +
|
||||
" <old_version>: A description of the version to generate the patch from.\n" +
|
||||
" <new_version>: A description of the version to generate the patch to.\n" +
|
||||
" <old_folder>: The folder where to find the old version.\n" +
|
||||
" <new_folder>: The folder where to find the new version.\n" +
|
||||
" <patch_file>: The .jar patch file to create which contains the patch and the patcher.\n" +
|
||||
" <file_set>: Can be one of:\n" +
|
||||
" ignored: The set of files that will not be included in the patch.\n" +
|
||||
" critical: Fully included in the patch, so they can be replaced at destination even if they have changed.\n" +
|
||||
" optional: A set of files that is ok for them no to exist when applying the patch.\n" +
|
||||
" delete: A set of regular expressions for paths that is safe to delete without user confirmation.\n" +
|
||||
" <flags>: Can be:\n" +
|
||||
" --zip_as_binary: Zip and jar files will be treated as binary files and not inspected internally.\n" +
|
||||
" --strict: The created patch will contain extra information to fully validate an installation. A strict\n" +
|
||||
" patch will only be applied if it is guaranteed that the patched version will match exactly\n" +
|
||||
" the source of the patch. This means that unexpected files will be deleted and all existing files\n" +
|
||||
" will be validated\n" +
|
||||
" --root=<dir>: Sets dir as the root directory of the patch. The root directory is the directory where the patch should be" +
|
||||
" applied to. For example on Mac, you can diff the two .app folders and set Contents as the root." +
|
||||
" The root directory is relative to <old_folder> and uses forwards-slashes as separators." +
|
||||
" --normalized: This creates a normalized patch. This flag only makes sense in addition to --zip_as_binary\n" +
|
||||
" A normalized patch must be used to move from an installation that was patched\n" +
|
||||
" in a non-binary way to a fully binary patch. This will yield a larger patch, but the\n" +
|
||||
" generated patch can be applied on versions where non-binary patches have been applied to and it\n" +
|
||||
" guarantees that the patched version will match exactly the original one.\n");
|
||||
}
|
||||
|
||||
private static void create(String oldBuildDesc,
|
||||
String newBuildDesc,
|
||||
String oldFolder,
|
||||
String newFolder,
|
||||
String patchFile,
|
||||
List<String> ignoredFiles,
|
||||
List<String> criticalFiles,
|
||||
List<String> optionalFiles) throws IOException, OperationCancelledException {
|
||||
private static void create(PatchSpec spec) throws IOException, OperationCancelledException {
|
||||
UpdaterUI ui = new ConsoleUpdaterUI();
|
||||
try {
|
||||
File tempPatchFile = Utils.createTempFile();
|
||||
PatchFileCreator.create(new File(oldFolder),
|
||||
new File(newFolder),
|
||||
tempPatchFile,
|
||||
ignoredFiles,
|
||||
criticalFiles,
|
||||
optionalFiles,
|
||||
ui);
|
||||
PatchFileCreator.create(spec, tempPatchFile, ui);
|
||||
|
||||
logger.info("Packing JAR file: " + patchFile );
|
||||
ui.startProcess("Packing JAR file '" + patchFile + "'...");
|
||||
logger.info("Packing JAR file: " + spec.getPatchFile() );
|
||||
ui.startProcess("Packing JAR file '" + spec.getPatchFile() + "'...");
|
||||
|
||||
FileOutputStream fileOut = new FileOutputStream(patchFile);
|
||||
FileOutputStream fileOut = new FileOutputStream(spec.getPatchFile());
|
||||
try {
|
||||
ZipOutputWrapper out = new ZipOutputWrapper(fileOut);
|
||||
ZipInputStream in = new ZipInputStream(new FileInputStream(resolveJarFile()));
|
||||
ZipInputStream in = new ZipInputStream(new FileInputStream(new File(spec.getJarFile())));
|
||||
try {
|
||||
ZipEntry e;
|
||||
while ((e = in.getNextEntry()) != null) {
|
||||
@@ -164,18 +220,6 @@ public class Runner {
|
||||
in.close();
|
||||
}
|
||||
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
props.setProperty(OLD_BUILD_DESCRIPTION, oldBuildDesc);
|
||||
props.setProperty(NEW_BUILD_DESCRIPTION, newBuildDesc);
|
||||
props.store(byteOut, "");
|
||||
}
|
||||
finally {
|
||||
byteOut.close();
|
||||
}
|
||||
|
||||
out.zipBytes(PATCH_PROPERTIES_ENTRY, byteOut);
|
||||
out.zipFile(PATCH_FILE_NAME, tempPatchFile);
|
||||
out.finish();
|
||||
}
|
||||
@@ -195,18 +239,7 @@ public class Runner {
|
||||
Utils.cleanup();
|
||||
}
|
||||
|
||||
private static void install(final String destFolder) throws Exception {
|
||||
InputStream in = Runner.class.getResourceAsStream("/" + PATCH_PROPERTIES_ENTRY);
|
||||
Properties props = new Properties();
|
||||
if (in != null) {
|
||||
try {
|
||||
props.load(in);
|
||||
}
|
||||
finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static void install(final String jarFile, final String destFolder) throws Exception {
|
||||
// todo[r.sh] to delete in IDEA 14 (after a full circle of platform updates)
|
||||
if (System.getProperty("swing.defaultlaf") == null) {
|
||||
SwingUtilities.invokeAndWait(new Runnable() {
|
||||
@@ -222,27 +255,25 @@ public class Runner {
|
||||
});
|
||||
}
|
||||
|
||||
new SwingUpdaterUI(props.getProperty(OLD_BUILD_DESCRIPTION),
|
||||
props.getProperty(NEW_BUILD_DESCRIPTION),
|
||||
new SwingUpdaterUI.InstallOperation() {
|
||||
public boolean execute(UpdaterUI ui) throws OperationCancelledException {
|
||||
logger.info("installing patch to the " + destFolder);
|
||||
return doInstall(ui, destFolder);
|
||||
}
|
||||
});
|
||||
new SwingUpdaterUI(new SwingUpdaterUI.InstallOperation() {
|
||||
public boolean execute(UpdaterUI ui) throws OperationCancelledException {
|
||||
logger.info("installing patch to the " + destFolder);
|
||||
return doInstall(jarFile, ui, destFolder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean doInstall(UpdaterUI ui, String destFolder) throws OperationCancelledException {
|
||||
private static boolean doInstall(String jarFile, UpdaterUI ui, String destFolder) throws OperationCancelledException {
|
||||
try {
|
||||
try {
|
||||
File patchFile = Utils.createTempFile();
|
||||
ZipFile jarFile = new ZipFile(resolveJarFile());
|
||||
ZipFile zipFile = new ZipFile(jarFile);
|
||||
|
||||
logger.info("Extracting patch file...");
|
||||
ui.startProcess("Extracting patch file...");
|
||||
ui.setProgressIndeterminate();
|
||||
try {
|
||||
InputStream in = Utils.getEntryInputStream(jarFile, PATCH_FILE_NAME);
|
||||
InputStream in = Utils.getEntryInputStream(zipFile, PATCH_FILE_NAME);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(patchFile));
|
||||
try {
|
||||
Utils.copyStream(in, out);
|
||||
@@ -253,7 +284,7 @@ public class Runner {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
jarFile.close();
|
||||
zipFile.close();
|
||||
}
|
||||
|
||||
ui.checkCancelled();
|
||||
@@ -270,6 +301,7 @@ public class Runner {
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
System.gc();
|
||||
cleanup(ui);
|
||||
}
|
||||
catch (IOException e) {
|
||||
@@ -281,11 +313,7 @@ public class Runner {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static File resolveJarFile() throws IOException {
|
||||
String jar = System.getProperty("JAR_FILE");
|
||||
if (jar != null) {
|
||||
return new File(jar);
|
||||
}
|
||||
private static String resolveJarFile() throws IOException {
|
||||
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");
|
||||
@@ -299,7 +327,7 @@ public class Runner {
|
||||
String jarFileUrl = path.substring(start, end);
|
||||
|
||||
try {
|
||||
return new File(new URI(jarFileUrl));
|
||||
return new File(new URI(jarFileUrl)).getAbsolutePath();
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
printStackTrace(e);
|
||||
|
||||
@@ -49,7 +49,7 @@ public class SwingUpdaterUI implements UpdaterUI {
|
||||
private final JFrame myFrame;
|
||||
private boolean myApplied;
|
||||
|
||||
public SwingUpdaterUI(String oldBuildDesc, String newBuildDesc, InstallOperation operation) {
|
||||
public SwingUpdaterUI(InstallOperation operation) {
|
||||
myOperation = operation;
|
||||
|
||||
myProcessTitle = new JLabel(" ");
|
||||
@@ -106,8 +106,6 @@ public class SwingUpdaterUI implements UpdaterUI {
|
||||
buttonsPanel.add(Box.createHorizontalGlue());
|
||||
buttonsPanel.add(myCancelButton);
|
||||
|
||||
myProcessTitle.setText("<html>Updating " + oldBuildDesc + " to " + newBuildDesc + "...");
|
||||
|
||||
myFrame.add(processPanel, BorderLayout.CENTER);
|
||||
myFrame.add(buttonsPanel, BorderLayout.SOUTH);
|
||||
|
||||
@@ -127,6 +125,18 @@ public class SwingUpdaterUI implements UpdaterUI {
|
||||
startRequestDispatching();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String oldBuildDesc, String newBuildDesc) {
|
||||
myProcessTitle.setText("<html>Updating " + oldBuildDesc + " to " + newBuildDesc + "...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showWarning(String message) {
|
||||
Object[] choices = new Object[] { "Retry", "Exit" };
|
||||
int choice = JOptionPane.showOptionDialog(null, message, "Warning", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, choices, choices[0]);
|
||||
return choice == 0;
|
||||
}
|
||||
|
||||
private void startRequestDispatching() {
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
@@ -214,6 +224,14 @@ public class SwingUpdaterUI implements UpdaterUI {
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(new Runnable() {
|
||||
public void run() {
|
||||
boolean proceed = true;
|
||||
for (ValidationResult result : validationResults) {
|
||||
if (result.options.contains(ValidationResult.Option.NONE)) {
|
||||
proceed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final JDialog dialog = new JDialog(myFrame, TITLE, true);
|
||||
dialog.setLayout(new BorderLayout());
|
||||
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
|
||||
@@ -222,12 +240,6 @@ public class SwingUpdaterUI implements UpdaterUI {
|
||||
buttonsPanel.setBorder(BUTTONS_BORDER);
|
||||
buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.X_AXIS));
|
||||
buttonsPanel.add(Box.createHorizontalGlue());
|
||||
JButton proceedButton = new JButton(PROCEED_BUTTON_TITLE);
|
||||
proceedButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
dialog.setVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
JButton cancelButton = new JButton(CANCEL_BUTTON_TITLE);
|
||||
cancelButton.addActionListener(new ActionListener() {
|
||||
@@ -237,11 +249,20 @@ public class SwingUpdaterUI implements UpdaterUI {
|
||||
dialog.setVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
buttonsPanel.add(proceedButton);
|
||||
buttonsPanel.add(cancelButton);
|
||||
|
||||
dialog.getRootPane().setDefaultButton(proceedButton);
|
||||
if (proceed) {
|
||||
JButton proceedButton = new JButton(PROCEED_BUTTON_TITLE);
|
||||
proceedButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
dialog.setVisible(false);
|
||||
}
|
||||
});
|
||||
buttonsPanel.add(proceedButton);
|
||||
dialog.getRootPane().setDefaultButton(proceedButton);
|
||||
} else {
|
||||
dialog.getRootPane().setDefaultButton(cancelButton);
|
||||
}
|
||||
|
||||
JTable table = new JTable();
|
||||
|
||||
@@ -256,10 +277,16 @@ public class SwingUpdaterUI implements UpdaterUI {
|
||||
each.setPreferredWidth(MyTableModel.getColumnWidth(i, new Dimension(600, 400).width));
|
||||
}
|
||||
|
||||
String message = "<html>There are some conflicts found in the installation.<br><br>" +
|
||||
"Please select desired solutions from the " + MyTableModel.COLUMNS[MyTableModel.OPTIONS_COLUMN_INDEX] +
|
||||
" column and press " + PROCEED_BUTTON_TITLE + ".<br>" +
|
||||
"If you do not want to proceed with the update, please press " + CANCEL_BUTTON_TITLE + ".</html>";
|
||||
String message = "<html>Some conflicts were found in the installation area.<br><br>";
|
||||
|
||||
if (proceed) {
|
||||
message += "Please select desired solutions from the " + MyTableModel.COLUMNS[MyTableModel.OPTIONS_COLUMN_INDEX] +
|
||||
" column and press " + PROCEED_BUTTON_TITLE + ".<br>" +
|
||||
"If you do not want to proceed with the update, please press " + CANCEL_BUTTON_TITLE + ".</html>";
|
||||
} else {
|
||||
message += "Some of the conflicts below do not have a solution, so the patch cannot be applied.<br>" +
|
||||
"Press " + CANCEL_BUTTON_TITLE + " to exit.</html>";
|
||||
}
|
||||
|
||||
JLabel label = new JLabel(message);
|
||||
label.setBorder(LABEL_BORDER);
|
||||
@@ -354,7 +381,7 @@ public class SwingUpdaterUI implements UpdaterUI {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new SwingUpdaterUI("xxx", "yyy", new InstallOperation() {
|
||||
new SwingUpdaterUI(new InstallOperation() {
|
||||
public boolean execute(UpdaterUI ui) throws OperationCancelledException {
|
||||
ui.startProcess("Process1");
|
||||
ui.checkCancelled();
|
||||
|
||||
@@ -6,48 +6,54 @@ import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class UpdateAction extends BaseUpdateAction {
|
||||
public UpdateAction(String path, long checksum) {
|
||||
super(path, checksum);
|
||||
public UpdateAction(Patch patch, String path, String source, long checksum, boolean move) {
|
||||
super(patch, path, source, checksum, move);
|
||||
}
|
||||
|
||||
public UpdateAction(DataInputStream in) throws IOException {
|
||||
super(in);
|
||||
public UpdateAction(Patch patch, String path, long checksum) {
|
||||
this(patch, path, path, checksum, false);
|
||||
}
|
||||
|
||||
public UpdateAction(Patch patch, DataInputStream in) throws IOException {
|
||||
super(patch, in);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBuildPatchFile(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException {
|
||||
patchOutput.putNextEntry(new ZipEntry(myPath));
|
||||
writeExecutableFlag(patchOutput, newerFile);
|
||||
writeDiff(olderFile, newerFile, patchOutput);
|
||||
patchOutput.closeEntry();
|
||||
if (!myIsMove) {
|
||||
patchOutput.putNextEntry(new ZipEntry(myPath));
|
||||
writeExecutableFlag(patchOutput, newerFile);
|
||||
writeDiff(olderFile, newerFile, patchOutput);
|
||||
patchOutput.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isModified(File toFile) throws IOException {
|
||||
return myChecksum != Digester.digestRegularFile(toFile);
|
||||
}
|
||||
protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException {
|
||||
File source = getSource(backupDir);
|
||||
File updated;
|
||||
if (!myIsMove) {
|
||||
updated = Utils.createTempFile();
|
||||
InputStream in = Utils.findEntryInputStream(patchFile, myPath);
|
||||
boolean executable = readExecutableFlag(in);
|
||||
|
||||
@Override
|
||||
protected void doApply(ZipFile patchFile, File toFile) throws IOException {
|
||||
InputStream in = Utils.findEntryInputStream(patchFile, myPath);
|
||||
boolean executable = readExecutableFlag(in);
|
||||
|
||||
File temp = Utils.createTempFile();
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(temp));
|
||||
try {
|
||||
InputStream oldFileIn = new FileInputStream(toFile);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(updated));
|
||||
try {
|
||||
applyDiff(in, oldFileIn, out);
|
||||
InputStream oldFileIn = Utils.newFileInputStream(source, myPatch.isNormalized());
|
||||
try {
|
||||
applyDiff(in, oldFileIn, out);
|
||||
}
|
||||
finally {
|
||||
oldFileIn.close();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
oldFileIn.close();
|
||||
out.close();
|
||||
}
|
||||
Utils.setExecutable(updated, executable);
|
||||
} else {
|
||||
updated = source;
|
||||
}
|
||||
finally {
|
||||
out.close();
|
||||
}
|
||||
|
||||
replaceUpdated(temp, toFile);
|
||||
Utils.setExecutable(toFile, executable);
|
||||
replaceUpdated(updated, toFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,24 +12,24 @@ public class UpdateZipAction extends BaseUpdateAction {
|
||||
Set<String> myFilesToUpdate;
|
||||
Set<String> myFilesToDelete;
|
||||
|
||||
public UpdateZipAction(String path, long checksum) {
|
||||
super(path, checksum);
|
||||
public UpdateZipAction(Patch patch, String path, String source, long checksum, boolean move) {
|
||||
super(patch, path, source, checksum, move);
|
||||
}
|
||||
|
||||
// test support
|
||||
public UpdateZipAction(String path,
|
||||
public UpdateZipAction(Patch patch, String path,
|
||||
Collection<String> filesToCreate,
|
||||
Collection<String> filesToUpdate,
|
||||
Collection<String> filesToDelete,
|
||||
long checksum) {
|
||||
super(path, checksum);
|
||||
super(patch, path, path, checksum, false);
|
||||
myFilesToCreate = new HashSet<String>(filesToCreate);
|
||||
myFilesToUpdate = new HashSet<String>(filesToUpdate);
|
||||
myFilesToDelete = new HashSet<String>(filesToDelete);
|
||||
}
|
||||
|
||||
public UpdateZipAction(DataInputStream in) throws IOException {
|
||||
super(in);
|
||||
public UpdateZipAction(Patch patch, DataInputStream in) throws IOException {
|
||||
super(patch, in);
|
||||
|
||||
int count = in.readInt();
|
||||
myFilesToCreate = new HashSet<String>(count);
|
||||
@@ -87,9 +87,9 @@ public class UpdateZipAction extends BaseUpdateAction {
|
||||
}
|
||||
});
|
||||
|
||||
DiffCalculator.Result diff = DiffCalculator.calculate(oldCheckSums, newCheckSums);
|
||||
DiffCalculator.Result diff = DiffCalculator.calculate(oldCheckSums, newCheckSums, new LinkedList<String>(), false);
|
||||
|
||||
myFilesToCreate = diff.filesToCreate;
|
||||
myFilesToCreate = diff.filesToCreate.keySet();
|
||||
myFilesToUpdate = diff.filesToUpdate.keySet();
|
||||
myFilesToDelete = diff.filesToDelete.keySet();
|
||||
|
||||
@@ -154,18 +154,15 @@ 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 {
|
||||
protected void doApply(final ZipFile patchFile, File backupDir, File toFile) throws IOException {
|
||||
File temp = Utils.createTempFile();
|
||||
FileOutputStream fileOut = new FileOutputStream(temp);
|
||||
try {
|
||||
final ZipOutputWrapper out = new ZipOutputWrapper(fileOut);
|
||||
out.setCompressionLevel(0);
|
||||
|
||||
processZipFile(toFile, new Processor() {
|
||||
processZipFile(getSource(backupDir), new Processor() {
|
||||
@Override
|
||||
public void process(ZipEntry entry, InputStream in) throws IOException {
|
||||
String path = entry.getName();
|
||||
if (myFilesToDelete.contains(path)) return;
|
||||
|
||||
@@ -16,5 +16,15 @@ public interface UpdaterUI {
|
||||
|
||||
void checkCancelled() throws OperationCancelledException;
|
||||
|
||||
void setDescription(String oldBuildDesc, String newBuildDesc);
|
||||
|
||||
/**
|
||||
* Shows a warning associated with the pretense of a file and asks the user if the validation needs be retried.
|
||||
* This function will return true iff the user wants to retry.
|
||||
* @param message The warning message to display.
|
||||
* @return true if the validation needs to be retried or false if te updater should quit.
|
||||
*/
|
||||
boolean showWarning(String message);
|
||||
|
||||
Map<String, ValidationResult.Option> askUser(List<ValidationResult> validationResults) throws OperationCancelledException;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.intellij.updater;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
@@ -73,6 +73,7 @@ public class Utils {
|
||||
public static void copy(File from, File to) throws IOException {
|
||||
Runner.logger.info("from " + from.getPath() + " to " + to.getPath());
|
||||
if (from.isDirectory()) {
|
||||
to.mkdirs();
|
||||
File[] files = from.listFiles();
|
||||
if (files == null) throw new IOException("Cannot get directory's content: " + from);
|
||||
for (File each : files) {
|
||||
@@ -91,6 +92,15 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void mirror(File from, File to) throws IOException {
|
||||
if (from.exists()) {
|
||||
copy(from, to);
|
||||
} else {
|
||||
delete(to);
|
||||
}
|
||||
}
|
||||
|
||||
public static void copyFileToStream(File from, OutputStream out) throws IOException {
|
||||
InputStream in = new BufferedInputStream(new FileInputStream(from));
|
||||
try {
|
||||
@@ -146,40 +156,121 @@ public class Utils {
|
||||
}
|
||||
|
||||
public static InputStream getEntryInputStream(ZipFile zipFile, String entryPath) throws IOException {
|
||||
InputStream result = findEntryInputStream(zipFile, entryPath);
|
||||
if (result == null) throw new IOException("Entry " + entryPath + " not found");
|
||||
Runner.logger.info("entryPath: " + entryPath);
|
||||
return result;
|
||||
ZipEntry entry = getZipEntry(zipFile, entryPath);
|
||||
return findEntryInputStreamForEntry(zipFile, entry);
|
||||
}
|
||||
|
||||
public static InputStream findEntryInputStream(ZipFile zipFile, String entryPath) throws IOException {
|
||||
ZipEntry entry = zipFile.getEntry(entryPath);
|
||||
if (entry == null || entry.isDirectory()) return null;
|
||||
if (entry == null) return null;
|
||||
return findEntryInputStreamForEntry(zipFile, entry);
|
||||
}
|
||||
|
||||
// if isDirectory check failed, check presence of 'file/' manually
|
||||
if (!entryPath.endsWith("/") && zipFile.getEntry(entryPath + "/") != null) return null;
|
||||
public static ZipEntry getZipEntry(ZipFile zipFile, String entryPath) throws IOException {
|
||||
ZipEntry entry = zipFile.getEntry(entryPath);
|
||||
if (entry == null) throw new IOException("Entry " + entryPath + " not found");
|
||||
Runner.logger.info("entryPath: " + entryPath);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public static InputStream findEntryInputStreamForEntry(ZipFile zipFile, ZipEntry entry) throws IOException {
|
||||
if (entry.isDirectory()) return null;
|
||||
// There is a bug in some JVM implementations where for a directory "X/" in a zipfile, if we do
|
||||
// "zip.getEntry("X/").isDirectory()" returns true, but if we do "zip.getEntry("X").isDirectory()" is false.
|
||||
// getEntry for "name" falls back to finding "X/", so here we make sure that didn't happen.
|
||||
if (zipFile.getEntry(entry.getName() + "/") != null) return null;
|
||||
|
||||
return new BufferedInputStream(zipFile.getInputStream(entry));
|
||||
}
|
||||
|
||||
public static LinkedHashSet<String> collectRelativePaths(File dir) {
|
||||
public static LinkedHashSet<String> collectRelativePaths(File dir, boolean includeDirectories) {
|
||||
LinkedHashSet<String> result = new LinkedHashSet<String>();
|
||||
collectRelativePaths(dir, result, null);
|
||||
collectRelativePaths(dir, result, null, includeDirectories);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void collectRelativePaths(File dir, LinkedHashSet<String> result, String parentPath) {
|
||||
private static void collectRelativePaths(File dir, LinkedHashSet<String> result, String parentPath, boolean includeDirectories) {
|
||||
File[] children = dir.listFiles();
|
||||
if (children == null) return;
|
||||
|
||||
for (File each : children) {
|
||||
String relativePath = (parentPath == null ? "" : parentPath + "/") + each.getName();
|
||||
if (each.isDirectory()) {
|
||||
collectRelativePaths(each, result, relativePath);
|
||||
if (includeDirectories) {
|
||||
// The trailing slash is very important, as it's used by zip to determine whether it is a directory.
|
||||
result.add(relativePath + "/");
|
||||
}
|
||||
collectRelativePaths(each, result, relativePath, includeDirectories);
|
||||
}
|
||||
else {
|
||||
result.add(relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static InputStream newFileInputStream(File file, boolean normalize) throws IOException {
|
||||
if (!normalize || !isZipFile(file.getName())) {
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
return new NormalizedZipInputStream(file);
|
||||
}
|
||||
|
||||
static class NormalizedZipInputStream extends InputStream {
|
||||
|
||||
private ArrayList<? extends ZipEntry> myEntries;
|
||||
private InputStream myStream = null;
|
||||
private int myNextEntry = 0;
|
||||
private final ZipFile myZip;
|
||||
private byte[] myByte = new byte[1];
|
||||
|
||||
NormalizedZipInputStream(File file) throws IOException {
|
||||
myZip = new ZipFile(file);
|
||||
myEntries = Collections.list(myZip.entries());
|
||||
Collections.sort(myEntries, new Comparator<ZipEntry>() {
|
||||
@Override
|
||||
public int compare(ZipEntry a, ZipEntry b) {
|
||||
return a.getName().compareTo(b.getName());
|
||||
}
|
||||
});
|
||||
|
||||
loadNextEntry();
|
||||
}
|
||||
|
||||
private void loadNextEntry() throws IOException {
|
||||
if (myStream != null) {
|
||||
myStream.close();
|
||||
}
|
||||
myStream = null;
|
||||
while (myNextEntry < myEntries.size() && myStream == null) {
|
||||
myStream = findEntryInputStreamForEntry(myZip, myEntries.get(myNextEntry++));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] bytes, int off, int len) throws IOException {
|
||||
if (myStream == null) {
|
||||
return -1;
|
||||
}
|
||||
int b = myStream.read(bytes, off, len);
|
||||
if (b == -1) {
|
||||
loadNextEntry();
|
||||
return read(bytes, off, len);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int b = read(myByte, 0, 1);
|
||||
return b == -1 ? -1 : myByte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (myStream != null) {
|
||||
myStream.close();
|
||||
}
|
||||
myZip.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
56
updater/src/com/intellij/updater/ValidateAction.java
Normal file
56
updater/src/com/intellij/updater/ValidateAction.java
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class ValidateAction extends PatchAction {
|
||||
// Only used on patch creation
|
||||
protected transient File myOlderDir;
|
||||
|
||||
public ValidateAction(Patch patch, String path, long checksum) {
|
||||
super(patch, path, checksum);
|
||||
}
|
||||
|
||||
public ValidateAction(Patch patch, DataInputStream in) throws IOException {
|
||||
super(patch, in);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBuildPatchFile(File olderFile, File newerFile, ZipOutputStream patchOutput) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult validate(File toDir) throws IOException {
|
||||
return doValidateNotChanged(getFile(toDir), ValidationResult.Kind.ERROR, ValidationResult.Action.VALIDATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBackup(File toFile, File backupFile) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRevert(File toFile, File backupFile) throws IOException {
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ public class ValidationResult implements Comparable<ValidationResult> {
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
CREATE("Create"), UPDATE("Update"), DELETE("Delete"), NO_ACTION("");
|
||||
CREATE("Create"), UPDATE("Update"), DELETE("Delete"), NO_ACTION(""), VALIDATE("Validate");
|
||||
|
||||
private final String myDisplayString;
|
||||
|
||||
@@ -24,7 +24,7 @@ public class ValidationResult implements Comparable<ValidationResult> {
|
||||
}
|
||||
|
||||
public enum Option {
|
||||
IGNORE, KEEP, REPLACE, DELETE, KILL_PROCESS
|
||||
NONE, IGNORE, KEEP, REPLACE, DELETE, KILL_PROCESS
|
||||
}
|
||||
|
||||
public static final String ABSENT_MESSAGE = "Absent";
|
||||
|
||||
@@ -71,13 +71,13 @@ Uninstalling IntelliJ IDEA
|
||||
|
||||
Licensing & pricing
|
||||
==========================
|
||||
Licensing and pricing information can be found at https://www.jetbrains.com/idea/buy/index.html.
|
||||
Licensing and pricing information can be found at http://www.jetbrains.com/idea/buy/index.html.
|
||||
|
||||
|
||||
IntelliJ IDEA Overview
|
||||
==========================
|
||||
For general info and facts on IntelliJ IDEA, you can refer to IntelliJ IDEA Info Kit at
|
||||
https://www.jetbrains.com/idea/documentation/product_info_kit.html.
|
||||
http://www.jetbrains.com/idea/documentation/product_info_kit.html.
|
||||
|
||||
|
||||
IDEA Development Package
|
||||
@@ -102,7 +102,7 @@ IDEA Development Package
|
||||
* StarTeam, Perforce, Subversion, Visual SourceSafe integration
|
||||
* Tomcat, Weblogic, WebSphere, Geronimo, JBoss, GlassFish, JSR45 integration
|
||||
|
||||
Download page: https://www.jetbrains.com/idea/download/index.html
|
||||
Download page: http://www.jetbrains.com/idea/download/index.html
|
||||
|
||||
Source code of additional open source plugins shipped with IntelliJ IDEA is available in the Subversion
|
||||
repository at:
|
||||
@@ -139,7 +139,7 @@ IntelliJ Community Site
|
||||
Support
|
||||
=======
|
||||
For technical support and assistance, you may find necessary information at the Support page
|
||||
(https://www.jetbrains.com/support/index.html) or contact us at support@jetbrains.com.
|
||||
(http://www.jetbrains.com/support/index.html) or contact us at support@jetbrains.com.
|
||||
|
||||
|
||||
Bug Reporting:
|
||||
@@ -158,7 +158,7 @@ Contacting us:
|
||||
|
||||
|
||||
=============
|
||||
You are encouraged to visit our IntelliJ IDEA web site at https://www.jetbrains.com/idea/
|
||||
You are encouraged to visit our IntelliJ IDEA web site at http://www.jetbrains.com/idea/
|
||||
or to contact us via e-mail at feedback@jetbrains.com if you have any comments about
|
||||
this release. In particular, we are very interested in any ease-of-use, user
|
||||
interface suggestions that you may have. We will be posting regular updates
|
||||
|
||||
@@ -2,20 +2,33 @@ package com.intellij.updater;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.io.File;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class DigesterTest extends UpdaterTestCase {
|
||||
@Test
|
||||
public void testBasics() throws Exception {
|
||||
Runner.initLogger();
|
||||
Map<String, Long> checkSums = Digester.digestFiles(getDataDir(), Collections.<String>emptyList(), TEST_UI);
|
||||
assertEquals(12, checkSums.size());
|
||||
assertEquals(CHECKSUMS.README_TXT, Digester.digestRegularFile(new File(getDataDir(), "Readme.txt"), false));
|
||||
assertEquals(CHECKSUMS.FOCUSKILLER_DLL, Digester.digestRegularFile(new File(getDataDir(), "/bin/focuskiller.dll"), false));
|
||||
assertEquals(CHECKSUMS.BOOTSTRAP_JAR, Digester.digestZipFile(new File(getDataDir(), "/lib/bootstrap.jar")));
|
||||
assertEquals(CHECKSUMS.BOOTSTRAP_JAR_BINARY, Digester.digestRegularFile(new File(getDataDir(), "/lib/bootstrap.jar"), false));
|
||||
|
||||
assertEquals(CHECKSUMS.README_TXT, (long)checkSums.get("Readme.txt"));
|
||||
assertEquals(CHECKSUMS.FOCUSKILLER_DLL, (long)checkSums.get("bin/focuskiller.dll"));
|
||||
assertEquals(CHECKSUMS.BOOTSTRAP_JAR, (long)checkSums.get("lib/bootstrap.jar"));
|
||||
assertEquals(CHECKSUMS.ANNOTATIONS_JAR_NORM,
|
||||
Digester.digestRegularFile(new File(getDataDir(), "/lib/annotations.jar"), true));
|
||||
assertEquals(CHECKSUMS.ANNOTATIONS_CHANGED_JAR_NORM,
|
||||
Digester.digestRegularFile(new File(getDataDir(), "/lib/annotations_changed.jar"), true));
|
||||
assertEquals(CHECKSUMS.BOOT_JAR_NORM,
|
||||
Digester.digestRegularFile(new File(getDataDir(), "/lib/boot.jar"), true));
|
||||
assertEquals(CHECKSUMS.BOOT2_JAR_NORM,
|
||||
Digester.digestRegularFile(new File(getDataDir(), "/lib/boot2.jar"), true));
|
||||
assertEquals(CHECKSUMS.BOOT2_CHANGED_WITH_UNCHANGED_CONTENT_JAR_NORM,
|
||||
Digester.digestRegularFile(new File(getDataDir(), "/lib/boot2_changed_with_unchanged_content.jar"), true));
|
||||
assertEquals(CHECKSUMS.BOOT_WITH_DIRECTORY_BECOMES_FILE_JAR_NORM,
|
||||
Digester.digestRegularFile(new File(getDataDir(), "/lib/boot_with_directory_becomes_file.jar"), true));
|
||||
assertEquals(CHECKSUMS.BOOTSTRAP_JAR_NORM,
|
||||
Digester.digestRegularFile(new File(getDataDir(), "/lib/bootstrap.jar"), true));
|
||||
assertEquals(CHECKSUMS.BOOTSTRAP_DELETED_JAR_NORM,
|
||||
Digester.digestRegularFile(new File(getDataDir(), "/lib/bootstrap_deleted.jar"), true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class PatchFileCreatorBinaryTest extends PatchFileCreatorTest {
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
myPatchSpec.setBinary(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 com.intellij.openapi.util.io.FileUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class PatchFileCreatorNotBinaryTest extends PatchFileCreatorTest {
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failOnEmptyTargetJar() throws Exception {
|
||||
final File sourceJar = new File(myOlderDir, "lib/empty.jar");
|
||||
FileUtil.copy(new File(myOlderDir, "lib/annotations.jar"), sourceJar);
|
||||
|
||||
try {
|
||||
final File targetJar = new File(myNewerDir, "lib/empty.jar");
|
||||
if (targetJar.exists()) targetJar.delete();
|
||||
assertTrue(targetJar.createNewFile());
|
||||
|
||||
try {
|
||||
createPatch();
|
||||
fail("Should have failed to create a patch against empty .jar");
|
||||
}
|
||||
catch (IOException e) {
|
||||
final String reason = e.getMessage();
|
||||
assertEquals("Corrupted target file: " + targetJar, reason);
|
||||
}
|
||||
finally {
|
||||
targetJar.delete();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
sourceJar.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failOnEmptySourceJar() throws Exception {
|
||||
final File sourceJar = new File(myOlderDir, "lib/empty.jar");
|
||||
if (sourceJar.exists()) sourceJar.delete();
|
||||
assertTrue(sourceJar.createNewFile());
|
||||
|
||||
try {
|
||||
final File targetJar = new File(myNewerDir, "lib/empty.jar");
|
||||
FileUtil.copy(new File(myNewerDir, "lib/annotations.jar"), targetJar);
|
||||
|
||||
try {
|
||||
createPatch();
|
||||
fail("Should have failed to create a patch from empty .jar");
|
||||
}
|
||||
catch (IOException e) {
|
||||
final String reason = e.getMessage();
|
||||
assertEquals("Corrupted source file: " + sourceJar, reason);
|
||||
}
|
||||
finally {
|
||||
targetJar.delete();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
sourceJar.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,7 @@ import com.intellij.openapi.util.io.FileUtil;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
@@ -15,109 +13,89 @@ import java.util.zip.ZipOutputStream;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public class PatchFileCreatorTest extends PatchTestCase {
|
||||
public abstract class PatchFileCreatorTest extends PatchTestCase {
|
||||
private File myFile;
|
||||
protected PatchSpec myPatchSpec;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
myFile = getTempFile("patch.zip");
|
||||
myPatchSpec = new PatchSpec()
|
||||
.setOldFolder(myOlderDir.getAbsolutePath())
|
||||
.setNewFolder(myNewerDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatingAndApplying() throws Exception {
|
||||
createPatch();
|
||||
Patch patch = createPatch();
|
||||
|
||||
assertAppliedAndRevertedCorrectly(PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failOnEmptySourceJar() throws Exception {
|
||||
final File sourceJar = new File(myOlderDir, "lib/empty.jar");
|
||||
if (sourceJar.exists()) sourceJar.delete();
|
||||
assertTrue(sourceJar.createNewFile());
|
||||
|
||||
try {
|
||||
final File targetJar = new File(myNewerDir, "lib/empty.jar");
|
||||
FileUtil.copy(new File(myNewerDir, "lib/annotations.jar"), targetJar);
|
||||
|
||||
try {
|
||||
createPatch();
|
||||
fail("Should have failed to create a patch from empty .jar");
|
||||
}
|
||||
catch (IOException e) {
|
||||
final String reason = e.getMessage();
|
||||
assertEquals("Corrupted source file: " + sourceJar, reason);
|
||||
}
|
||||
finally {
|
||||
targetJar.delete();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
sourceJar.delete();
|
||||
}
|
||||
public void testCreatingAndApplyingStrict() throws Exception {
|
||||
myPatchSpec.setStrict(true);
|
||||
Patch patch = createPatch();
|
||||
assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failOnEmptyTargetJar() throws Exception {
|
||||
final File sourceJar = new File(myOlderDir, "lib/empty.jar");
|
||||
FileUtil.copy(new File(myOlderDir, "lib/annotations.jar"), sourceJar);
|
||||
public void testCreatingAndApplyingOnADifferentRoot() throws Exception {
|
||||
myPatchSpec.setRoot("bin/");
|
||||
myPatchSpec.setStrict(true);
|
||||
|
||||
try {
|
||||
final File targetJar = new File(myNewerDir, "lib/empty.jar");
|
||||
if (targetJar.exists()) targetJar.delete();
|
||||
assertTrue(targetJar.createNewFile());
|
||||
Patch patch = createPatch();
|
||||
|
||||
try {
|
||||
createPatch();
|
||||
fail("Should have failed to create a patch against empty .jar");
|
||||
}
|
||||
catch (IOException e) {
|
||||
final String reason = e.getMessage();
|
||||
assertEquals("Corrupted target file: " + targetJar, reason);
|
||||
}
|
||||
finally {
|
||||
targetJar.delete();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
sourceJar.delete();
|
||||
}
|
||||
File target = new File(myOlderDir, "bin");
|
||||
assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, target, TEST_UI));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatingAndFailingOnADifferentRoot() throws Exception {
|
||||
myPatchSpec.setRoot("bin/");
|
||||
myPatchSpec.setStrict(true);
|
||||
|
||||
Patch patch = createPatch();
|
||||
|
||||
File target = new File(myOlderDir, "bin");
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, target, TEST_UI);
|
||||
preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction(patch));
|
||||
assertNothingHasChanged(patch, preparationResult, new HashMap<String, ValidationResult.Option>());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReverting() throws Exception {
|
||||
createPatch();
|
||||
Patch patch = createPatch();
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction());
|
||||
assertNothingHasChanged(preparationResult, new HashMap<String, ValidationResult.Option>());
|
||||
preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction(patch));
|
||||
assertNothingHasChanged(patch, 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);
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, 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 {
|
||||
// Lock the file. FileLock is not good here, because we need to prevent deletion.
|
||||
int b = raf.read();
|
||||
raf.seek(0);
|
||||
raf.write(b);
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
|
||||
Map<String, Long> original = Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI);
|
||||
Map<String, Long> original = patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, 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));
|
||||
assertEquals(original, patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI));
|
||||
}
|
||||
finally {
|
||||
raf.close();
|
||||
@@ -126,44 +104,57 @@ public class PatchFileCreatorTest extends PatchTestCase {
|
||||
|
||||
@Test
|
||||
public void testApplyingWithAbsentFileToDelete() throws Exception {
|
||||
PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
|
||||
Collections.<String>emptyList(), TEST_UI);
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
new File(myOlderDir, "bin/idea.bat").delete();
|
||||
|
||||
assertAppliedAndRevertedCorrectly(PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyingWithAbsentFileToUpdateStrict() throws Exception {
|
||||
myPatchSpec.setStrict(true);
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
new File(myOlderDir, "lib/annotations.jar").delete();
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertEquals(1, preparationResult.validationResults.size());
|
||||
assertEquals(new ValidationResult(ValidationResult.Kind.ERROR,
|
||||
"lib/annotations.jar",
|
||||
ValidationResult.Action.UPDATE,
|
||||
ValidationResult.ABSENT_MESSAGE,
|
||||
ValidationResult.Option.NONE), preparationResult.validationResults.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyingWithAbsentOptionalFile() throws Exception {
|
||||
FileUtil.writeToFile(new File(myNewerDir, "bin/idea.bat"), "new content".getBytes());
|
||||
|
||||
PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
|
||||
Collections.singletonList("bin/idea.bat"), TEST_UI);
|
||||
myPatchSpec.setOptionalFiles(Collections.singletonList("bin/idea.bat"));
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
new File(myOlderDir, "bin/idea.bat").delete();
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertTrue(preparationResult.validationResults.isEmpty());
|
||||
assertAppliedAndRevertedCorrectly(preparationResult);
|
||||
assertAppliedAndRevertedCorrectly(patch, preparationResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRevertingWithAbsentFileToDelete() throws Exception {
|
||||
PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
|
||||
Collections.<String>emptyList(), TEST_UI);
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
new File(myOlderDir, "bin/idea.bat").delete();
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction());
|
||||
assertNothingHasChanged(preparationResult, new HashMap<String, ValidationResult.Option>());
|
||||
preparationResult.patch.getActions().add(new MyFailOnApplyPatchAction(patch));
|
||||
assertNothingHasChanged(patch, preparationResult, new HashMap<String, ValidationResult.Option>());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyingWithoutCriticalFiles() throws Exception {
|
||||
PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
|
||||
Collections.<String>emptyList(), TEST_UI);
|
||||
PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
|
||||
assertTrue(PatchFileCreator.apply(preparationResult, new HashMap<String, ValidationResult.Option>(), TEST_UI));
|
||||
@@ -171,24 +162,49 @@ public class PatchFileCreatorTest extends PatchTestCase {
|
||||
|
||||
@Test
|
||||
public void testApplyingWithCriticalFiles() throws Exception {
|
||||
PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Arrays.asList("lib/annotations.jar"),
|
||||
Collections.<String>emptyList(), TEST_UI);
|
||||
myPatchSpec.setCriticalFiles(Arrays.asList("lib/annotations.jar"));
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
}
|
||||
|
||||
assertTrue(PatchFileCreator.apply(preparationResult, new HashMap<String, ValidationResult.Option>(), TEST_UI));
|
||||
assertAppliedCorrectly();
|
||||
@Test
|
||||
public void testApplyingWithModifiedCriticalFiles() throws Exception {
|
||||
myPatchSpec.setStrict(true);
|
||||
myPatchSpec.setCriticalFiles(Arrays.asList("lib/annotations.jar"));
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(new File(myOlderDir, "lib/annotations.jar"), "rw");
|
||||
raf.seek(20);
|
||||
raf.write(42);
|
||||
raf.close();
|
||||
|
||||
assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyingWithModifiedCriticalFilesAndDifferentRoot() throws Exception {
|
||||
myPatchSpec.setStrict(true);
|
||||
myPatchSpec.setRoot("lib/");
|
||||
myPatchSpec.setCriticalFiles(Arrays.asList("lib/annotations.jar"));
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(new File(myOlderDir, "lib/annotations.jar"), "rw");
|
||||
raf.seek(20);
|
||||
raf.write(42);
|
||||
raf.close();
|
||||
|
||||
File toDir = new File(myOlderDir, "lib/");
|
||||
assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, toDir, TEST_UI));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyingWithCaseChangedNames() throws Exception {
|
||||
FileUtil.rename(new File(myOlderDir, "Readme.txt"),
|
||||
new File(myOlderDir, "README.txt"));
|
||||
FileUtil.rename(new File(myOlderDir, "Readme.txt"), new File(myOlderDir, "README.txt"));
|
||||
|
||||
PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
|
||||
Collections.<String>emptyList(), TEST_UI);
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
assertAppliedAndRevertedCorrectly(PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -201,13 +217,11 @@ public class PatchFileCreatorTest extends PatchTestCase {
|
||||
new File(file, "subDir").mkdir();
|
||||
new File(file, "subDir/subFile.txt").createNewFile();
|
||||
|
||||
FileUtil.copy(new File(myOlderDir, "lib/boot.jar"),
|
||||
new File(myOlderDir, "lib/boot_with_directory_becomes_file.jar"));
|
||||
FileUtil.copy(new File(myOlderDir, "lib/boot.jar"), new File(myOlderDir, "lib/boot_with_directory_becomes_file.jar"));
|
||||
|
||||
PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
|
||||
Collections.<String>emptyList(), TEST_UI);
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
assertAppliedAndRevertedCorrectly(PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -219,15 +233,14 @@ public class PatchFileCreatorTest extends PatchTestCase {
|
||||
FileUtil.copy(new File(myOlderDir, "lib/boot_with_directory_becomes_file.jar"),
|
||||
new File(myOlderDir, "lib/boot.jar"));
|
||||
|
||||
PatchFileCreator.create(myOlderDir, myNewerDir, myFile, Collections.<String>emptyList(), Collections.<String>emptyList(),
|
||||
Collections.<String>emptyList(), TEST_UI);
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
assertAppliedAndRevertedCorrectly(PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
assertAppliedAndRevertedCorrectly(patch, PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConsideringOptions() throws Exception {
|
||||
createPatch();
|
||||
Patch patch = createPatch();
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
Map<String, ValidationResult.Option> options = new HashMap<String, ValidationResult.Option>();
|
||||
@@ -235,47 +248,241 @@ public class PatchFileCreatorTest extends PatchTestCase {
|
||||
options.put(each.getPath(), ValidationResult.Option.IGNORE);
|
||||
}
|
||||
|
||||
assertNothingHasChanged(preparationResult, options);
|
||||
assertNothingHasChanged(patch, preparationResult, options);
|
||||
}
|
||||
|
||||
private void createPatch() throws IOException, OperationCancelledException {
|
||||
PatchFileCreator.create(myOlderDir, myNewerDir, myFile,
|
||||
Collections.<String>emptyList(), Collections.<String>emptyList(), Collections.<String>emptyList(), TEST_UI);
|
||||
@Test
|
||||
public void testApplyWhenCommonFileChanges() throws Exception {
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
FileUtil.copy(new File(myOlderDir, "/lib/bootstrap.jar"),
|
||||
new File(myOlderDir, "/lib/boot.jar"));
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertTrue(preparationResult.validationResults.isEmpty());
|
||||
assertAppliedAndRevertedCorrectly(patch, preparationResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyWhenCommonFileChangesStrict() throws Exception {
|
||||
myPatchSpec.setStrict(true);
|
||||
PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
FileUtil.copy(new File(myOlderDir, "/lib/bootstrap.jar"), new File(myOlderDir, "/lib/boot.jar"));
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertEquals(1, preparationResult.validationResults.size());
|
||||
assertEquals(
|
||||
new ValidationResult(ValidationResult.Kind.ERROR, "lib/boot.jar", ValidationResult.Action.VALIDATE, ValidationResult.MODIFIED_MESSAGE,
|
||||
ValidationResult.Option.NONE), preparationResult.validationResults.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyWhenNewFileExists() throws Exception {
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
FileUtil.writeToFile(new File(myOlderDir, "newfile.txt"), "hello");
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertTrue(preparationResult.validationResults.isEmpty());
|
||||
assertAppliedAndRevertedCorrectly(patch, preparationResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyWhenNewFileExistsStrict() throws Exception {
|
||||
myPatchSpec.setStrict(true);
|
||||
myPatchSpec.setDeleteFiles(Collections.singletonList("lib/java_pid.*\\.hprof"));
|
||||
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
FileUtil.writeToFile(new File(myOlderDir, "newfile.txt"), "hello");
|
||||
FileUtil.writeToFile(new File(myOlderDir, "lib/java_pid1234.hprof"), "bye!");
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertEquals(1, preparationResult.validationResults.size());
|
||||
assertEquals(new ValidationResult(ValidationResult.Kind.CONFLICT, "newfile.txt", ValidationResult.Action.VALIDATE, "Unexpected file",
|
||||
ValidationResult.Option.DELETE), preparationResult.validationResults.get(0));
|
||||
assertAppliedAndRevertedCorrectly(patch, preparationResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyWhenNewDeletableFileExistsStrict() throws Exception {
|
||||
myPatchSpec.setStrict(true);
|
||||
myPatchSpec.setDeleteFiles(Collections.singletonList("lib/java_pid.*\\.hprof"));
|
||||
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
FileUtil.writeToFile(new File(myOlderDir, "lib/java_pid1234.hprof"), "bye!");
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertEquals(0, preparationResult.validationResults.size());
|
||||
assertAppliedAndRevertedCorrectly(patch, preparationResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyWhenNewDirectoryExistsStrict() throws Exception {
|
||||
myPatchSpec.setStrict(true);
|
||||
new File(myOlderDir, "delete").mkdirs();
|
||||
FileUtil.writeToFile(new File(myOlderDir, "delete/deleteme.txt"), "bye!");
|
||||
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
|
||||
new File(myOlderDir, "unexpected_newdir").mkdirs();
|
||||
FileUtil.writeToFile(new File(myOlderDir, "unexpected_newdir/unexpected.txt"), "bye!");
|
||||
|
||||
new File(myOlderDir, "newDir").mkdir();
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertEquals(3, preparationResult.validationResults.size());
|
||||
assertEquals(new ValidationResult(ValidationResult.Kind.CONFLICT,
|
||||
"unexpected_newdir/unexpected.txt",
|
||||
ValidationResult.Action.VALIDATE,
|
||||
"Unexpected file",
|
||||
ValidationResult.Option.DELETE), preparationResult.validationResults.get(0));
|
||||
assertEquals(new ValidationResult(ValidationResult.Kind.CONFLICT,
|
||||
"unexpected_newdir/",
|
||||
ValidationResult.Action.VALIDATE,
|
||||
"Unexpected file",
|
||||
ValidationResult.Option.DELETE), preparationResult.validationResults.get(1));
|
||||
assertEquals(new ValidationResult(ValidationResult.Kind.CONFLICT,
|
||||
"newDir/",
|
||||
ValidationResult.Action.CREATE,
|
||||
ValidationResult.ALREADY_EXISTS_MESSAGE,
|
||||
ValidationResult.Option.REPLACE), preparationResult.validationResults.get(2));
|
||||
new File(myOlderDir, "newDir").delete();
|
||||
assertAppliedAndRevertedCorrectly(patch, preparationResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveFileByContent() throws IOException, OperationCancelledException {
|
||||
myPatchSpec.setStrict(true);
|
||||
FileUtil.writeToFile(new File(myOlderDir, "move/from/this/directory/move.me"), "oldcontent");
|
||||
FileUtil.writeToFile(new File(myOlderDir, "a/deleted/file/that/is/a/copy/move.me"), "newcontent");
|
||||
FileUtil.writeToFile(new File(myNewerDir, "move/to/this/directory/move.me"), "newcontent");
|
||||
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
PatchAction action = getAction(patch, "move/to/this/directory/move.me");
|
||||
assertTrue(action instanceof UpdateAction);
|
||||
UpdateAction update = (UpdateAction)action;
|
||||
assertTrue(update.isMove());
|
||||
assertEquals("a/deleted/file/that/is/a/copy/move.me", update.getSourcePath());
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertAppliedAndRevertedCorrectly(patch, preparationResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveCriticalFileByContent() throws IOException, OperationCancelledException {
|
||||
myPatchSpec.setStrict(true);
|
||||
myPatchSpec.setCriticalFiles(Collections.singletonList("a/deleted/file/that/is/a/copy/move.me"));
|
||||
|
||||
FileUtil.writeToFile(new File(myOlderDir, "move/from/this/directory/move.me"), "oldcontent");
|
||||
FileUtil.writeToFile(new File(myOlderDir, "a/deleted/file/that/is/a/copy/move.me"), "newcontent");
|
||||
FileUtil.writeToFile(new File(myNewerDir, "move/to/this/directory/move.me"), "newcontent");
|
||||
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
PatchAction action = getAction(patch, "move/to/this/directory/move.me");
|
||||
assertTrue(action instanceof CreateAction);
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertAppliedAndRevertedCorrectly(patch, preparationResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDontMoveFromDirectoryToFile() throws IOException, OperationCancelledException {
|
||||
myPatchSpec.setStrict(true);
|
||||
new File(myOlderDir, "from/move.me").mkdirs();
|
||||
FileUtil.writeToFile(new File(myNewerDir, "move/to/move.me"), "different");
|
||||
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
// Creating a patch would have crashed if the directory had been chosen.
|
||||
PatchAction action = getAction(patch, "move/to/move.me");
|
||||
assertTrue(action instanceof CreateAction);
|
||||
action = getAction(patch, "from/move.me/");
|
||||
assertTrue(action instanceof DeleteAction);
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertEquals(0, preparationResult.validationResults.size());
|
||||
assertAppliedAndRevertedCorrectly(patch, preparationResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveFileByLocation() throws IOException, OperationCancelledException {
|
||||
myPatchSpec.setStrict(true);
|
||||
FileUtil.writeToFile(new File(myOlderDir, "move/from/this/directory/move.me"), "they");
|
||||
FileUtil.writeToFile(new File(myOlderDir, "not/from/this/one/move.me"), "are");
|
||||
FileUtil.writeToFile(new File(myNewerDir, "move/to/this/directory/move.me"), "different");
|
||||
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
PatchAction action = getAction(patch, "move/to/this/directory/move.me");
|
||||
assertTrue(action instanceof UpdateAction);
|
||||
UpdateAction update = (UpdateAction)action;
|
||||
assertTrue(!update.isMove());
|
||||
assertEquals("move/from/this/directory/move.me", update.getSourcePath());
|
||||
|
||||
PatchFileCreator.PreparationResult preparationResult = PatchFileCreator.prepareAndValidate(myFile, myOlderDir, TEST_UI);
|
||||
assertAppliedAndRevertedCorrectly(patch, preparationResult);
|
||||
}
|
||||
|
||||
protected PatchAction getAction(Patch patch, String path) {
|
||||
for (PatchAction action : patch.getActions()) {
|
||||
if (action.getPath().equals(path)) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Patch createPatch() throws IOException, OperationCancelledException {
|
||||
Patch patch = PatchFileCreator.create(myPatchSpec, myFile, TEST_UI);
|
||||
assertTrue(myFile.exists());
|
||||
return patch;
|
||||
}
|
||||
|
||||
private void assertNothingHasChanged(PatchFileCreator.PreparationResult preparationResult, Map<String, ValidationResult.Option> options)
|
||||
private void assertNothingHasChanged(Patch patch, PatchFileCreator.PreparationResult preparationResult, Map<String, ValidationResult.Option> options)
|
||||
throws Exception {
|
||||
Map<String, Long> before = Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI);
|
||||
Map<String, Long> before = patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI);
|
||||
PatchFileCreator.apply(preparationResult, options, TEST_UI);
|
||||
Map<String, Long> after = Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI);
|
||||
Map<String, Long> after = patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI);
|
||||
|
||||
DiffCalculator.Result diff = DiffCalculator.calculate(before, after);
|
||||
DiffCalculator.Result diff = DiffCalculator.calculate(before, after, new LinkedList<String>(), false);
|
||||
assertTrue(diff.filesToCreate.isEmpty());
|
||||
assertTrue(diff.filesToDelete.isEmpty());
|
||||
assertTrue(diff.filesToUpdate.isEmpty());
|
||||
}
|
||||
|
||||
private void assertAppliedAndRevertedCorrectly(PatchFileCreator.PreparationResult preparationResult)
|
||||
private void assertAppliedAndRevertedCorrectly(Patch patch, PatchFileCreator.PreparationResult preparationResult)
|
||||
throws IOException, OperationCancelledException {
|
||||
|
||||
Map<String, Long> original = Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI);
|
||||
|
||||
Map<String, Long> original = patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI);
|
||||
Map<String, Long> target = patch.digestFiles(myNewerDir, Collections.<String>emptyList(), false, TEST_UI);
|
||||
File backup = getTempFile("backup");
|
||||
|
||||
HashMap<String, ValidationResult.Option> options = new HashMap<String, ValidationResult.Option>();
|
||||
for (ValidationResult each : preparationResult.validationResults) {
|
||||
assertTrue(each.toString(), each.kind != ValidationResult.Kind.ERROR);
|
||||
if (patch.isStrict()) {
|
||||
assertFalse(each.options.contains(ValidationResult.Option.NONE));
|
||||
assertTrue(each.options.size() > 0);
|
||||
options.put(each.path, each.options.get(0));
|
||||
} else {
|
||||
assertTrue(each.toString(), each.kind != ValidationResult.Kind.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
List<PatchAction> appliedActions =
|
||||
PatchFileCreator.apply(preparationResult, new HashMap<String, ValidationResult.Option>(), backup, TEST_UI).appliedActions;
|
||||
assertAppliedCorrectly();
|
||||
PatchFileCreator.apply(preparationResult, options, backup, TEST_UI).appliedActions;
|
||||
Map<String, Long> patched = patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI);
|
||||
|
||||
assertFalse(original.equals(Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI)));
|
||||
if (patch.isStrict()) {
|
||||
assertEquals(patched, target);
|
||||
} else {
|
||||
assertAppliedCorrectly();
|
||||
}
|
||||
|
||||
assertNotEquals(original, patched);
|
||||
|
||||
PatchFileCreator.revert(preparationResult, appliedActions, backup, TEST_UI);
|
||||
|
||||
assertEquals(original, Digester.digestFiles(myOlderDir, Collections.<String>emptyList(), TEST_UI));
|
||||
Map<String, Long> reverted = patch.digestFiles(myOlderDir, Collections.<String>emptyList(), false, TEST_UI);
|
||||
assertEquals(original, reverted);
|
||||
}
|
||||
|
||||
protected void assertAppliedCorrectly() throws IOException {
|
||||
@@ -333,8 +540,11 @@ public class PatchFileCreatorTest extends PatchTestCase {
|
||||
}
|
||||
|
||||
private static class MyFailOnApplyPatchAction extends PatchAction {
|
||||
public MyFailOnApplyPatchAction() {
|
||||
super("_dummy_file_", -1);
|
||||
// Only used on patch creation
|
||||
protected transient File myOlderDir;
|
||||
|
||||
public MyFailOnApplyPatchAction(Patch patch) {
|
||||
super(patch, "_dummy_file_", Digester.INVALID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -348,12 +558,12 @@ public class PatchFileCreatorTest extends PatchTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValidationResult doValidate(File toFile) throws IOException {
|
||||
protected ValidationResult validate(File toDir) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doApply(ZipFile patchFile, File toFile) throws IOException {
|
||||
protected void doApply(ZipFile patchFile, File backupDir, File toFile) throws IOException {
|
||||
throw new IOException("dummy exception");
|
||||
}
|
||||
|
||||
|
||||
@@ -20,26 +20,34 @@ public class PatchTest extends PatchTestCase {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
myPatch = new Patch(myOlderDir, myNewerDir, Collections.<String>emptyList(), Collections.<String>emptyList(),
|
||||
Collections.<String>emptyList(), TEST_UI);
|
||||
PatchSpec spec = new PatchSpec()
|
||||
.setOldFolder(myOlderDir.getAbsolutePath())
|
||||
.setNewFolder(myNewerDir.getAbsolutePath());
|
||||
myPatch = new Patch(spec, TEST_UI);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDigestFiles() throws Exception {
|
||||
Map<String, Long> checkSums = myPatch.digestFiles(getDataDir(), Collections.<String>emptyList(), false, TEST_UI);
|
||||
assertEquals(9, checkSums.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasics() throws Exception {
|
||||
List<PatchAction> expectedActions = Arrays.asList(
|
||||
new CreateAction("newDir/newFile.txt"),
|
||||
new UpdateAction("Readme.txt", CHECKSUMS.README_TXT),
|
||||
new UpdateZipAction("lib/annotations.jar",
|
||||
new CreateAction(myPatch, "newDir/newFile.txt"),
|
||||
new UpdateAction(myPatch, "Readme.txt", CHECKSUMS.README_TXT),
|
||||
new UpdateZipAction(myPatch, "lib/annotations.jar",
|
||||
Arrays.asList("org/jetbrains/annotations/NewClass.class"),
|
||||
Arrays.asList("org/jetbrains/annotations/Nullable.class"),
|
||||
Arrays.asList("org/jetbrains/annotations/TestOnly.class"),
|
||||
CHECKSUMS.ANNOTATIONS_JAR),
|
||||
new UpdateZipAction("lib/bootstrap.jar",
|
||||
new UpdateZipAction(myPatch, "lib/bootstrap.jar",
|
||||
Collections.<String>emptyList(),
|
||||
Collections.<String>emptyList(),
|
||||
Arrays.asList("com/intellij/ide/ClassloaderUtil.class"),
|
||||
CHECKSUMS.BOOTSTRAP_JAR),
|
||||
new DeleteAction("bin/idea.bat", CHECKSUMS.IDEA_BAT));
|
||||
new DeleteAction(myPatch, "bin/idea.bat", CHECKSUMS.IDEA_BAT));
|
||||
List<PatchAction> actualActions = new ArrayList<PatchAction>(myPatch.getActions());
|
||||
Collections.sort(expectedActions, COMPARATOR);
|
||||
Collections.sort(actualActions, COMPARATOR);
|
||||
@@ -48,21 +56,21 @@ public class PatchTest extends PatchTestCase {
|
||||
|
||||
@Test
|
||||
public void testCreatingWithIgnoredFiles() throws Exception {
|
||||
myPatch = new Patch(myOlderDir,
|
||||
myNewerDir,
|
||||
Arrays.asList("Readme.txt", "bin/idea.bat"),
|
||||
Collections.<String>emptyList(),
|
||||
Collections.<String>emptyList(),
|
||||
PatchSpec spec = new PatchSpec()
|
||||
.setOldFolder(myOlderDir.getAbsolutePath())
|
||||
.setNewFolder(myNewerDir.getAbsolutePath())
|
||||
.setIgnoredFiles(Arrays.asList("Readme.txt", "bin/idea.bat"));
|
||||
myPatch = new Patch(spec,
|
||||
TEST_UI);
|
||||
|
||||
List<PatchAction> expectedActions = Arrays.asList(
|
||||
new CreateAction("newDir/newFile.txt"),
|
||||
new UpdateZipAction("lib/annotations.jar",
|
||||
new CreateAction(myPatch, "newDir/newFile.txt"),
|
||||
new UpdateZipAction(myPatch, "lib/annotations.jar",
|
||||
Arrays.asList("org/jetbrains/annotations/NewClass.class"),
|
||||
Arrays.asList("org/jetbrains/annotations/Nullable.class"),
|
||||
Arrays.asList("org/jetbrains/annotations/TestOnly.class"),
|
||||
CHECKSUMS.ANNOTATIONS_JAR),
|
||||
new UpdateZipAction("lib/bootstrap.jar",
|
||||
new UpdateZipAction(myPatch, "lib/bootstrap.jar",
|
||||
Collections.<String>emptyList(),
|
||||
Collections.<String>emptyList(),
|
||||
Arrays.asList("com/intellij/ide/ClassloaderUtil.class"),
|
||||
@@ -125,8 +133,11 @@ public class PatchTest extends PatchTestCase {
|
||||
ValidationResult.Option.IGNORE))),
|
||||
new HashSet<ValidationResult>(myPatch.validate(myOlderDir, TEST_UI)));
|
||||
|
||||
myPatch = new Patch(myOlderDir, myNewerDir, Collections.<String>emptyList(), Collections.<String>emptyList(),
|
||||
Arrays.asList("lib/annotations.jar"), TEST_UI);
|
||||
PatchSpec spec = new PatchSpec()
|
||||
.setOldFolder(myOlderDir.getAbsolutePath())
|
||||
.setNewFolder(myNewerDir.getAbsolutePath())
|
||||
.setOptionalFiles(Arrays.asList("lib/annotations.jar"));
|
||||
myPatch = new Patch(spec, TEST_UI);
|
||||
FileUtil.delete(new File(myOlderDir, "lib/annotations.jar"));
|
||||
assertEquals(Collections.<ValidationResult>emptyList(),
|
||||
myPatch.validate(myOlderDir, TEST_UI));
|
||||
|
||||
@@ -14,12 +14,12 @@ public class RunnerTest extends UpdaterTestCase {
|
||||
Runner.initLogger();
|
||||
|
||||
assertEquals(Arrays.asList("xxx", "yyy", "zzz/zzz", "aaa"),
|
||||
Runner.extractFiles(args, "ignored"));
|
||||
Runner.extractArguments(args, "ignored"));
|
||||
|
||||
assertEquals(Arrays.asList("ccc"),
|
||||
Runner.extractFiles(args, "critical"));
|
||||
Runner.extractArguments(args, "critical"));
|
||||
|
||||
assertEquals(Collections.<String>emptyList(),
|
||||
Runner.extractFiles(args, "unknown"));
|
||||
Runner.extractArguments(args, "unknown"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,15 @@ public abstract class UpdaterTestCase {
|
||||
@Override
|
||||
public void setStatus(String status) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String oldBuildDesc, String newBuildDesc) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showWarning(String message) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
protected CheckSums CHECKSUMS;
|
||||
@@ -54,7 +63,16 @@ public abstract class UpdaterTestCase {
|
||||
public final long IDEA_BAT;
|
||||
public final long ANNOTATIONS_JAR;
|
||||
public final long BOOTSTRAP_JAR;
|
||||
public final long BOOTSTRAP_JAR_BINARY;
|
||||
public final long FOCUSKILLER_DLL;
|
||||
public final long ANNOTATIONS_JAR_NORM;
|
||||
public final long ANNOTATIONS_CHANGED_JAR_NORM;
|
||||
public final long BOOT_JAR_NORM;
|
||||
public final long BOOT2_JAR_NORM;
|
||||
public final long BOOT2_CHANGED_WITH_UNCHANGED_CONTENT_JAR_NORM;
|
||||
public final long BOOT_WITH_DIRECTORY_BECOMES_FILE_JAR_NORM;
|
||||
public final long BOOTSTRAP_JAR_NORM;
|
||||
public final long BOOTSTRAP_DELETED_JAR_NORM;
|
||||
|
||||
public CheckSums(boolean windowsLineEnds) {
|
||||
if (windowsLineEnds) {
|
||||
@@ -68,6 +86,16 @@ public abstract class UpdaterTestCase {
|
||||
ANNOTATIONS_JAR = 2119442657L;
|
||||
BOOTSTRAP_JAR = 2082851308L;
|
||||
FOCUSKILLER_DLL = 1991212227L;
|
||||
BOOTSTRAP_JAR_BINARY = 2745721972L;
|
||||
|
||||
ANNOTATIONS_JAR_NORM = 2119442657L;
|
||||
ANNOTATIONS_CHANGED_JAR_NORM = 4088078858L;
|
||||
BOOT_JAR_NORM = 3018038682L;
|
||||
BOOT2_JAR_NORM = 2406818996L;
|
||||
BOOT2_CHANGED_WITH_UNCHANGED_CONTENT_JAR_NORM = 2406818996L;
|
||||
BOOT_WITH_DIRECTORY_BECOMES_FILE_JAR_NORM = 1972168924;
|
||||
BOOTSTRAP_JAR_NORM = 2082851308;
|
||||
BOOTSTRAP_DELETED_JAR_NORM = 544883981L;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user