JAL-3130 adapted getdown src. attempt 2. first attempt failed due to cp'ed .git files
[jalview.git] / getdown / src / getdown / core / src / main / java / com / threerings / getdown / tools / Differ.java
1 //
2 // Getdown - application installer, patcher and launcher
3 // Copyright (C) 2004-2018 Getdown authors
4 // https://github.com/threerings/getdown/blob/master/LICENSE
5
6 package com.threerings.getdown.tools;
7
8 import java.io.BufferedOutputStream;
9 import java.io.File;
10 import java.io.FileInputStream;
11 import java.io.FileOutputStream;
12 import java.io.IOException;
13 import java.io.InputStream;
14
15 import java.util.ArrayList;
16 import java.util.Enumeration;
17 import java.util.jar.JarEntry;
18 import java.util.jar.JarFile;
19 import java.util.jar.JarOutputStream;
20 import java.util.zip.ZipEntry;
21
22 import java.security.MessageDigest;
23
24 import com.threerings.getdown.data.Application;
25 import com.threerings.getdown.data.Digest;
26 import com.threerings.getdown.data.EnvConfig;
27 import com.threerings.getdown.data.Resource;
28 import com.threerings.getdown.util.FileUtil;
29 import com.threerings.getdown.util.StreamUtil;
30
31 /**
32  * Generates patch files between two particular revisions of an
33  * application. The differences between all the files in the two
34  * revisions are bundled into a single patch file which is placed into the
35  * target version directory.
36  */
37 public class Differ
38 {
39     /**
40      * Creates a single patch file that contains the differences between
41      * the two specified application directories. The patch file will be
42      * created in the <code>nvdir</code> directory with name
43      * <code>patchV.dat</code> where V is the old application version.
44      */
45     public void createDiff (File nvdir, File ovdir, boolean verbose)
46         throws IOException
47     {
48         // sanity check
49         String nvers = nvdir.getName();
50         String overs = ovdir.getName();
51         try {
52             if (Long.parseLong(nvers) <= Long.parseLong(overs)) {
53                 String err = "New version (" + nvers + ") must be greater " +
54                     "than old version (" + overs + ").";
55                 throw new IOException(err);
56             }
57         } catch (NumberFormatException nfe) {
58             throw new IOException("Non-numeric versions? [nvers=" + nvers +
59                                   ", overs=" + overs + "].");
60         }
61
62         Application oapp = new Application(new EnvConfig(ovdir));
63         oapp.init(false);
64         ArrayList<Resource> orsrcs = new ArrayList<>();
65         orsrcs.addAll(oapp.getCodeResources());
66         orsrcs.addAll(oapp.getResources());
67
68         Application napp = new Application(new EnvConfig(nvdir));
69         napp.init(false);
70         ArrayList<Resource> nrsrcs = new ArrayList<>();
71         nrsrcs.addAll(napp.getCodeResources());
72         nrsrcs.addAll(napp.getResources());
73
74         // first create a patch for the main application
75         File patch = new File(nvdir, "patch" + overs + ".dat");
76         createPatch(patch, orsrcs, nrsrcs, verbose);
77
78         // next create patches for any auxiliary resource groups
79         for (Application.AuxGroup ag : napp.getAuxGroups()) {
80             orsrcs = new ArrayList<>();
81             Application.AuxGroup oag = oapp.getAuxGroup(ag.name);
82             if (oag != null) {
83                 orsrcs.addAll(oag.codes);
84                 orsrcs.addAll(oag.rsrcs);
85             }
86             nrsrcs = new ArrayList<>();
87             nrsrcs.addAll(ag.codes);
88             nrsrcs.addAll(ag.rsrcs);
89             patch = new File(nvdir, "patch-" + ag.name + overs + ".dat");
90             createPatch(patch, orsrcs, nrsrcs, verbose);
91         }
92     }
93
94     protected void createPatch (File patch, ArrayList<Resource> orsrcs,
95                                 ArrayList<Resource> nrsrcs, boolean verbose)
96         throws IOException
97     {
98         int version = Digest.VERSION;
99         MessageDigest md = Digest.getMessageDigest(version);
100         try (FileOutputStream fos = new FileOutputStream(patch);
101              BufferedOutputStream buffered = new BufferedOutputStream(fos);
102              JarOutputStream jout = new JarOutputStream(buffered)) {
103
104             // for each file in the new application, it either already exists
105             // in the old application, or it is new
106             for (Resource rsrc : nrsrcs) {
107                 int oidx = orsrcs.indexOf(rsrc);
108                 Resource orsrc = (oidx == -1) ? null : orsrcs.remove(oidx);
109                 if (orsrc != null) {
110                     // first see if they are the same
111                     String odig = orsrc.computeDigest(version, md, null);
112                     String ndig = rsrc.computeDigest(version, md, null);
113                     if (odig.equals(ndig)) {
114                         if (verbose) {
115                             System.out.println("Unchanged: " + rsrc.getPath());
116                         }
117                         // by leaving it out, it will be left as is during the
118                         // patching process
119                         continue;
120                     }
121
122                     // otherwise potentially create a jar diff
123                     if (rsrc.getPath().endsWith(".jar")) {
124                         if (verbose) {
125                             System.out.println("JarDiff: " + rsrc.getPath());
126                         }
127                         // here's a juicy one: JarDiff blindly pulls ZipEntry
128                         // objects out of one jar file and stuffs them into
129                         // another without clearing out things like the
130                         // compressed size, so if, for whatever reason (like
131                         // different JRE versions or phase of the moon) the
132                         // compressed size in the old jar file is different
133                         // than the compressed size generated when creating the
134                         // jardiff jar file, ZipOutputStream will choke and
135                         // we'll be hosed; so we recreate the jar files in
136                         // their entirety before running jardiff on 'em
137                         File otemp = rebuildJar(orsrc.getLocal());
138                         File temp = rebuildJar(rsrc.getLocal());
139                         jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.PATCH));
140                         jarDiff(otemp, temp, jout);
141                         FileUtil.deleteHarder(otemp);
142                         FileUtil.deleteHarder(temp);
143                         continue;
144                     }
145                 }
146
147                 if (verbose) {
148                     System.out.println("Addition: " + rsrc.getPath());
149                 }
150                 jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.CREATE));
151                 pipe(rsrc.getLocal(), jout);
152             }
153
154             // now any file remaining in orsrcs needs to be removed
155             for (Resource rsrc : orsrcs) {
156                 // add an entry with the resource name and the deletion suffix
157                 if (verbose) {
158                     System.out.println("Removal: " + rsrc.getPath());
159                 }
160                 jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.DELETE));
161             }
162
163             System.out.println("Created patch file: " + patch);
164
165         } catch (IOException ioe) {
166             FileUtil.deleteHarder(patch);
167             throw ioe;
168         }
169     }
170
171     protected File rebuildJar (File target)
172         throws IOException
173     {
174         File temp = File.createTempFile("differ", "jar");
175         try (JarFile jar = new JarFile(target);
176              FileOutputStream tempFos = new FileOutputStream(temp);
177              BufferedOutputStream tempBos = new BufferedOutputStream(tempFos);
178              JarOutputStream jout = new JarOutputStream(tempBos)) {
179             byte[] buffer = new byte[4096];
180             for (Enumeration< JarEntry > iter = jar.entries(); iter.hasMoreElements();) {
181                 JarEntry entry = iter.nextElement();
182                 entry.setCompressedSize(-1);
183                 jout.putNextEntry(entry);
184                 try (InputStream in = jar.getInputStream(entry)) {
185                     int size = in.read(buffer);
186                     while (size != -1) {
187                         jout.write(buffer, 0, size);
188                         size = in.read(buffer);
189                     }
190                 }
191             }
192         }
193         return temp;
194     }
195
196     protected void jarDiff (File ofile, File nfile, JarOutputStream jout)
197         throws IOException
198     {
199         JarDiff.createPatch(ofile.getPath(), nfile.getPath(), jout, false);
200     }
201
202     public static void main (String[] args)
203     {
204         if (args.length < 2) {
205             System.err.println(
206                 "Usage: Differ [-verbose] new_vers_dir old_vers_dir");
207             System.exit(255);
208         }
209         Differ differ = new Differ();
210         boolean verbose = false;
211         int aidx = 0;
212         if (args[0].equals("-verbose")) {
213             verbose = true;
214             aidx++;
215         }
216         try {
217             differ.createDiff(new File(args[aidx++]),
218                               new File(args[aidx++]), verbose);
219         } catch (IOException ioe) {
220             System.err.println("Error: " + ioe.getMessage());
221             System.exit(255);
222         }
223     }
224
225     protected static void pipe (File file, JarOutputStream jout)
226         throws IOException
227     {
228         try (FileInputStream fin = new FileInputStream(file)) {
229             StreamUtil.copy(fin, jout);
230         }
231     }
232 }