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 org.apache.mina.common.ByteBuffer;
32
33 import java.util.Vector;
34 import java.util.Iterator;
35
36 import uk.ac.rdg.resc.jstyx.types.ULong;
37 import uk.ac.rdg.resc.jstyx.types.DirEntry;
38 import uk.ac.rdg.resc.jstyx.StyxException;
39 import uk.ac.rdg.resc.jstyx.messages.StyxBuffer;
40
41 /***
42 * Class representing a directory on a Styx server. One would only rarely need
43 * to create subclasses of this; an example would be creating a StyxDirectory that
44 * represents a directory on the host filesystem (see DirectoryOnDisk).
45 *
46 * @author Jon Blower
47 * $Revision: 601 $
48 * $Date: 2006-03-20 17:51:50 +0000 (Mon, 20 Mar 2006) $
49 * $Log$
50 * Revision 1.13 2006/03/20 17:51:50 jonblower
51 * Adding authentication to base JStyx system
52 *
53 * Revision 1.12 2006/01/04 16:47:29 jonblower
54 * Reworked getName() and getFullPath()
55 *
56 * Revision 1.11 2005/09/08 07:08:59 jonblower
57 * Removed "String user" from list of parameters to StyxFile.write()
58 *
59 * Revision 1.10 2005/05/19 14:46:51 jonblower
60 * Changed behaviour of StyxDirectory.createChild(): no longer adds file to namespace in this method
61 *
62 * Revision 1.9 2005/05/11 10:34:31 jonblower
63 * Changed so that addChild() returns this StyxDirectory object to allow chaining
64 *
65 * Revision 1.8 2005/04/28 08:11:15 jonblower
66 * Modified permissions handling in documentation directory of SGS
67 *
68 * Revision 1.7 2005/03/24 09:48:31 jonblower
69 * Changed 'count' from long to int throughout for reading and writing
70 *
71 * Revision 1.6 2005/03/22 17:48:27 jonblower
72 * Removed debug code that tracked ByteBuffer allocation
73 *
74 * Revision 1.5 2005/03/21 17:57:11 jonblower
75 * Trying to fix ByteBuffer leak in SGS server
76 *
77 * Revision 1.4 2005/03/16 17:56:24 jonblower
78 * Replaced use of java.nio.ByteBuffer with MINA's ByteBuffer to minimise copying of buffers
79 *
80 * Revision 1.3 2005/03/11 14:02:16 jonblower
81 * Merged MINA-Test_20059309 into main line of development
82 *
83 * Revision 1.2.2.1 2005/03/10 11:53:54 jonblower
84 * Modified for MINA framework
85 *
86 * Revision 1.2 2005/03/01 13:47:43 jonblower
87 * Changed default user and group to 'user' and 'group'
88 *
89 * Revision 1.1.1.1 2005/02/16 18:58:32 jonblower
90 * Initial import
91 *
92 */
93 public class StyxDirectory extends StyxFile
94 {
95
96 private Vector children;
97
98 /*** Creates a new instance of StyxDirectory */
99 public StyxDirectory(String name, String owner, String group, int permissions)
100 throws StyxException
101 {
102
103 super(name, owner, group, permissions, false, false);
104 this.directory = true;
105 this.children = new Vector();
106 }
107
108 /***
109 * Creates a directory with default permissions (0777, rwxrwxrwx)
110 */
111 public StyxDirectory(String name) throws StyxException
112 {
113 this(name, "user", "group", 0777);
114 }
115
116 /***
117 * Creates a directory with the given permissions
118 */
119 public StyxDirectory(String name, int permissions) throws StyxException
120 {
121 this(name, "user", "group", permissions);
122 }
123
124 /***
125 * This method is overridden to return a more meaningful error message.
126 */
127 public void checkSetLength(ULong newLength) throws StyxException
128 {
129 throw new StyxException("cannot change the length of a directory");
130 }
131
132 /***
133 * @return true if this is the root directory (i.e. if it has no parent)
134 */
135 public boolean isRoot()
136 {
137 return (this.parent == null);
138 }
139
140 /***
141 * Gets the full path relative to the root of this file system, or
142 * a single slash if this is the root directory. Since this is a directory,
143 * the full path will end in a slash
144 */
145 public String getFullPath()
146 {
147 if (this.parent == null)
148 {
149 return "/";
150 }
151 return this.parent.getFullPath() + this.getName() + "/";
152 }
153
154 /***
155 * @return the name of this file
156 */
157 public String getName()
158 {
159 return this.name;
160 }
161
162 /***
163 * Returns the directory contents. This method cannot be overridden.
164 */
165 public final void read(StyxFileClient client, long offset, int count, int tag)
166 throws StyxException
167 {
168 if (client == null)
169 {
170 throw new StyxException("Internal error: client is null");
171 }
172
173
174
175
176 if (offset != 0 && offset != client.getOffset())
177 {
178 throw new StyxException("Invalid offset when reading directory");
179 }
180
181
182 ByteBuffer buf = ByteBuffer.allocate((int)count);
183 StyxBuffer styxBuf = new StyxBuffer(buf);
184 StyxFile sf;
185 int nextFile = (offset == 0) ? 0 : client.getNextFileToRead();
186
187 while (nextFile < this.children.size())
188 {
189 sf = (StyxFile)this.children.get(nextFile);
190
191 DirEntry dirEntry = sf.getDirEntry();
192 if (dirEntry.getSize() <= buf.remaining())
193 {
194 styxBuf.putDirEntry(dirEntry);
195 }
196 else
197 {
198 break;
199 }
200 nextFile++;
201 }
202
203 buf.flip();
204
205
206 client.setOffset(offset + buf.limit());
207 client.setNextFileToRead(nextFile);
208
209
210 byte[] bytes = new byte[buf.remaining()];
211 buf.get(bytes);
212
213 buf.release();
214
215 this.replyRead(client, bytes, tag);
216 }
217
218 /***
219 * This always throws a StyxException as it is illegal to write to a
220 * directory
221 */
222 public void write(StyxFileClient client, long offset, int count,
223 ByteBuffer data, boolean truncate, int tag)
224 throws StyxException
225 {
226 throw new StyxException("Cannot write to a directory");
227 }
228
229 /***
230 * @return the length of the file. This is always zero: subclasses cannot
231 * override this.
232 */
233 public final ULong getLength()
234 {
235 return ULong.ZERO;
236 }
237
238 /***
239 * Refreshes this file (if it represents another entity, such as a file on disk,
240 * this method is used to make sure that the file metadata (length, access
241 * time etc) are up to date.
242 */
243 public synchronized void refresh()
244 {
245 this.refresh(true);
246 }
247
248 /***
249 * @param updateChildren If this is set true, this method will also refresh
250 * all the immediate children of this directory. This default method does
251 * nothing; subclasses must override this.
252 */
253 protected synchronized void refresh(boolean updateChildren)
254 {
255 return;
256 }
257
258 /***
259 * Gets all the direct descendants of this directory
260 * @return the children as an array of StyxFiles or null if this StyxFile
261 * is not a directory
262 */
263 public synchronized StyxFile[] getChildren()
264 {
265 if (this.children == null)
266 {
267 return null;
268 }
269
270
271 return (StyxFile[])this.children.toArray(new StyxFile[0]);
272 }
273
274 /***
275 * Gets the number of direct descendants of this directory
276 */
277 public int getNumChildren()
278 {
279 if (this.children == null)
280 {
281 return 0;
282 }
283 return this.children.size();
284 }
285
286 /***
287 * Adds a file to this directory. If a file with the same name already
288 * exists, throws a FileExistsException
289 * @return this StyxDirectory object, so that calls can be chained:
290 * <code>StyxDirectory root = new StyxDirectory().addChild(file1).addChile(file2)</code>
291 */
292 public synchronized StyxDirectory addChild(StyxFile sf) throws FileExistsException
293 {
294
295
296 synchronized (this.children)
297 {
298 if (childExists(sf.getName()))
299 {
300 throw new FileExistsException(sf.getName() + " already exists");
301 }
302 sf.parent = this;
303 this.children.add(sf);
304 }
305
306
307 this.fireContentsChanged();
308 return this;
309 }
310
311 /***
312 * @return true if a child with the given name exists in this directory
313 */
314 public boolean childExists(String name)
315 {
316 name = name.trim();
317
318 synchronized (this.children)
319 {
320 for (int i = 0; i < this.children.size(); i++)
321 {
322 StyxFile f = (StyxFile)this.children.get(i);
323 if (f.getName().equals(name))
324 {
325 return true;
326 }
327 }
328 }
329 return false;
330 }
331
332 /***
333 * Creates a new file to be added to this directory. This default
334 * implementation throws an exception; subclasses should override this method
335 * to allow files to be created.
336 * This method should <b>not</b> add the new file to the directory. This will
337 * be done in StyxServerProtocolHandler.replyCreate().
338 * @return The newly-created file
339 */
340 public StyxFile createChild(String name, int perm, boolean isDir,
341 boolean isAppOnly, boolean isExclusive) throws StyxException
342 {
343 throw new StyxException("cannot create a new file in this type of directory");
344 }
345
346 /***
347 * Removes the given file from this directory.
348 */
349 public synchronized void removeChild(StyxFile child)
350 {
351 this.children.remove(child);
352
353
354 this.fireContentsChanged();
355 }
356
357 /***
358 * Removes this directory from the server. This directory must be empty
359 * (see this.removeAllChildren())
360 * @throws StyxException if this is the root directory, or if it is not empty
361 */
362 public synchronized void remove() throws StyxException
363 {
364 if (this.isRoot())
365 {
366 throw new StyxException("Cannot remove the root directory of the server");
367 }
368 if (this.getNumChildren() != 0)
369 {
370 throw new StyxException("Cannot remove a directory unless it is empty");
371 }
372 super.remove();
373 }
374
375 /***
376 * Recursively removes all children from this directory.
377 */
378 public synchronized void removeAllChildren()
379 {
380 synchronized(this.children)
381 {
382 StyxFile sf;
383 while(this.children.size() > 0)
384 {
385
386 sf = (StyxFile)this.children.get(0);
387 if (sf instanceof StyxDirectory)
388 {
389
390 StyxDirectory sd = (StyxDirectory)sf;
391 sd.removeAllChildren();
392 }
393
394 try
395 {
396
397 sf.remove();
398 }
399 catch(StyxException se)
400 {
401
402
403
404 se.printStackTrace();
405
406
407 this.children.remove(sf);
408 }
409 }
410 }
411 }
412
413 /***
414 * Gets the child with the given name or null if it does not exist
415 */
416 public StyxFile getChild(String name)
417 {
418 if (name.equals(".."))
419 {
420 return this.getParent();
421 }
422 if (name.equals("."))
423 {
424 return this;
425
426 }
427 for (int i = 0; i < this.children.size(); i++)
428 {
429 StyxFile sf = (StyxFile)this.children.get(i);
430 String sfName = sf.getName();
431 if (name.equals(sfName))
432 {
433 return sf;
434 }
435 }
436 return null;
437 }
438
439 /***
440 * Returns the parent of this directory. The parent of the root directory is
441 * the root itself (according to the Inferno manual)
442 */
443 public final StyxDirectory getParent()
444 {
445 return this.parent == null ? this : this.parent;
446 }
447
448 }