A couple of bugfixes.
[jsvc.git] / src / dolda / jsvc / store / FileStore.java
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.exists()) {
42                         if(!FileStore.this.base.mkdirs())
43                             throw(new RuntimeException("Could not create store directory (Java won't tell me why)"));
44                     }
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 }