1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 package uk.ac.rdg.resc.jstyx.server;
30
31 import java.io.File;
32 import java.io.RandomAccessFile;
33 import java.io.FileInputStream;
34 import java.io.FileNotFoundException;
35 import java.io.IOException;
36 import java.nio.channels.FileChannel;
37
38 import org.apache.log4j.Logger;
39 import org.apache.mina.common.ByteBuffer;
40
41 import uk.ac.rdg.resc.jstyx.StyxException;
42 import uk.ac.rdg.resc.jstyx.types.ULong;
43
44 /***
45 * Class representing a file on a hard drive. Note that creating a new instance
46 * of this class does not create an actual new file on the hard disk; it simply
47 * creates a wrapper for an existing file.
48 *
49 * This class opens a new Input/OutputStream every time a read/write message
50 * arrives, so it is probably not very efficient (but this was the simplest way
51 * to implement reliably).
52 *
53 * @author Jon Blower
54 * $Revision: 476 $
55 * $Date: 2005-11-09 17:42:24 +0000 (Wed, 09 Nov 2005) $
56 * $Log$
57 * Revision 1.19 2005/11/09 17:42:24 jonblower
58 * Modified EOF recognition (now requires zero-byte message to be written to the end of the file)
59 *
60 * Revision 1.18 2005/11/04 19:34:35 jonblower
61 * Added code to recognise EOF (i.e. zero-byte) write messages
62 *
63 * Revision 1.15 2005/09/08 07:08:59 jonblower
64 * Removed "String user" from list of parameters to StyxFile.write()
65 *
66 * Revision 1.13 2005/05/19 14:47:38 jonblower
67 * Realised that new RandomAccessFile("..", "rw") doesn't throw FileNotFoundException
68 *
69 * Revision 1.12 2005/05/11 10:33:50 jonblower
70 * Implemented MonitoredFileOnDisk.java
71 *
72 * Revision 1.11 2005/05/10 19:19:05 jonblower
73 * Reinstated chan.truncate()
74 *
75 * Revision 1.10 2005/05/10 12:43:57 jonblower
76 * Rewrote and simplified: now closes file after each read or write
77 *
78 * Revision 1.9 2005/05/10 08:02:18 jonblower
79 * Changes related to implementing MonitoredFileOnDisk
80 *
81 * Revision 1.8 2005/05/09 07:13:52 jonblower
82 * Changed getFileOnDisk() to getFileOrDirectoryOnDisk()
83 *
84 * Revision 1.7 2005/04/28 08:11:15 jonblower
85 * Modified permissions handling in documentation directory of SGS
86 *
87 * Revision 1.6 2005/04/27 16:11:43 jonblower
88 * Added capability to add documentation files to SGS namespace
89 *
90 * Revision 1.5 2005/03/24 09:48:31 jonblower
91 * Changed 'count' from long to int throughout for reading and writing
92 *
93 * Revision 1.4 2005/03/19 21:47:02 jonblower
94 * Further fixes relating to releasing ByteBuffers
95 *
96 * Revision 1.3 2005/03/16 17:56:23 jonblower
97 * Replaced use of java.nio.ByteBuffer with MINA's ByteBuffer to minimise copying of buffers
98 *
99 * Revision 1.2 2005/03/11 14:02:16 jonblower
100 * Merged MINA-Test_20059309 into main line of development
101 *
102 * Revision 1.1.1.1.2.1 2005/03/10 20:55:37 jonblower
103 * Removed references to Netty
104 *
105 * Revision 1.1.1.1 2005/02/16 18:58:31 jonblower
106 * Initial import
107 *
108 */
109 public class FileOnDisk extends StyxFile
110 {
111 private static final Logger log = Logger.getLogger(FileOnDisk.class);
112
113 protected File file;
114 protected boolean mustExist;
115
116
117
118
119 protected boolean eofWritten;
120
121
122 /***
123 * Gets a StyxFile that wraps the given java.io.File. If the File is a
124 * directory, this will return an instance of DirectoryOnDisk, otherwise
125 * it will return an instance of FileOnDisk.
126 * @todo Set file permissions correctly
127 * @throws StyxException if the File does not exist
128 */
129 public static StyxFile getFileOrDirectoryOnDisk(File file) throws StyxException
130 {
131 if (!file.exists())
132 {
133 throw new StyxException("file " + file.getName() + " does not exist");
134 }
135 if (file.isDirectory())
136 {
137 return new DirectoryOnDisk(file);
138 }
139 else
140 {
141 return new FileOnDisk(file);
142 }
143 }
144
145 /***
146 * Creates a new FileOnDisk that wraps the file at the given path
147 */
148 public FileOnDisk(String filepath) throws StyxException
149 {
150 this(new File(filepath));
151 }
152
153 /***
154 * Creates a new FileOnDisk whose name is the same as that of (the last part
155 * of) the underlying file (i.e. file.getName())
156 * @param file The java.io.File which this represents
157 * @throws StyxException if the java.io.File does not exist
158 */
159 public FileOnDisk(File file) throws StyxException
160 {
161 this(file.getName(), file);
162 }
163
164 /***
165 * Creates a FileOnDisk with the default permissions (0666, rw-rw-rw-)
166 * @param name The name of this file as it will appear in the namespace
167 * @param file The java.io.File which this represents
168 * @throws StyxException if the java.io.File does not exist
169 */
170 public FileOnDisk(String name, File file) throws StyxException
171 {
172 this(name, file, 0666, true);
173 }
174
175 /***
176 * Creates a FileOnDisk with default permissions (0666). The name of the file
177 * in the namespace will be file.getName().
178 * @param file The java.io.File which this represents
179 * @param mustExist If this is true, then an exception will be thrown by this
180 * constructor if the underlying java.io.File does not exist. Furthermore,
181 * if the File is deleted, the FileOnDisk will be removed from the namespace.
182 * If this is set false, a non-existent File will be represented as a
183 * zero-length read-only FileOnDisk in the namespace.
184 * @throws StyxException if the java.io.File does not exist and mustExist=true
185 */
186 public FileOnDisk(File file, boolean mustExist)
187 throws StyxException
188 {
189 this(file.getName(), file, 0666, mustExist);
190 }
191
192 /***
193 * @param name The name of this file as it will appear in the namespace
194 * @param file The java.io.File which this represents
195 * @param permissions the permissions of the file
196 * @param mustExist If this is true, then an exception will be thrown by this
197 * constructor if the underlying java.io.File does not exist. Furthermore,
198 * if the File is deleted, the FileOnDisk will be removed from the namespace.
199 * If this is set false, a non-existent File will be represented as a
200 * zero-length read-only FileOnDisk in the namespace.
201 * @throws StyxException if the java.io.File does not exist and mustExist=true
202 */
203 public FileOnDisk(String name, File file, int permissions, boolean mustExist)
204 throws StyxException
205 {
206 super(name.trim(), permissions);
207 if (!file.exists() && mustExist)
208 {
209 throw new StyxException("file " + file.getName() + " does not exist");
210 }
211 this.file = file;
212 this.mustExist = mustExist;
213 this.eofWritten = false;
214 }
215
216 /***
217 * Reads from the underlying java.io.File. This method opens a new file
218 * channel, reads the data, then closes the channel again.
219 *
220 * If the java.io.File does not exist and mustExist==true, this method will
221 * throw a StyxException. If the File does not exist and mustExist==false,
222 * this method will simply return zero bytes to the client.
223 */
224 public synchronized void read(StyxFileClient client, long offset, int count, int tag)
225 throws StyxException
226 {
227 try
228 {
229
230 FileChannel chan = new FileInputStream(this.file).getChannel();
231
232
233
234 ByteBuffer buf = ByteBuffer.allocate(count);
235
236
237 buf.position(0).limit(count);
238
239
240
241 int numRead = chan.read(buf.buf(), offset);
242 log.debug("Read " + numRead + " bytes from " + this.file.getPath());
243
244 chan.close();
245
246 buf.flip();
247 this.replyRead(client, buf, tag);
248 }
249 catch(FileNotFoundException fnfe)
250 {
251
252 if (mustExist)
253 {
254 log.debug("The file " + this.file.getPath() +
255 " has been removed by another process");
256
257 this.remove();
258 throw new StyxException("The file " + this.name + " was removed.");
259 }
260 else
261 {
262
263 this.replyRead(client, new byte[0], tag);
264 }
265 }
266 catch(IOException ioe)
267 {
268 throw new StyxException("An error of class " + ioe.getClass() +
269 " occurred when trying to read from " + this.getFullPath() +
270 ": " + ioe.getMessage());
271 }
272 }
273
274 /***
275 * Writes data to the underlying java.io.File. If the File does not exist
276 * and <code>mustExist</code> is true, this throws a StyxException. If the File
277 * does not exist and <code>mustExist</code> is false.
278 */
279 public synchronized void write(StyxFileClient client, long offset,
280 int count, ByteBuffer data, boolean truncate, int tag)
281 throws StyxException
282 {
283 if (this.mustExist && !this.file.exists())
284 {
285
286 log.debug("The file " + this.file.getPath() +
287 " has been removed by another process");
288
289 this.remove();
290 throw new StyxException("The file " + this.name + " was removed.");
291 }
292 try
293 {
294 int nWritten = 0;
295
296
297 if (data.remaining() == 0 && offset == this.file.length())
298 {
299 log.debug("Got EOF signal");
300 this.eofWritten = true;
301 }
302 else
303 {
304
305
306
307
308 FileChannel chan = new RandomAccessFile(this.file, "rw").getChannel();
309
310
311 int pos = data.position();
312 int lim = data.limit();
313
314 data.limit(data.position() + count);
315
316
317 nWritten = chan.write(data.buf(), offset);
318
319
320 data.limit(lim).position(pos);
321
322
323 if (truncate)
324 {
325 log.debug("Truncating file at " + (offset + nWritten) + " bytes");
326 chan.truncate(offset + nWritten);
327 }
328
329 this.eofWritten = false;
330
331 chan.close();
332 }
333
334 this.replyWrite(client, nWritten, tag);
335 }
336 catch(IOException ioe)
337 {
338 throw new StyxException("An error of class " + ioe.getClass() +
339 " occurred when trying to write to " + this.getFullPath() +
340 ": " + ioe.getMessage());
341 }
342 }
343
344 /***
345 * Reads all metadata from underlying disk file
346 */
347 public synchronized void refresh()
348 {
349
350
351 this.lastModifiedTime = this.file.lastModified() / 1000;
352 }
353
354 /***
355 * @return the length of the file, or zero if the file does not exist
356 */
357 public ULong getLength()
358 {
359
360 return new ULong(this.file.length());
361 }
362
363 /***
364 * @return true if we have received an EOF message from the client when
365 * the client uploaded data to this file and no data have been received since
366 * this EOF message was received.
367 */
368 public boolean receivedEOF()
369 {
370 return this.eofWritten;
371 }
372
373 /***
374 * Closes the open FileChannel and removes the underlying file from the disk
375 */
376 protected synchronized void delete()
377 {
378 if (this.file.exists())
379 {
380 this.file.delete();
381 }
382 }
383
384 }