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