| package com.intellij.updater; |
| |
| import java.io.*; |
| import java.util.*; |
| import java.util.zip.ZipFile; |
| |
| public class Patch { |
| private List<PatchAction> myActions = new ArrayList<PatchAction>(); |
| |
| 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; |
| |
| 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(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; |
| |
| Runner.logger.info("Calculating difference..."); |
| ui.startProcess("Calculating difference..."); |
| ui.checkCancelled(); |
| |
| diff = DiffCalculator.calculate(Digester.digestFiles(olderDir, ignoredFiles, ui), |
| Digester.digestFiles(newerDir, ignoredFiles, ui)); |
| |
| 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()) { |
| tempActions.add(new DeleteAction(each.getKey(), each.getValue())); |
| } |
| |
| for (String each : diff.filesToCreate) { |
| tempActions.add(new CreateAction(each)); |
| } |
| |
| for (Map.Entry<String, Long> each : diff.filesToUpdate.entrySet()) { |
| if (Utils.isZipFile(each.getKey())) { |
| tempActions.add(new UpdateZipAction(each.getKey(), each.getValue())); |
| } |
| else { |
| tempActions.add(new UpdateAction(each.getKey(), each.getValue())); |
| } |
| } |
| |
| Runner.logger.info("Preparing actions..."); |
| ui.startProcess("Preparing actions..."); |
| ui.checkCancelled(); |
| |
| for (PatchAction each : tempActions) { |
| ui.setStatus(each.getPath()); |
| ui.checkCancelled(); |
| |
| if (!each.calculate(olderDir, newerDir)) continue; |
| myActions.add(each); |
| each.setCritical(criticalFiles.contains(each.getPath())); |
| each.setOptional(optionalFiles.contains(each.getPath())); |
| } |
| } |
| |
| public List<PatchAction> getActions() { |
| return myActions; |
| } |
| |
| 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 == DeleteAction.class) { |
| key = DELETE_ACTION_KEY; |
| } |
| else { |
| throw new RuntimeException("Unknown action " + each); |
| } |
| dataOut.writeInt(key); |
| each.write(dataOut); |
| } |
| } |
| finally { |
| dataOut.flush(); |
| } |
| } |
| |
| private void read(InputStream patchIn) throws IOException { |
| List<PatchAction> newActions = new ArrayList<PatchAction>(); |
| |
| @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") DataInputStream in = new DataInputStream(patchIn); |
| int size = in.readInt(); |
| |
| while (size-- > 0) { |
| int key = in.readInt(); |
| PatchAction a; |
| switch (key) { |
| case CREATE_ACTION_KEY: |
| a = new CreateAction(in); |
| break; |
| case UPDATE_ACTION_KEY: |
| a = new UpdateAction(in); |
| break; |
| case UPDATE_ZIP_ACTION_KEY: |
| a = new UpdateZipAction(in); |
| break; |
| case DELETE_ACTION_KEY: |
| a = new DeleteAction(in); |
| break; |
| default: |
| throw new RuntimeException("Unknown action type " + key); |
| } |
| newActions.add(a); |
| } |
| |
| myActions = newActions; |
| } |
| |
| public List<ValidationResult> validate(final File toDir, UpdaterUI ui) throws IOException, OperationCancelledException { |
| final LinkedHashSet<String> files = Utils.collectRelativePaths(toDir); |
| final List<ValidationResult> result = new ArrayList<ValidationResult>(); |
| |
| Runner.logger.info("Validating installation..."); |
| forEach(myActions, "Validating installation...", ui, true, |
| new ActionsProcessor() { |
| 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 backupDir, |
| final Map<String, ValidationResult.Option> options, |
| UpdaterUI ui) throws IOException, OperationCancelledException { |
| |
| List<PatchAction> actionsToProcess = new ArrayList<PatchAction>(); |
| for (PatchAction each : myActions) { |
| if (each.shouldApply(toDir, options)) actionsToProcess.add(each); |
| } |
| |
| forEach(actionsToProcess, "Backing up files...", ui, true, |
| new ActionsProcessor() { |
| public void forEach(PatchAction each) throws IOException { |
| each.backup(toDir, backupDir); |
| } |
| }); |
| |
| final List<PatchAction> appliedActions = new ArrayList<PatchAction>(); |
| boolean shouldRevert = false; |
| boolean cancelled = false; |
| try { |
| forEach(actionsToProcess, "Applying patch...", ui, true, |
| new ActionsProcessor() { |
| public void forEach(PatchAction each) throws IOException { |
| appliedActions.add(each); |
| each.apply(patchFile, toDir); |
| } |
| }); |
| } |
| catch (OperationCancelledException e) { |
| Runner.printStackTrace(e); |
| shouldRevert = true; |
| cancelled = true; |
| } |
| catch (Throwable e) { |
| Runner.printStackTrace(e); |
| shouldRevert = true; |
| ui.showError(e); |
| } |
| |
| if (shouldRevert) { |
| revert(appliedActions, backupDir, toDir, ui); |
| appliedActions.clear(); |
| |
| if (cancelled) throw new OperationCancelledException(); |
| } |
| |
| // on OS X we need to update bundle timestamp to reset Info.plist caches. |
| toDir.setLastModified(System.currentTimeMillis()); |
| |
| return new ApplicationResult(appliedActions); |
| } |
| |
| public void revert(List<PatchAction> actions, final File backupDir, final File toDir, UpdaterUI ui) |
| throws OperationCancelledException, IOException { |
| Collections.reverse(actions); |
| forEach(actions, "Reverting...", ui, false, |
| new ActionsProcessor() { |
| public void forEach(PatchAction each) throws IOException { |
| each.revert(toDir, backupDir); |
| } |
| }); |
| } |
| |
| private static void forEach(List<PatchAction> actions, String title, UpdaterUI ui, boolean canBeCancelled, ActionsProcessor processor) |
| throws OperationCancelledException, IOException { |
| ui.startProcess(title); |
| if (canBeCancelled) ui.checkCancelled(); |
| |
| for (int i = 0; i < actions.size(); i++) { |
| PatchAction each = actions.get(i); |
| |
| ui.setStatus(each.getPath()); |
| if (canBeCancelled) ui.checkCancelled(); |
| |
| processor.forEach(each); |
| |
| ui.setProgress((i + 1) * 100 / actions.size()); |
| } |
| } |
| |
| public interface ActionsProcessor { |
| void forEach(PatchAction each) throws IOException; |
| } |
| |
| public static class ApplicationResult { |
| final boolean applied; |
| final List<PatchAction> appliedActions; |
| |
| public ApplicationResult(List<PatchAction> appliedActions) { |
| this.applied = !appliedActions.isEmpty(); |
| this.appliedActions = appliedActions; |
| } |
| } |
| } |