1 | /* |
---|
2 | * ZipCache.java |
---|
3 | * |
---|
4 | * Copyright (C) 2010, 2014 Mark Evenson |
---|
5 | * $Id: ZipCache.java 15494 2020-11-30 08:22:38Z mevenson $ |
---|
6 | * |
---|
7 | * This program is free software; you can redistribute it and/or |
---|
8 | * modify it under the terms of the GNU General Public License |
---|
9 | * as published by the Free Software Foundation; either version 2 |
---|
10 | * of the License, or (at your option) any later version. |
---|
11 | * |
---|
12 | * This program is distributed in the hope that it will be useful, |
---|
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
15 | * GNU General Public License for more details. |
---|
16 | * |
---|
17 | * You should have received a copy of the GNU General Public License |
---|
18 | * along with this program; if not, write to the Free Software |
---|
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
---|
20 | * |
---|
21 | * As a special exception, the copyright holders of this library give you |
---|
22 | * permission to link this library with independent modules to produce an |
---|
23 | * executable, regardless of the license terms of these independent |
---|
24 | * modules, and to copy and distribute the resulting executable under |
---|
25 | * terms of your choice, provided that you also meet, for each linked |
---|
26 | * independent module, the terms and conditions of the license of that |
---|
27 | * module. An independent module is a module which is not derived from |
---|
28 | * or based on this library. If you modify this library, you may extend |
---|
29 | * this exception to your version of the library, but you are not |
---|
30 | * obligated to do so. If you do not wish to do so, delete this |
---|
31 | * exception statement from your version. |
---|
32 | */ |
---|
33 | package org.armedbear.lisp; |
---|
34 | |
---|
35 | import java.io.ByteArrayInputStream; |
---|
36 | import java.io.ByteArrayOutputStream; |
---|
37 | import org.armedbear.lisp.util.HttpHead; |
---|
38 | import static org.armedbear.lisp.Lisp.*; |
---|
39 | |
---|
40 | import java.io.File; |
---|
41 | import java.io.IOException; |
---|
42 | import java.io.InputStream; |
---|
43 | import java.net.JarURLConnection; |
---|
44 | import java.net.MalformedURLException; |
---|
45 | import java.net.URL; |
---|
46 | import java.net.URLConnection; |
---|
47 | import java.text.ParsePosition; |
---|
48 | import java.text.SimpleDateFormat; |
---|
49 | import java.util.Date; |
---|
50 | import java.util.Enumeration; |
---|
51 | import java.util.Iterator; |
---|
52 | import java.util.HashMap; |
---|
53 | import java.util.Locale; |
---|
54 | import java.util.LinkedHashMap; |
---|
55 | import java.util.Map; |
---|
56 | import java.util.Set; |
---|
57 | import java.util.logging.Level; |
---|
58 | import java.util.logging.Logger; |
---|
59 | import java.util.zip.ZipException; |
---|
60 | import java.util.zip.ZipFile; |
---|
61 | import java.util.zip.ZipEntry; |
---|
62 | import java.util.zip.ZipInputStream; |
---|
63 | |
---|
64 | /** |
---|
65 | * A cache for all zip/jar file access by JarPathname that uses the last |
---|
66 | * modified time of the cached resource. |
---|
67 | * |
---|
68 | * If you run into problems with caching, use |
---|
69 | * (SYS::DISABLE-ZIP-CACHE). Once disabled, the caching cannot be |
---|
70 | * re-enabled. |
---|
71 | * |
---|
72 | */ |
---|
73 | public class ZipCache { |
---|
74 | public static final boolean checkZipFile(Pathname name) { |
---|
75 | InputStream input = name.getInputStream(); |
---|
76 | try { |
---|
77 | byte[] bytes = new byte[4]; |
---|
78 | int bytesRead = input.read(bytes); |
---|
79 | return bytesRead == 4 && bytes[0] == 80 && bytes[1] == 75 && bytes[2] == 3 && bytes[3] == 4; |
---|
80 | } catch (Throwable t) { |
---|
81 | // any error probably means 'no' |
---|
82 | return false; |
---|
83 | } finally { |
---|
84 | if (input != null) { |
---|
85 | try { |
---|
86 | input.close(); |
---|
87 | } catch (IOException e) { |
---|
88 | } // ignore exceptions |
---|
89 | } |
---|
90 | } |
---|
91 | } |
---|
92 | static InputStream getInputStream(ZipFile jarFile, String entryPath) { |
---|
93 | ZipEntry entry = jarFile.getEntry(entryPath); |
---|
94 | if (entry == null) { |
---|
95 | Debug.trace("Failed to find entry " + "'" + entryPath + "'" + " in " + "'" + jarFile.getName() + "'"); |
---|
96 | return null; |
---|
97 | } |
---|
98 | InputStream result = null; |
---|
99 | try { |
---|
100 | result = jarFile.getInputStream(entry); |
---|
101 | } catch (IOException e) { |
---|
102 | Debug.trace("Failed to open InputStream for " + "'" + entryPath + "'" + " in " + "'" + jarFile.getName() + "'"); |
---|
103 | return null; |
---|
104 | } |
---|
105 | return result; |
---|
106 | } |
---|
107 | public static ZipInputStream getZipInputStream(ZipFile zipfile, String entryName) { |
---|
108 | return ZipCache.getZipInputStream(zipfile, entryName, false); |
---|
109 | } |
---|
110 | public static ZipInputStream getZipInputStream(ZipFile zipfile, String entryName, boolean errorOnFailure) { |
---|
111 | ZipEntry zipEntry = zipfile.getEntry(entryName); |
---|
112 | ZipInputStream stream = null; |
---|
113 | try { |
---|
114 | stream = new ZipInputStream(zipfile.getInputStream(zipEntry)); |
---|
115 | } catch (IOException e) { |
---|
116 | if (errorOnFailure) { |
---|
117 | simple_error("Failed to open '" + entryName + "' in zipfile '" + zipfile + "': " + e.getMessage()); |
---|
118 | } |
---|
119 | return null; |
---|
120 | } |
---|
121 | return stream; |
---|
122 | } |
---|
123 | public static ByteArrayOutputStream readEntry(ZipInputStream stream) { |
---|
124 | ByteArrayOutputStream result = new ByteArrayOutputStream(); |
---|
125 | int count; |
---|
126 | byte[] buf = new byte[1024]; // What's a decent buffer size? |
---|
127 | try { |
---|
128 | while ((count = stream.read(buf, 0, buf.length)) != -1) { |
---|
129 | result.write(buf, 0, count); |
---|
130 | } |
---|
131 | } catch (IOException e) { |
---|
132 | Debug.trace("Failed to read entry from " + stream + ": " + e); |
---|
133 | return null; |
---|
134 | } |
---|
135 | return result; |
---|
136 | } |
---|
137 | public static ZipEntry getEntry(ZipInputStream zipInputStream, String entryName) { |
---|
138 | return ZipCache.getEntry(zipInputStream, entryName, false); |
---|
139 | } |
---|
140 | public static ZipEntry getEntry(ZipInputStream zipInputStream, String entryName, boolean errorOnFailure) { |
---|
141 | ZipEntry entry = null; |
---|
142 | do { |
---|
143 | try { |
---|
144 | entry = zipInputStream.getNextEntry(); |
---|
145 | } catch (IOException e) { |
---|
146 | if (errorOnFailure) { |
---|
147 | Lisp.error(new FileError("Failed to seek for " + "'" + entryName + "'" + " in " + zipInputStream.toString())); |
---|
148 | } |
---|
149 | return null; |
---|
150 | } |
---|
151 | } while (entry != null && !entry.getName().equals(entryName)); |
---|
152 | if (entry != null) { |
---|
153 | return entry; |
---|
154 | } |
---|
155 | if (errorOnFailure) { |
---|
156 | Lisp.error(new FileError("Failed to find " + "'" + entryName + "'" + " in " + zipInputStream.toString())); |
---|
157 | } |
---|
158 | return null; |
---|
159 | } |
---|
160 | |
---|
161 | public static InputStream getEntryAsInputStream(ZipInputStream zipInputStream, String entryName) { |
---|
162 | ZipEntry entry = getEntry(zipInputStream, entryName); |
---|
163 | ByteArrayOutputStream bytes = readEntry(zipInputStream); |
---|
164 | return new ByteArrayInputStream(bytes.toByteArray()); |
---|
165 | } |
---|
166 | |
---|
167 | public static InputStream getEntryAsInputStream(JarPathname archiveEntry) { |
---|
168 | JarPathname archiveJar = archiveEntry.getArchive(); |
---|
169 | Archive archive = ZipCache.getArchive(archiveJar); |
---|
170 | InputStream result = archive.getEntryAsInputStream(archiveEntry); |
---|
171 | if (result == null) { |
---|
172 | simple_error("Failed to get InputStream for ~a", archiveEntry); |
---|
173 | } |
---|
174 | return result; |
---|
175 | } |
---|
176 | |
---|
177 | // To make this thread safe, we should return a proxy for ZipFile |
---|
178 | // that keeps track of the number of outstanding references handed |
---|
179 | // out, not allowing ZipFile.close() to succeed until that count |
---|
180 | // has been reduced to 1 or the finalizer is executing. |
---|
181 | // Unfortunately the relatively simple strategy of extending |
---|
182 | // ZipFile via a CachedZipFile does not work because there is not |
---|
183 | // a null arg constructor for ZipFile. |
---|
184 | static HashMap<JarPathname, Archive> cache = new HashMap<JarPathname, Archive>(); |
---|
185 | |
---|
186 | abstract static public class Archive { |
---|
187 | JarPathname root; |
---|
188 | LinkedHashMap<JarPathname, ZipEntry> entries |
---|
189 | = new LinkedHashMap<JarPathname, ZipEntry>(); |
---|
190 | long lastModified; |
---|
191 | |
---|
192 | abstract InputStream getEntryAsInputStream(JarPathname entry); |
---|
193 | abstract ZipEntry getEntry(JarPathname entry); |
---|
194 | abstract void populateAllEntries(); |
---|
195 | abstract void close(); |
---|
196 | abstract long getLastModified(); |
---|
197 | } |
---|
198 | |
---|
199 | static public class ArchiveStream |
---|
200 | extends Archive |
---|
201 | { |
---|
202 | ZipInputStream source; |
---|
203 | ZipEntry rootEntry; |
---|
204 | |
---|
205 | public ArchiveStream(InputStream stream, JarPathname root, ZipEntry rootEntry) { |
---|
206 | if (!(stream instanceof ZipInputStream)) { |
---|
207 | this.source = new ZipInputStream(stream); |
---|
208 | } else { |
---|
209 | this.source = (ZipInputStream)stream; |
---|
210 | } |
---|
211 | this.root = root; |
---|
212 | this.rootEntry = rootEntry; |
---|
213 | this.lastModified = rootEntry.getTime(); // FIXME how to re-check time as modified? |
---|
214 | } |
---|
215 | |
---|
216 | // TODO wrap in a weak reference to allow JVM to possibly reclaim memory |
---|
217 | LinkedHashMap<JarPathname, ByteArrayOutputStream> contents |
---|
218 | = new LinkedHashMap<JarPathname, ByteArrayOutputStream>(); |
---|
219 | |
---|
220 | boolean populated = false; |
---|
221 | |
---|
222 | public InputStream getEntryAsInputStream(JarPathname entry) { |
---|
223 | if (!populated) { |
---|
224 | populateAllEntries(); |
---|
225 | } |
---|
226 | |
---|
227 | entry.setVersion(Keyword.NEWEST); |
---|
228 | ByteArrayOutputStream bytes = contents.get(entry); |
---|
229 | if (bytes != null) { |
---|
230 | return new ByteArrayInputStream(bytes.toByteArray()); |
---|
231 | } |
---|
232 | return null; |
---|
233 | } |
---|
234 | |
---|
235 | public ZipEntry getEntry(JarPathname entry) { |
---|
236 | if (!populated) { |
---|
237 | populateAllEntries(); |
---|
238 | } |
---|
239 | entry.setVersion(Keyword.NEWEST); |
---|
240 | ZipEntry result = entries.get(entry); |
---|
241 | return result; |
---|
242 | } |
---|
243 | |
---|
244 | void populateAllEntries() { |
---|
245 | if (populated) { |
---|
246 | return; |
---|
247 | } |
---|
248 | ZipEntry entry; |
---|
249 | try { |
---|
250 | while ((entry = source.getNextEntry()) != null) { |
---|
251 | String name = entry.getName(); |
---|
252 | JarPathname entryPathname |
---|
253 | = (JarPathname)JarPathname.createEntryFromJar(root, name); |
---|
254 | entries.put(entryPathname, entry); |
---|
255 | ByteArrayOutputStream bytes |
---|
256 | = readEntry(source); |
---|
257 | contents.put(entryPathname, bytes); |
---|
258 | } |
---|
259 | populated = true; |
---|
260 | } catch (IOException e) { |
---|
261 | simple_error("Failed to read entries from zip archive", root); |
---|
262 | } |
---|
263 | } |
---|
264 | |
---|
265 | void close () { |
---|
266 | if (source != null) { |
---|
267 | try { |
---|
268 | source.close(); |
---|
269 | } catch (IOException ex) { |
---|
270 | {} |
---|
271 | } |
---|
272 | } |
---|
273 | } |
---|
274 | |
---|
275 | long getLastModified() { |
---|
276 | return ((URLPathname)root.getRootJar()).getLastModified(); |
---|
277 | } |
---|
278 | } |
---|
279 | |
---|
280 | static public class ArchiveURL |
---|
281 | extends ArchiveFile |
---|
282 | { |
---|
283 | JarURLConnection connection; |
---|
284 | |
---|
285 | public ArchiveURL(JarPathname jar) |
---|
286 | throws java.io.IOException |
---|
287 | { |
---|
288 | String rootJarURLString = jar.getRootJarAsURLString(); |
---|
289 | URL rootJarURL = new URL(rootJarURLString); |
---|
290 | JarURLConnection jarConnection |
---|
291 | = (JarURLConnection) rootJarURL.openConnection(); |
---|
292 | |
---|
293 | this.root = jar; |
---|
294 | this.connection = jarConnection; |
---|
295 | this.file = (ZipFile)connection.getJarFile(); |
---|
296 | this.lastModified = connection.getLastModified(); |
---|
297 | } |
---|
298 | |
---|
299 | void close() { |
---|
300 | super.close(); |
---|
301 | // TODO: do we need to clean up from the connection? |
---|
302 | } |
---|
303 | } |
---|
304 | |
---|
305 | static public class ArchiveFile |
---|
306 | extends Archive |
---|
307 | { |
---|
308 | ZipFile file; |
---|
309 | |
---|
310 | ZipFile get() { return file;} |
---|
311 | |
---|
312 | ArchiveFile() {} |
---|
313 | |
---|
314 | public ArchiveFile(JarPathname jar) |
---|
315 | throws ZipException, IOException |
---|
316 | { |
---|
317 | File f = ((Pathname)jar.getRootJar()).getFile(); |
---|
318 | this.root = jar; |
---|
319 | this.file = new ZipFile(f); |
---|
320 | this.lastModified = f.lastModified(); |
---|
321 | } |
---|
322 | |
---|
323 | long getLastModified() { |
---|
324 | long result = 0; |
---|
325 | |
---|
326 | File f = ((Pathname)root.getRootJar()).getFile(); |
---|
327 | if (f != null) { |
---|
328 | result = f.lastModified(); |
---|
329 | } |
---|
330 | return result; |
---|
331 | } |
---|
332 | |
---|
333 | public ZipEntry getEntry(JarPathname entryPathname) { |
---|
334 | entryPathname.setVersion(Keyword.NEWEST); |
---|
335 | ZipEntry result = entries.get(entryPathname); |
---|
336 | if (result != null) { |
---|
337 | return result; |
---|
338 | } |
---|
339 | String entryPath = entryPathname.asEntryPath(); |
---|
340 | result = file.getEntry(entryPath); |
---|
341 | |
---|
342 | if (result == null) { |
---|
343 | return null; |
---|
344 | } |
---|
345 | |
---|
346 | // ZipFile.getEntry() will return directories when asked for |
---|
347 | // files. |
---|
348 | if (result.isDirectory() |
---|
349 | && (!entryPathname.getName().equals(NIL) |
---|
350 | || !entryPathname.getType().equals(NIL))) { |
---|
351 | return null; |
---|
352 | } |
---|
353 | |
---|
354 | entries.put(entryPathname, result); |
---|
355 | return result; |
---|
356 | } |
---|
357 | |
---|
358 | void populateAllEntries() { |
---|
359 | ZipFile f = file; |
---|
360 | if (f.size() == entries.size()) { |
---|
361 | return; |
---|
362 | } |
---|
363 | |
---|
364 | Enumeration<? extends ZipEntry> e = f.entries(); |
---|
365 | while (e.hasMoreElements()) { |
---|
366 | ZipEntry entry = e.nextElement(); |
---|
367 | String name = entry.getName(); |
---|
368 | JarPathname entryPathname |
---|
369 | = (JarPathname)JarPathname.createEntryFromJar(root, name); |
---|
370 | entries.put(entryPathname, entry); |
---|
371 | } |
---|
372 | } |
---|
373 | |
---|
374 | InputStream getEntryAsInputStream(JarPathname entry) { |
---|
375 | InputStream result = null; |
---|
376 | entry.setVersion(Keyword.NEWEST); |
---|
377 | ZipEntry zipEntry = getEntry(entry); |
---|
378 | |
---|
379 | try { |
---|
380 | result = file.getInputStream(zipEntry); |
---|
381 | } catch (IOException e) {} // FIXME how to signal a meaningful error? |
---|
382 | |
---|
383 | return result; |
---|
384 | } |
---|
385 | void close() { |
---|
386 | if (file != null) { |
---|
387 | try { |
---|
388 | file.close(); |
---|
389 | } catch (IOException e) {} |
---|
390 | |
---|
391 | } |
---|
392 | } |
---|
393 | } |
---|
394 | |
---|
395 | static boolean cacheEnabled = true; |
---|
396 | private final static Primitive DISABLE_ZIP_CACHE = new disable_zip_cache(); |
---|
397 | final static class disable_zip_cache extends Primitive { |
---|
398 | disable_zip_cache() { |
---|
399 | super("disable-zip-cache", PACKAGE_SYS, true, "", |
---|
400 | "Not currently implemented"); |
---|
401 | } |
---|
402 | @Override |
---|
403 | public LispObject execute() { |
---|
404 | return NIL; |
---|
405 | } |
---|
406 | } |
---|
407 | static public synchronized void disable() { |
---|
408 | cacheEnabled = false; |
---|
409 | cache.clear(); |
---|
410 | } |
---|
411 | |
---|
412 | synchronized public static LinkedHashMap<JarPathname,ZipEntry> getEntries(JarPathname jar) { |
---|
413 | Archive archive = getArchive(jar); |
---|
414 | archive.populateAllEntries(); // Very expensive for jars with large number of entries |
---|
415 | return archive.entries; |
---|
416 | } |
---|
417 | |
---|
418 | synchronized public static Iterator<Map.Entry<JarPathname,ZipEntry>> getEntriesIterator(JarPathname jar) { |
---|
419 | LinkedHashMap<JarPathname,ZipEntry> entries = getEntries(jar); |
---|
420 | Set<Map.Entry<JarPathname,ZipEntry>> set = entries.entrySet(); |
---|
421 | return set.iterator(); |
---|
422 | } |
---|
423 | |
---|
424 | static ZipEntry getZipEntry(JarPathname archiveEntry) { |
---|
425 | JarPathname archiveJar = archiveEntry.getArchive(); |
---|
426 | Archive zip = getArchive(archiveJar); |
---|
427 | ZipEntry entry = zip.getEntry(archiveEntry); |
---|
428 | return entry; |
---|
429 | } |
---|
430 | |
---|
431 | // ??? we assume that DIRECTORY, NAME, and TYPE components are NIL |
---|
432 | synchronized public static Archive getArchive(JarPathname jar) { |
---|
433 | jar.setVersion(Keyword.NEWEST); |
---|
434 | Archive result = cache.get(jar); |
---|
435 | if (result != null) { |
---|
436 | long time = result.getLastModified(); |
---|
437 | if (time != result.lastModified) { |
---|
438 | cache.remove(jar); |
---|
439 | return getArchive(jar); |
---|
440 | } |
---|
441 | return result; |
---|
442 | } |
---|
443 | Pathname rootJar = (Pathname) jar.getRootJar(); |
---|
444 | LispObject innerJars = jar.getJars().cdr(); |
---|
445 | |
---|
446 | if (!rootJar.isLocalFile()) { |
---|
447 | return getArchiveURL(jar); |
---|
448 | } |
---|
449 | |
---|
450 | if (innerJars.equals(NIL)) { |
---|
451 | return getArchiveFile(jar); |
---|
452 | } |
---|
453 | |
---|
454 | result = getArchiveStreamFromFile(jar); |
---|
455 | cache.put(result.root, result); |
---|
456 | |
---|
457 | JarPathname nextArchive = new JarPathname(); |
---|
458 | nextArchive |
---|
459 | .setDevice(new Cons(rootJar, |
---|
460 | new Cons(innerJars.car(), NIL))) |
---|
461 | .setDirectory(NIL) |
---|
462 | .setName(NIL) |
---|
463 | .setType(NIL) |
---|
464 | .setVersion(Keyword.NEWEST); |
---|
465 | |
---|
466 | innerJars = innerJars.cdr(); |
---|
467 | while (innerJars.car() != NIL) { |
---|
468 | Pathname nextJarArchive = (Pathname)innerJars.car(); |
---|
469 | |
---|
470 | JarPathname nextAsEntry = new JarPathname(); |
---|
471 | nextAsEntry |
---|
472 | .setDevice(nextArchive.getDevice()) |
---|
473 | .setDirectory(nextJarArchive.getDirectory()) |
---|
474 | .setName(nextJarArchive.getName()) |
---|
475 | .setType(nextJarArchive.getType()) |
---|
476 | .setVersion(Keyword.NEWEST); |
---|
477 | // FIXME |
---|
478 | // The pathnames for subsquent entries in a PATHNAME-JAR |
---|
479 | // are relative. Should they be? |
---|
480 | LispObject directories = nextAsEntry.getDirectory(); |
---|
481 | if ( !directories.equals(NIL) |
---|
482 | && directories.car().equals(Keyword.RELATIVE)) { |
---|
483 | directories = directories.cdr().push(Keyword.ABSOLUTE); |
---|
484 | nextAsEntry.setDirectory(directories); |
---|
485 | } |
---|
486 | |
---|
487 | nextArchive.setDevice(nextArchive.getDevice().reverse().push(nextJarArchive).reverse()); |
---|
488 | ArchiveStream stream = (ArchiveStream) result; |
---|
489 | |
---|
490 | ZipEntry entry = stream.getEntry(nextAsEntry); |
---|
491 | if (entry == null) { |
---|
492 | return null; |
---|
493 | } |
---|
494 | |
---|
495 | InputStream inputStream = stream.getEntryAsInputStream(nextAsEntry); |
---|
496 | if (inputStream == null) { |
---|
497 | return null; |
---|
498 | } |
---|
499 | stream = new ArchiveStream(inputStream, nextArchive, entry); |
---|
500 | result = stream; |
---|
501 | cache.put(nextArchive, result); |
---|
502 | |
---|
503 | innerJars = innerJars.cdr(); |
---|
504 | if (innerJars.cdr().equals(NIL) |
---|
505 | && (!jar.getDirectory().equals(NIL) |
---|
506 | && jar.getName().equals(NIL) |
---|
507 | && jar.getType().equals(NIL))) { |
---|
508 | simple_error("Currently unimplemented retrieval of an entry in a nested pathnames"); |
---|
509 | return (Archive)UNREACHED; |
---|
510 | } |
---|
511 | } |
---|
512 | return result; |
---|
513 | } |
---|
514 | |
---|
515 | static ArchiveStream getArchiveStreamFromFile(JarPathname p) { |
---|
516 | JarPathname innerArchiveAsEntry = JarPathname.archiveAsEntry(p); |
---|
517 | JarPathname root = new JarPathname(); |
---|
518 | root = (JarPathname)root.copyFrom(innerArchiveAsEntry); |
---|
519 | root |
---|
520 | .setDirectory(NIL) |
---|
521 | .setName(NIL) |
---|
522 | .setType(NIL) |
---|
523 | .setVersion(Keyword.NEWEST); |
---|
524 | |
---|
525 | ArchiveFile rootArchiveFile = (ArchiveFile)getArchiveFile(root); |
---|
526 | ZipEntry entry = rootArchiveFile.getEntry(innerArchiveAsEntry); |
---|
527 | if (entry == null) { |
---|
528 | return null; |
---|
529 | } |
---|
530 | InputStream inputStream = rootArchiveFile.getEntryAsInputStream(innerArchiveAsEntry); |
---|
531 | if (inputStream == null) { |
---|
532 | return null; |
---|
533 | } |
---|
534 | ArchiveStream result = new ArchiveStream(inputStream, p, entry); |
---|
535 | return result; |
---|
536 | } |
---|
537 | |
---|
538 | public static Archive getArchiveURL(JarPathname jar) { |
---|
539 | Pathname rootJar = (Pathname) jar.getRootJar(); |
---|
540 | jar.setVersion(Keyword.NEWEST); |
---|
541 | |
---|
542 | URL rootJarURL = null; |
---|
543 | try { |
---|
544 | ArchiveURL result = new ArchiveURL(jar); |
---|
545 | cache.put(jar, result); |
---|
546 | return result; |
---|
547 | } catch (MalformedURLException e) { |
---|
548 | simple_error("Failed to form root URL for ~a", jar); |
---|
549 | return (Archive)UNREACHED; |
---|
550 | } catch (IOException e) { |
---|
551 | simple_error("Failed to fetch ~a: ~a", jar, e); |
---|
552 | return (Archive)UNREACHED; |
---|
553 | } |
---|
554 | } |
---|
555 | |
---|
556 | static public Archive getArchiveFile(JarPathname jar) { |
---|
557 | jar.setVersion(Keyword.NEWEST); |
---|
558 | try { |
---|
559 | ArchiveFile result = new ArchiveFile(jar); |
---|
560 | cache.put(jar, result); |
---|
561 | return result; |
---|
562 | } catch (ZipException e) { |
---|
563 | error(new FileError("Failed to open local zip archive" |
---|
564 | + " because " + e, jar)); |
---|
565 | |
---|
566 | return (Archive)UNREACHED; |
---|
567 | } catch (IOException e) { |
---|
568 | error(new FileError("Failed to open local zip archive" |
---|
569 | + " because " + e, jar)); |
---|
570 | return (Archive)UNREACHED; |
---|
571 | } |
---|
572 | } |
---|
573 | |
---|
574 | // unused |
---|
575 | static void checkRemoteLastModified(ArchiveURL archive) { |
---|
576 | // Unfortunately, the Apple JDK under OS X doesn't do |
---|
577 | // HTTP HEAD requests, instead refetching the entire |
---|
578 | // resource, and I assume this is the case in all |
---|
579 | // Sun-derived JVMs. So, we use a custom HEAD |
---|
580 | // implementation only looking for Last-Modified |
---|
581 | // headers, which if we don't find, we give up and |
---|
582 | // refetch the resource. |
---|
583 | |
---|
584 | String dateString = null; |
---|
585 | |
---|
586 | String url = archive.root.getRootJarAsURLString(); |
---|
587 | |
---|
588 | try { |
---|
589 | dateString = HttpHead.get(url, "Last-Modified"); |
---|
590 | } catch (IOException ex) { |
---|
591 | Debug.trace(ex); |
---|
592 | } |
---|
593 | Date date = null; |
---|
594 | ParsePosition pos = new ParsePosition(0); |
---|
595 | |
---|
596 | final SimpleDateFormat ASCTIME |
---|
597 | = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", Locale.US); |
---|
598 | final SimpleDateFormat RFC_1036 |
---|
599 | = new SimpleDateFormat("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US); |
---|
600 | final SimpleDateFormat RFC_1123 |
---|
601 | = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); |
---|
602 | |
---|
603 | if (dateString != null) { |
---|
604 | date = RFC_1123.parse(dateString, pos); |
---|
605 | if (date == null) { |
---|
606 | date = RFC_1036.parse(dateString, pos); |
---|
607 | if (date == null) { |
---|
608 | date = ASCTIME.parse(dateString, pos); |
---|
609 | } |
---|
610 | } |
---|
611 | } |
---|
612 | |
---|
613 | // Replace older item in cache |
---|
614 | if (date == null || date.getTime() > archive.lastModified) { |
---|
615 | JarPathname root = archive.root; |
---|
616 | Archive entry = getArchiveURL(root); |
---|
617 | cache.put(root, entry); |
---|
618 | } |
---|
619 | if (date == null) { |
---|
620 | if (dateString == null) { |
---|
621 | Debug.trace("Failed to retrieve request header: " |
---|
622 | + url.toString()); |
---|
623 | } else { |
---|
624 | Debug.trace("Failed to parse Last-Modified date: " + |
---|
625 | dateString); |
---|
626 | } |
---|
627 | } |
---|
628 | } |
---|
629 | |
---|
630 | // ## clear-zip-cache => boolean |
---|
631 | private static final Primitive CLEAR_ZIP_CACHE = new clear_zip_cache(); |
---|
632 | private static class clear_zip_cache extends Primitive { |
---|
633 | clear_zip_cache() { |
---|
634 | super("clear-zip-cache", PACKAGE_SYS, true); |
---|
635 | } |
---|
636 | @Override |
---|
637 | public LispObject execute() { |
---|
638 | int size = cache.size(); |
---|
639 | cache.clear(); |
---|
640 | return size == 0 ? NIL : T; |
---|
641 | } |
---|
642 | } |
---|
643 | |
---|
644 | // ## remove-zip-cache-entry pathname => boolean |
---|
645 | private static final Primitive REMOVE_ZIP_CACHE_ENTRY = new remove_zip_cache_entry(); |
---|
646 | private static class remove_zip_cache_entry extends Primitive { |
---|
647 | remove_zip_cache_entry() { |
---|
648 | super("remove-zip-cache-entry", PACKAGE_SYS, true, "pathname"); |
---|
649 | } |
---|
650 | @Override |
---|
651 | public LispObject execute(LispObject arg) { |
---|
652 | Pathname p = coerceToPathname(arg); |
---|
653 | boolean result = false; |
---|
654 | if (p instanceof JarPathname) { |
---|
655 | result = ZipCache.remove((JarPathname)p); |
---|
656 | } |
---|
657 | return result ? T : NIL; |
---|
658 | } |
---|
659 | } |
---|
660 | |
---|
661 | synchronized public static boolean remove(Pathname pathname) { |
---|
662 | JarPathname p = JarPathname.createFromPathname(pathname); |
---|
663 | return remove(p); |
---|
664 | } |
---|
665 | |
---|
666 | synchronized public static boolean remove(JarPathname p) { |
---|
667 | p.setVersion(Keyword.NEWEST); |
---|
668 | Archive archive = cache.get(p); |
---|
669 | if (archive != null) { |
---|
670 | archive.close(); |
---|
671 | cache.remove(p); |
---|
672 | return true; |
---|
673 | } |
---|
674 | return false; |
---|
675 | } |
---|
676 | } |
---|
677 | |
---|