blob: 7741b813318a5696bd8ca5914a70901cc68bfb2a [file] [log] [blame]
package com.intellij.updater;
import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class UpdateZipAction extends BaseUpdateAction {
Set<String> myFilesToCreate;
Set<String> myFilesToUpdate;
Set<String> myFilesToDelete;
public UpdateZipAction(String path, long checksum) {
super(path, checksum);
}
// test support
public UpdateZipAction(String path,
Collection<String> filesToCreate,
Collection<String> filesToUpdate,
Collection<String> filesToDelete,
long checksum) {
super(path, checksum);
myFilesToCreate = new HashSet<String>(filesToCreate);
myFilesToUpdate = new HashSet<String>(filesToUpdate);
myFilesToDelete = new HashSet<String>(filesToDelete);
}
public UpdateZipAction(DataInputStream in) throws IOException {
super(in);
int count = in.readInt();
myFilesToCreate = new HashSet<String>(count);
while (count-- > 0) {
myFilesToCreate.add(in.readUTF());
}
count = in.readInt();
myFilesToUpdate = new HashSet<String>(count);
while (count-- > 0) {
myFilesToUpdate.add(in.readUTF());
}
count = in.readInt();
myFilesToDelete = new HashSet<String>(count);
while (count-- > 0) {
myFilesToDelete.add(in.readUTF());
}
}
@Override
public void write(DataOutputStream out) throws IOException {
super.write(out);
out.writeInt(myFilesToCreate.size());
for (String each : myFilesToCreate) {
out.writeUTF(each);
}
out.writeInt(myFilesToUpdate.size());
for (String each : myFilesToUpdate) {
out.writeUTF(each);
}
out.writeInt(myFilesToDelete.size());
for (String each : myFilesToDelete) {
out.writeUTF(each);
}
}
@Override
protected boolean doCalculate(File olderFile, File newerFile) throws IOException {
final Map<String, Long> oldCheckSums = new HashMap<String, Long>();
final Map<String, Long> newCheckSums = new HashMap<String, Long>();
processZipFile(olderFile, new Processor() {
public void process(ZipEntry entry, InputStream in) throws IOException {
oldCheckSums.put(entry.getName(), Digester.digestStream(in));
}
});
processZipFile(newerFile, new Processor() {
public void process(ZipEntry entry, InputStream in) throws IOException {
newCheckSums.put(entry.getName(), Digester.digestStream(in));
}
});
DiffCalculator.Result diff = DiffCalculator.calculate(oldCheckSums, newCheckSums);
myFilesToCreate = diff.filesToCreate;
myFilesToUpdate = diff.filesToUpdate.keySet();
myFilesToDelete = diff.filesToDelete.keySet();
return !(myFilesToCreate.isEmpty() && myFilesToUpdate.isEmpty() && myFilesToDelete.isEmpty());
}
@Override
public void doBuildPatchFile(final File olderFile, final File newerFile, final ZipOutputStream patchOutput) throws IOException {
try {
//noinspection IOResourceOpenedButNotSafelyClosed
new ZipFile(newerFile).close();
}
catch (IOException e) {
Runner.logger.error("Corrupted target file: " + newerFile);
Runner.printStackTrace(e);
throw new IOException("Corrupted target file: " + newerFile, e);
}
final Set<String> filesToProcess = new HashSet<String>(myFilesToCreate);
filesToProcess.addAll(myFilesToUpdate);
if (filesToProcess.isEmpty()) return;
final ZipFile olderZip;
try {
olderZip = new ZipFile(olderFile);
}
catch (IOException e) {
Runner.logger.error("Corrupted source file: " + olderFile);
Runner.printStackTrace(e);
throw new IOException("Corrupted source file: " + olderFile, e);
}
try {
processZipFile(newerFile, new Processor() {
public void process(ZipEntry newerEntry, InputStream newerEntryIn) throws IOException {
if (newerEntry.isDirectory()) return;
String name = newerEntry.getName();
if (!filesToProcess.contains(name)) return;
try {
patchOutput.putNextEntry(new ZipEntry(myPath + "/" + name));
InputStream olderEntryIn = Utils.findEntryInputStream(olderZip, name);
if (olderEntryIn == null) {
Utils.copyStream(newerEntryIn, patchOutput);
}
else {
writeDiff(olderEntryIn, newerEntryIn, patchOutput);
}
patchOutput.closeEntry();
}
catch (IOException e) {
Runner.logger.error("Error building patch for .zip entry " + name);
Runner.printStackTrace(e);
throw new IOException("Error building patch for .zip entry " + name, e);
}
}
});
}
finally {
olderZip.close();
}
}
protected void doApply(final ZipFile patchFile, 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() {
public void process(ZipEntry entry, InputStream in) throws IOException {
String path = entry.getName();
if (myFilesToDelete.contains(path)) return;
if (myFilesToUpdate.contains(path)) {
OutputStream entryOut = out.zipStream(path);
try {
applyDiff(Utils.findEntryInputStream(patchFile, myPath + "/" + path), in, entryOut);
}
finally {
entryOut.close();
}
}
else {
out.zipEntry(entry, in);
}
}
});
for (String each : myFilesToCreate) {
InputStream in = Utils.getEntryInputStream(patchFile, myPath + "/" + each);
try {
out.zipEntry(each, in);
}
finally {
in.close();
}
}
out.finish();
}
finally {
fileOut.close();
}
replaceUpdated(temp, toFile);
}
private static void processZipFile(File file, Processor processor) throws IOException {
ZipInputStream in = new ZipInputStream(new FileInputStream(file));
try {
ZipEntry inEntry;
Set<String> processed = new HashSet<String>();
while ((inEntry = in.getNextEntry()) != null) {
if (inEntry.isDirectory()) continue;
if (processed.contains(inEntry.getName())) {
throw new IOException("Duplicate entry '" + inEntry.getName() + "' in " + file.getPath());
}
//noinspection IOResourceOpenedButNotSafelyClosed
processor.process(inEntry, new BufferedInputStream(in));
processed.add(inEntry.getName());
}
}
finally {
in.close();
}
}
private interface Processor {
void process(ZipEntry entry, InputStream in) throws IOException;
}
@Override
public String toString() {
return super.toString() + myFilesToCreate + " " + myFilesToUpdate + " " + myFilesToDelete;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
UpdateZipAction that = (UpdateZipAction)o;
if (myFilesToCreate != null ? !myFilesToCreate.equals(that.myFilesToCreate) : that.myFilesToCreate != null) return false;
if (myFilesToUpdate != null ? !myFilesToUpdate.equals(that.myFilesToUpdate) : that.myFilesToUpdate != null) return false;
if (myFilesToDelete != null ? !myFilesToDelete.equals(that.myFilesToDelete) : that.myFilesToDelete != null) return false;
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (myFilesToCreate != null ? myFilesToCreate.hashCode() : 0);
result = 31 * result + (myFilesToUpdate != null ? myFilesToUpdate.hashCode() : 0);
result = 31 * result + (myFilesToDelete != null ? myFilesToDelete.hashCode() : 0);
return result;
}
}