Improved the store library massively and added a preliminary filesystem store.
[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() {
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}