2004/01/19 00:16:54
[org.ibex.core.git] / src / org / xwt / Stream.java
1 // Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
2 package org.xwt;
3
4 import java.io.*;
5 import java.util.*;
6 import java.util.zip.*;
7 import org.xwt.js.*;
8 import org.xwt.util.*;
9 import org.xwt.translators.MSPack;
10 import org.bouncycastle.util.encoders.Base64;
11
12 /**
13  *   Essentiall an InputStream "factory".  You can repeatedly ask a
14  *   Stream for an InputStream, and each InputStream you get back will
15  *   be totally independent of the others (ie separate stream position
16  *   and state) although they draw from the same data source.
17  */
18 public abstract class Stream extends JS.Cloneable {
19
20     // Public Interface //////////////////////////////////////////////////////////////////////////////
21
22     public static InputStream getInputStream(Object js) throws IOException { return ((Stream)((JS)js).unclone()).getInputStream();}
23     public static class NotCacheableException extends Exception { }
24
25     // streams are "sealed" by default to prevent accidental object leakage
26     public void put(Object key, Object val) throws JSExn { }
27     public Object get(Object key) throws JSExn { return null; }
28
29     // Private Interface //////////////////////////////////////////////////////////////////////////////
30
31     protected abstract InputStream getInputStream() throws IOException;
32     protected String getCacheKey() throws NotCacheableException { throw new NotCacheableException(); }
33
34     /** HTTP or HTTPS resource */
35     public static class HTTP extends Stream {
36         private String url;
37         HTTP(String url) { while (url.endsWith("/")) url = url.substring(0, url.length() - 1); this.url = url; }
38         public Object get(Object key) throws JSExn { return new HTTP(url + "/" + (String)key); }
39         public String getCacheKey(Vec path) throws NotCacheableException { return url; }
40         public InputStream getInputStream() throws IOException { return new org.xwt.HTTP(url).GET(); }
41     }
42
43     /** byte arrays */
44     public static class ByteArray extends Stream {
45         private byte[] bytes;
46         private String cacheKey;
47         ByteArray(byte[] bytes, String cacheKey) { this.bytes = bytes; this.cacheKey = cacheKey; }
48         public String getCacheKey() throws NotCacheableException {
49             if (cacheKey == null) throw new NotCacheableException(); return cacheKey; }
50         public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(bytes); }
51     }
52
53     /** a file */
54     public static class File extends Stream {
55         private String path;
56         File(String path) { this.path = path; }
57         public String toString() { return "file:" + path; }
58         public String getCacheKey() throws NotCacheableException { throw new NotCacheableException(); /* already on disk */ }
59         public InputStream getInputStream() throws IOException { return new FileInputStream(path); }
60         public Object get(Object key) throws JSExn { return new File(path + java.io.File.separatorChar + (String)key); }
61     }
62
63     /** "unwrap" a Zip archive */
64     public static class Zip extends Stream {
65         private Stream parent;
66         private String path;
67         Zip(Stream parent) { this(parent, null); }
68         Zip(Stream parent, String path) {
69             while(path != null && path.startsWith("/")) path = path.substring(1);
70             this.parent = parent;
71             this.path = path;
72         }
73         public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "!zip:"; }
74         public Object get(Object key) throws JSExn { return new Zip(parent, path==null?(String)key:path+'/'+(String)key); }
75         public InputStream getInputStream() throws IOException {
76             InputStream pis = parent.getInputStream();
77             ZipInputStream zis = new ZipInputStream(pis);
78             ZipEntry ze = zis.getNextEntry();
79             while(ze != null && !ze.getName().equals(path)) ze = zis.getNextEntry();
80             if (ze == null) throw new IOException("requested file (" + path + ") not found in archive");
81             return new KnownLength.KnownLengthInputStream(zis, (int)ze.getSize());
82         }
83     }
84
85     /** "unwrap" a Cab archive */
86     public static class Cab extends Stream {
87         private Stream parent;
88         private String path;
89         Cab(Stream parent) { this(parent, null); }
90         Cab(Stream parent, String path) { this.parent = parent; this.path = path; }
91         public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "!cab:"; }
92         public Object get(Object key) throws JSExn { return new Cab(parent, path==null?(String)key:path+'/'+(String)key); }
93         public InputStream getInputStream() throws IOException { return new MSPack(parent.getInputStream()).getInputStream(path); }
94     }
95
96     /** the Builtin resource */
97     public static class Builtin extends Stream {
98         public Builtin() { };
99         public String getCacheKey() throws NotCacheableException { throw new NotCacheableException(); }
100         public InputStream getInputStream() throws IOException { return Platform.getBuiltinInputStream(); }
101     }
102
103     /** shadow resource which replaces the graft */
104     public static class ProgressWatcher extends Stream {
105         final Stream watchee;
106         JS callback;
107         ProgressWatcher(Stream watchee, JS callback) { this.watchee = watchee; this.callback = callback; }
108         public String getCacheKey() throws NotCacheableException { return watchee.getCacheKey(); }
109         public InputStream getInputStream() throws IOException {
110             final InputStream is = watchee.getInputStream();
111             return new FilterInputStream(is) {
112                     int bytesDownloaded = 0;
113                     public int read() throws IOException {
114                         int ret = super.read();
115                         if (ret != -1) bytesDownloaded++;
116                         return ret;
117                     }
118                     public int read(byte[] b, int off, int len) throws IOException {
119                         int ret = super.read(b, off, len);
120                         if (ret != 1) bytesDownloaded += ret;
121                         Scheduler.add(new Scheduler.Task() { public void perform() throws Exception {
122                             callback.call(N(bytesDownloaded),
123                                           N(is instanceof KnownLength ? ((KnownLength)is).getLength() : 0), null, null, 2);
124                         } });
125                         return ret;
126                     }
127                 };
128         }
129     }
130
131     /** subclass from this if you want a CachedInputStream for each path */
132     public static class CachedStream extends Stream {
133         private Stream parent;
134         private boolean disk = false;
135         private String key;
136         public String getCacheKey() throws NotCacheableException { return key; }
137         CachedInputStream cis = null;
138         public CachedStream(Stream p, String s, boolean d) throws NotCacheableException {
139             this.parent = p; this.disk = d; this.key = p.getCacheKey();
140         }
141         public InputStream getInputStream() throws IOException {
142             if (cis != null) return cis.getInputStream();
143             if (!disk) {
144                 cis = new CachedInputStream(parent.getInputStream());
145             } else {
146                 java.io.File f = LocalStorage.Cache.getCacheFileForKey(key);
147                 if (f.exists()) return new FileInputStream(f);
148                 cis = new CachedInputStream(parent.getInputStream(), f);
149             }
150             return cis.getInputStream();
151         }
152     }
153 }