A couple of bugfixes.
[jsvc.git] / src / dolda / jsvc / store / FileStore.java
CommitLineData
5d99f865
FT
1package dolda.jsvc.store;
2
3import dolda.jsvc.*;
4import dolda.jsvc.util.*;
5import java.io.*;
6import java.util.*;
7import java.security.*;
8
9class 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) {
241 java.io.File root = new java.io.File(rootname);
242 ThreadContext ctx = ThreadContext.current();
243 if(ctx != null) {
244 if(ctx.server().name() != null)
245 root = new java.io.File(root, ctx.server().name());
246 }
247 return(new FileStore(pkg, root));
248 }
249 });
250 }
251}