Commit | Line | Data |
---|---|---|
5d99f865 FT |
1 | package dolda.jsvc.store; |
2 | ||
3 | import dolda.jsvc.*; | |
4 | import dolda.jsvc.util.*; | |
5 | import java.io.*; | |
6 | import java.util.*; | |
7 | import java.security.*; | |
8 | ||
9 | class FileStore extends Store { | |
10 | private final java.io.File base; | |
11 | private static final int smbuflimit; | |
12 | private static int txserial = 0; | |
13 | ||
14 | static { | |
15 | int res; /* Java is stupid, as usual... */ | |
16 | try { | |
17 | String p = System.getProperty("dolda.jsvc.store.smallbuf"); | |
18 | if(p != null) | |
19 | res = Integer.parseInt(p); | |
20 | else | |
21 | res = 65536; | |
22 | } catch(SecurityException e) { | |
23 | res = 65536; | |
24 | } | |
25 | smbuflimit = res; | |
26 | } | |
27 | ||
28 | private FileStore(Package pkg, java.io.File root) { | |
29 | super(pkg); | |
30 | String nm = pkg.getName(); | |
31 | java.io.File base = root; | |
32 | int p = 0; | |
33 | int p2; | |
34 | while((p2 = nm.indexOf('.', p)) >= 0) { | |
35 | base = new java.io.File(base, nm.substring(p, p2)); | |
36 | p = p2 + 1; | |
37 | } | |
38 | this.base = new java.io.File(base, nm.substring(p)); | |
39 | AccessController.doPrivileged(new PrivilegedAction<Object>() { | |
40 | public Object run() { | |
41 | if(!FileStore.this.base.mkdirs()) | |
42 | throw(new RuntimeException("Could not create store directory (Java won't tell me why)")); | |
43 | return(null); | |
44 | } | |
45 | }); | |
46 | } | |
47 | ||
48 | private static String mangle(String in) { | |
49 | byte[] bytes = in.getBytes(Misc.utf8); | |
50 | StringBuilder buf = new StringBuilder(); | |
51 | for(int i = 0; i < bytes.length; i++) { | |
52 | byte b = bytes[i]; | |
53 | if(((b >= '0') && (b <= '9')) || ((b >= 'A') && (b <= 'Z')) || ((b >= 'a') && (b <= 'z'))) { | |
54 | buf.append((char)b); | |
55 | } else { | |
56 | buf.append('_'); | |
57 | buf.append(Misc.int2hex((b & 0xf0) >> 4, true)); | |
58 | buf.append(Misc.int2hex(b & 0x0f, true)); | |
59 | } | |
60 | } | |
61 | return(buf.toString()); | |
62 | } | |
63 | ||
64 | private static String demangle(String in) { | |
65 | ByteArrayOutputStream buf = new ByteArrayOutputStream(); | |
66 | for(int i = 0; i < in.length(); i++) { | |
67 | char c = in.charAt(i); | |
68 | if(c == '_') { | |
69 | char d1 = in.charAt(i + 1); | |
70 | char d2 = in.charAt(i + 2); | |
71 | i += 2; | |
72 | buf.write((byte)((Misc.hex2int(d1) << 4) | Misc.hex2int(d2))); | |
73 | } else { | |
74 | if(c >= 256) | |
75 | throw(new RuntimeException("Invalid filename in store")); | |
76 | buf.write(c); | |
77 | } | |
78 | } | |
79 | byte[] bytes = buf.toByteArray(); | |
80 | return(new String(bytes, Misc.utf8)); | |
81 | } | |
82 | ||
83 | private class RFile implements File { | |
84 | private final java.io.File fs; | |
85 | private final String name; | |
86 | ||
87 | private class TXStream extends OutputStream { | |
88 | private FileOutputStream buf = null; | |
89 | private java.io.File tmpfile; | |
90 | private boolean closed = false; | |
91 | ||
92 | private void init() throws IOException { | |
93 | try { | |
94 | buf = AccessController.doPrivileged(new PrivilegedExceptionAction<FileOutputStream>() { | |
95 | public FileOutputStream run() throws IOException { | |
96 | synchronized(RFile.class) { | |
97 | int serial = txserial++; | |
98 | tmpfile = new java.io.File(fs.getPath() + ".new." + txserial); | |
99 | if(tmpfile.exists()) { | |
100 | if(!tmpfile.delete()) | |
101 | throw(new IOException("Could not delete previous temporary file (Java won't tell my why)")); | |
102 | } | |
103 | return(new FileOutputStream(tmpfile)); | |
104 | } | |
105 | } | |
106 | }); | |
107 | } catch(PrivilegedActionException e) { | |
108 | throw((IOException)e.getCause()); | |
109 | } | |
110 | } | |
111 | ||
112 | public void write(byte[] b, int off, int len) throws IOException { | |
113 | if(closed) | |
114 | throw(new IOException("This file has already been committed")); | |
115 | if(buf == null) | |
116 | init(); | |
117 | buf.write(b, off, len); | |
118 | } | |
119 | ||
120 | public void write(byte[] b) throws IOException { | |
121 | if(closed) | |
122 | throw(new IOException("This file has already been committed")); | |
123 | if(buf == null) | |
124 | init(); | |
125 | buf.write(b); | |
126 | } | |
127 | ||
128 | public void write(int b) throws IOException { | |
129 | if(closed) | |
130 | throw(new IOException("This file has already been committed")); | |
131 | if(buf == null) | |
132 | init(); | |
133 | buf.write(b); | |
134 | } | |
135 | ||
136 | public void flush() throws IOException { | |
137 | if(buf == null) | |
138 | init(); | |
139 | buf.flush(); | |
140 | } | |
141 | ||
142 | public void close() throws IOException { | |
143 | flush(); | |
144 | closed = true; | |
145 | buf.close(); | |
146 | try { | |
147 | AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { | |
148 | public Object run() throws IOException { | |
149 | if(!tmpfile.renameTo(fs)) { | |
150 | fs.delete(); | |
151 | if(!tmpfile.renameTo(fs)) | |
152 | throw(new IOException("Could not replace previous file contents with new (Java won't tell me why)")); | |
153 | } | |
154 | return(null); | |
155 | } | |
156 | }); | |
157 | } catch(PrivilegedActionException e) { | |
158 | throw((IOException)e.getCause()); | |
159 | } | |
160 | } | |
161 | } | |
162 | ||
163 | private RFile(java.io.File fs, String name) { | |
164 | this.fs = fs; | |
165 | this.name = name; | |
166 | } | |
167 | ||
168 | public String name() { | |
169 | return(name); | |
170 | } | |
171 | ||
172 | public InputStream read() { | |
173 | return(AccessController.doPrivileged(new PrivilegedAction<InputStream>() { | |
174 | public InputStream run() { | |
175 | try { | |
176 | return(new FileInputStream(fs)); | |
177 | } catch(FileNotFoundException e) { | |
178 | return(null); | |
179 | } | |
180 | } | |
181 | })); | |
182 | } | |
183 | ||
184 | public OutputStream store() { | |
185 | return(new TXStream()); | |
186 | } | |
187 | ||
188 | public long mtime() { | |
189 | return(AccessController.doPrivileged(new PrivilegedAction<Long>() { | |
190 | public Long run() { | |
191 | return(fs.lastModified()); | |
192 | } | |
193 | })); | |
194 | } | |
195 | ||
196 | public void remove() { | |
197 | AccessController.doPrivileged(new PrivilegedAction<Object>() { | |
198 | public InputStream run() { | |
199 | if(!fs.delete()) | |
200 | throw(new RuntimeException("Could not delete the file " + fs.getPath() + " (Java won't tell me why)")); | |
201 | return(null); | |
202 | } | |
203 | }); | |
204 | } | |
205 | } | |
206 | ||
207 | public File get(String name) { | |
208 | return(new RFile(new java.io.File(base, mangle(name)), name)); | |
209 | } | |
210 | ||
211 | public Iterator<File> iterator() { | |
212 | final java.io.File[] ls = base.listFiles(); | |
213 | return(new Iterator<File>() { | |
214 | private int i = 0; | |
215 | private File cur = null; | |
216 | ||
217 | public boolean hasNext() { | |
218 | return(i < ls.length); | |
219 | } | |
220 | ||
221 | public File next() { | |
222 | java.io.File f = ls[i++]; | |
223 | cur = new RFile(f, demangle(f.getName())); | |
224 | return(cur); | |
225 | } | |
226 | ||
227 | public void remove() { | |
228 | if(cur == null) | |
229 | throw(new IllegalStateException()); | |
230 | cur.remove(); | |
231 | cur = null; | |
232 | } | |
233 | }); | |
234 | } | |
235 | ||
236 | public static void register() { | |
237 | Store.register("file", new Factory() { | |
238 | public Store create(String rootname, Package pkg) { | |
239 | java.io.File root = new java.io.File(rootname); | |
240 | ThreadContext ctx = ThreadContext.current(); | |
241 | if(ctx != null) { | |
242 | if(ctx.server().name() != null) | |
243 | root = new java.io.File(root, ctx.server().name()); | |
244 | } | |
245 | return(new FileStore(pkg, root)); | |
246 | } | |
247 | }); | |
248 | } | |
249 | } |