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.client;
30
31 import java.util.Vector;
32 import java.util.Iterator;
33 import java.util.Date;
34
35 import java.io.File;
36 import java.io.InputStream;
37
38 import org.apache.log4j.Logger;
39
40 import org.apache.mina.common.ByteBuffer;
41
42 import uk.ac.rdg.resc.jstyx.StyxException;
43 import uk.ac.rdg.resc.jstyx.StyxUtils;
44
45 import uk.ac.rdg.resc.jstyx.types.DirEntry;
46 import uk.ac.rdg.resc.jstyx.types.ULong;
47 import uk.ac.rdg.resc.jstyx.types.Qid;
48
49 import uk.ac.rdg.resc.jstyx.messages.*;
50 import uk.ac.rdg.resc.jstyx.client.callbacks.*;
51
52 /***
53 * A Styx file (or directory) from the point of view of the client. (It is called
54 * a CStyxFile in order to avoid confusion with the server-side StyxFile class.)
55 * To create a CStyxFile, open a StyxConnection and use the getFile() method.
56 * CStyxFiles cannot be created directly.
57 * @todo implement a create() method that automatically creates all necessary
58 * subdirectories
59 * @todo implement changing of stat data (length etc) via a Twstat message
60 *
61 * @author Jon Blower
62 * $Revision: 609 $
63 * $Date: 2006-03-31 18:09:42 +0100 (Fri, 31 Mar 2006) $
64 * $Log$
65 * Revision 1.46 2006/03/21 14:58:41 jonblower
66 * Implemented clear-text password-based authentication and did some simple tests
67 *
68 * Revision 1.45 2006/01/06 10:14:22 jonblower
69 * Clarified comments
70 *
71 * Revision 1.44 2005/12/07 08:51:13 jonblower
72 * Added option to readAsync() to open file for reading and writing with truncation
73 *
74 * Revision 1.43 2005/12/01 08:21:55 jonblower
75 * Fixed javadoc comments
76 *
77 * Revision 1.42 2005/11/07 22:00:54 jonblower
78 * Added upload(java.io.File) method
79 *
80 * Revision 1.41 2005/10/14 18:05:19 jonblower
81 * Added walkFid() and exists() methods
82 *
83 * Revision 1.40 2005/09/11 18:50:42 jonblower
84 * Added toString() method
85 *
86 * Revision 1.39 2005/09/02 16:54:26 jonblower
87 * Added new writeAsync() methods
88 *
89 * Revision 1.38 2005/08/31 17:07:15 jonblower
90 * Fixed bug in getContents() and released ByteBuffer in fireDataArrived()
91 *
92 * Revision 1.36 2005/08/10 18:31:55 jonblower
93 * Bug fixes, plus added synchronous openOrCreate() method
94 *
95 * Revision 1.34 2005/08/05 13:46:40 jonblower
96 * Factored out all callback objects from CStyxFile into separate classes
97 *
98 * Revision 1.33 2005/08/04 16:48:57 jonblower
99 * Added and edited upload() methods in CStyxFile
100 *
101 * Revision 1.32 2005/07/28 16:37:35 jonblower
102 * Added isSameFile() method
103 *
104 * Revision 1.31 2005/07/06 17:46:19 jonblower
105 * Added setDirEntry()
106 *
107 * Revision 1.30 2005/06/27 17:18:03 jonblower
108 * Added accelerated downloading methods, and responded to changes with MessageCallback
109 *
110 * Revision 1.29 2005/06/22 17:08:34 jonblower
111 * Changed to allow target file to be null in download()
112 *
113 * Revision 1.28 2005/06/20 17:20:34 jonblower
114 * Added download() and downloadAsync() to CStyxFile
115 *
116 * Revision 1.27 2005/06/14 07:45:16 jonblower
117 * Implemented setting of params and async notification of parameter changes
118 *
119 * Revision 1.26 2005/05/26 16:48:48 jonblower
120 * Fixed bug in uploadFileAsync()
121 *
122 * Revision 1.24 2005/05/25 16:58:01 jonblower
123 * Changed implementation of openOrCreate()
124 *
125 * Revision 1.23 2005/05/25 15:36:55 jonblower
126 * No longer requires open fid, implemented createAsync() and openOrCreateAsync() methods
127 *
128 * Revision 1.22 2005/05/24 12:55:30 jonblower
129 * Added uploadFile()
130 *
131 * Revision 1.21 2005/05/23 16:48:17 jonblower
132 * Overhauled CStyxFile (esp. asynchronous methods) and StyxConnection (added cache of CStyxFiles)
133 *
134 * Revision 1.20 2005/05/23 07:36:19 jonblower
135 * Implementing uploadFileAsync
136 *
137 * Revision 1.19 2005/05/18 17:12:01 jonblower
138 * Added getURL() method
139 *
140 * Revision 1.18 2005/05/17 14:36:11 jonblower
141 * Fixed bug with getChildrenAsync()
142 *
143 * Revision 1.17 2005/05/12 15:59:59 jonblower
144 * Implemented getChildrenAsync()
145 *
146 * Revision 1.16 2005/05/12 14:20:55 jonblower
147 * Changed dataSent() method to dataWritten() (more accurate name)
148 *
149 * Revision 1.15 2005/05/12 08:00:33 jonblower
150 * Added getChildrenAsync() to CStyxFile and childrenFound() to CStyxFileChangeListener
151 *
152 * Revision 1.14 2005/05/12 07:40:52 jonblower
153 * CStyxFile.close() no longer throws a StyxException
154 *
155 * Revision 1.12 2005/05/05 07:08:37 jonblower
156 * Improved handling of buffers in change listeners
157 *
158 * Revision 1.11 2005/03/19 21:46:58 jonblower
159 * Further fixes relating to releasing ByteBuffers
160 *
161 * Revision 1.10 2005/03/18 13:55:59 jonblower
162 * Improved freeing of ByteBuffers, and bug fixes
163 *
164 * Revision 1.9 2005/03/16 17:55:52 jonblower
165 * Replaced use of java.nio.ByteBuffer with MINA's ByteBuffer to minimise copying of buffers
166 *
167 * Revision 1.8 2005/03/11 13:58:25 jonblower
168 * Merged MINA-Test_20059309 into main line of development
169 *
170 * Revision 1.7.2.3 2005/03/11 12:30:29 jonblower
171 * Changed so that message payloads are always ints, not longs
172 *
173 * Revision 1.7.2.2 2005/03/10 20:54:57 jonblower
174 * Removed references to Netty
175 *
176 * Revision 1.7.2.1 2005/03/10 11:49:17 jonblower
177 * Removed unneeded reference to StyxBuffer
178 *
179 * Revision 1.7 2005/03/07 08:29:18 jonblower
180 * Minor changes (better handling of path building)
181 *
182 * Revision 1.6 2005/02/28 11:43:36 jonblower
183 * Tidied up logging code
184 *
185 * Revision 1.5 2005/02/21 18:06:13 jonblower
186 * Un-synchronized many methods, made more robust in case of multiple calls to asynchronous methods
187 *
188 * Revision 1.4 2005/02/18 17:57:31 jonblower
189 * Changed a constructor to private access
190 *
191 * Revision 1.3 2005/02/18 09:11:35 jonblower
192 * Remove 'synchronized' from some methods, added some comments
193 *
194 * Revision 1.1.1.1 2005/02/16 18:58:17 jonblower
195 * Initial import
196 *
197 */
198 public class CStyxFile
199 {
200
201 private static final Logger log = Logger.getLogger(CStyxFile.class);
202
203 private StyxConnection conn;
204 private String path;
205
206 private String name;
207
208 private long fid;
209 private Qid qid;
210 private DirEntry dirEntry;
211 private int ioUnit;
212
213
214 private int mode;
215
216 private Vector listeners;
217
218 private CStyxFile[] children;
219
220 /***
221 * Creates a new instance of CStyxFile. This doesn't actually open or create
222 * the file; use open() or create() for this.
223 * @throws InvalidPathException if the path is not valid
224 */
225 CStyxFile(StyxConnection conn, String path)
226 {
227 this.conn = conn;
228 this.path = getCanonicalPath(path);
229
230 int lastSlash = this.path.lastIndexOf("/");
231 this.name = this.path.substring(lastSlash + 1);
232 this.fid = -1;
233 this.ioUnit = 0;
234 this.mode = -1;
235 this.qid = null;
236 this.dirEntry = null;
237 this.listeners = new Vector();
238 }
239
240 /***
241 * @return The connection on which this file sits
242 */
243 public StyxConnection getConnection()
244 {
245 return this.conn;
246 }
247
248 /***
249 * Gets the name of the file, i.e. the last part of the path
250 */
251 public String getName()
252 {
253 return this.name;
254 }
255
256 /***
257 * Gets the full path of the file
258 */
259 public String getPath()
260 {
261 return this.path;
262 }
263
264 /***
265 * Gets the full path of the parent of this file
266 */
267 public String getParentPath()
268 {
269 int lastSlash = this.path.lastIndexOf("/");
270 return this.path.substring(0, lastSlash);
271 }
272
273 /***
274 * @return the full URL to this file (e.g. "styx://localhost:9092/path/to/this")
275 * @todo Include user name in the URL?
276 */
277 public String getURL()
278 {
279 StringBuffer buf = new StringBuffer("styx://");
280 buf.append(this.conn.getRemoteHost());
281 buf.append(":");
282 buf.append(this.conn.getRemotePort());
283 buf.append(this.getPath());
284 return buf.toString();
285 }
286
287 /***
288 * @return true if this is a directory. This method will block if it is
289 * necessary to send a message to get the stat of this file.
290 */
291 public boolean isDirectory() throws StyxException
292 {
293 if (this.qid == null)
294 {
295
296
297 this.refresh();
298 }
299 return this.qid.isDirectory();
300 }
301
302 /***
303 * @return true if this is an authorization file.
304 */
305 public boolean isAuth()
306 {
307
308
309
310 return this.qid == null ? false : this.qid.isAuth();
311 }
312
313 /***
314 * @return the owner of the file
315 */
316 public String getOwner() throws StyxException
317 {
318 if (this.dirEntry == null)
319 {
320 this.refresh();
321 }
322 return this.dirEntry.getOwner();
323 }
324
325 /***
326 * @return the group of the file
327 */
328 public String getGroup() throws StyxException
329 {
330 if (this.dirEntry == null)
331 {
332 this.refresh();
333 }
334 return this.dirEntry.getGroup();
335 }
336
337 /***
338 * @return the length of the file in bytes. This method will block if it is
339 * necessary to send a message to get the stat of this file.
340 */
341 public long getLength() throws StyxException
342 {
343 if (this.dirEntry == null)
344 {
345 this.refresh();
346 }
347 return this.dirEntry.getFileLength().asLong();
348 }
349
350 /***
351 * @return the last modified time of the file. This method will block if it is
352 * necessary to send a message to get the stat of this file.
353 */
354 public Date getLastModified() throws StyxException
355 {
356 if (this.dirEntry == null)
357 {
358 this.refresh();
359 }
360 return new Date(this.dirEntry.getLastModifiedTime() * 1000);
361 }
362
363 /***
364 * @return the DirEntry object containing this file's attributes. If the
365 * DirEntry has not yet been set, this will return null. In this case,
366 * call refresh() on this file. Alternatively, if all you want is the Qid,
367 * it is possible that the Qid will be set, even if the DirEntry has not:
368 * try getQid().
369 */
370 public DirEntry getDirEntry()
371 {
372 return this.dirEntry;
373 }
374
375 /***
376 * Sets the dirEntry of this file. This method is not used often. It is most
377 * useful when creating a CStyxFile from reading a directory: in this case
378 * we have the DirEntry without having to separately stat the file.
379 */
380 public void setDirEntry(DirEntry dirEntry)
381 {
382 this.dirEntry = dirEntry;
383
384 this.qid = this.dirEntry.getQid();
385 }
386
387 /***
388 * @return true if a fid has been set for this file
389 */
390 public boolean hasFid()
391 {
392 return (this.fid >= 0);
393 }
394
395 /***
396 * @return the fid of this file
397 */
398 public long getFid()
399 {
400 return this.fid;
401 }
402
403 /***
404 * Set the mode of this file: this should not normally be called, except
405 * by the OpenCallback, when the file has been open
406 */
407 public void setMode(int newMode)
408 {
409 this.mode = newMode;
410 }
411
412 /***
413 * @return the mode under which this file is open, or -1 if this file is
414 * not open.
415 */
416 public int getMode()
417 {
418 return this.mode;
419 }
420
421 /***
422 * Sets the fid of the file. This should not normally be called except
423 * by GetFidCallback
424 */
425 public void setFid(long newFid)
426 {
427 this.fid = newFid;
428 }
429
430 /***
431 * Sets the qid of the file. This should not normally be called except
432 * by the callbacks in the uk.ac.rdg.resc.jstyx.client.callbacks package
433 */
434 public void setQid(Qid newQid)
435 {
436 this.qid = newQid;
437 }
438
439 /***
440 * @return the Qid of this file, or null if the qid has not yet been set
441 */
442 public Qid getQid()
443 {
444 return this.qid;
445 }
446
447 /***
448 * @return true if this file is open (i.e. its mode has been set)
449 */
450 public boolean isOpen()
451 {
452 return (this.mode >= 0);
453 }
454
455 /***
456 * Returns true if this CStyxFile represents the same file on the server
457 * as the CStyxFile passed in the argument. This method may block if the
458 * dirEntry of one or both of the files is not set. Compares the qids of
459 * the two files (see Qid.equals())
460 * @throws StyxException if the dirEntry of one of the files was not set and
461 * there was an error getting the file's stat. If you know that the dirEntry
462 * of both files is already set, you can safely ignore this exception.
463 */
464 public boolean isSameFile(CStyxFile otherFile) throws StyxException
465 {
466 if (this.getQid() == null)
467 {
468 this.refresh();
469 }
470 if (otherFile.getQid() == null)
471 {
472 otherFile.refresh();
473 }
474 return this.getQid().equals(otherFile.getQid());
475 }
476
477 /***
478 * Sets the maximum number of bytes that can be read from or written to
479 * this file in a single operation. This method should not normally be
480 * called directly, except by the OpenCallback.
481 */
482 public void setIoUnit(int newIoUnit)
483 {
484 this.ioUnit = newIoUnit;
485 }
486
487 /***
488 * @return the maximum number of bytes that can be read from or written to
489 * this file in a single operation. Will only return valid data once the file
490 * has been opened (or created).
491 */
492 public int getIoUnit()
493 {
494 return this.ioUnit;
495 }
496
497 /***
498 * Sets the children of this file. This is not normally called directly,
499 * except by the GetChildrenCallback.
500 */
501 public void setChildren(CStyxFile[] children)
502 {
503 this.children = children;
504 }
505
506 /***
507 * @return the Logger used by this class
508 */
509 public static Logger getLogger()
510 {
511 return log;
512 }
513
514 /***
515 * Gets a new fid from the pool and walks it to the
516 * location of this file. Does not block; calls the replyArrived() method
517 * of the given callback if the walk was successful, or the error() method
518 * otherwise.
519 */
520 public void walkFidAsync(MessageCallback callback)
521 {
522 new GetFidCallback(this, callback).walkFid();
523 }
524
525 /***
526 * Gets a new fid from the pool and walks it to the
527 * location of this file. This method blocks until the fid has been walked
528 * to the location of the file
529 * @throws StyxException if there was an error walking the fid (i.e. the
530 * file does not exist)
531 */
532 public void walkFid() throws StyxException
533 {
534 StyxReplyCallback callback = new StyxReplyCallback();
535 this.walkFidAsync(callback);
536
537 callback.getReply();
538 }
539
540 /***
541 * Tests to see if this file exists on the server, returning true if so
542 * and false if not. This method may block (it will do so if we have
543 * to get a new fid and walk it to this file location, i.e. if the file
544 * has not been opened before).
545 */
546 public boolean exists()
547 {
548 if (this.hasFid())
549 {
550 return true;
551
552 }
553 else
554 {
555 try
556 {
557 this.walkFid();
558 return true;
559 }
560 catch (StyxException ex)
561 {
562
563 return false;
564 }
565 }
566 }
567
568 /***
569 * Opens the file on the server, i.e. prepares the file for reading or
570 * writing. This blocks until the open is complete. Use of this method will
571 * not cause the fileOpen() events of registered change listeners to be fired.
572 * @param mode Integer representing the mode - see the constants in StyxUtils.
573 * For example, to open a file for reading, use StyxUtils.OREAD. To open a
574 * file for writing with truncation use StyxUtils.OWRITE | StyxUtils.OTRUNC.
575 */
576 public void open(int mode) throws StyxException
577 {
578 StyxReplyCallback callback = new StyxReplyCallback();
579 this.openAsync(mode, callback);
580
581
582
583 callback.getReply();
584 }
585
586 /***
587 * Opens the file on the server. This call does not block; the fileOpen()
588 * method of any registered change listeners will be called when the Ropen message
589 * arrives. The error() method of any registered change listeners will be
590 * called if an error occurs opening the file.
591 * @param mode Integer representing the mode - see the constants in StyxUtils.
592 * For example, to open a file for reading, use StyxUtils.OREAD. To open a
593 * file for writing with truncation use StyxUtils.OWRITE | StyxUtils.OTRUNC.
594 * @throws IllegalStateException if the file is already open
595 */
596 public synchronized void openAsync(int mode)
597 {
598 this.openAsync(mode, null);
599 }
600
601 /***
602 * Opens the file on the server. This call does not block; the replyArrived()
603 * method of the provided callback object will be called when the Ropen message
604 * arrives. The error() method of the provided callback will be called if
605 * an error occurred when opening the file, or if the file was already open
606 * under a different mode.
607 * @param mode Integer representing the mode - see the constants in StyxUtils.
608 * For example, to open a file for reading, use StyxUtils.OREAD. To open a
609 * file for writing with truncation use StyxUtils.OWRITE | StyxUtils.OTRUNC.
610 * @param callback The MessageCallback object that will handle the Ropen message
611 */
612 public void openAsync(int mode, MessageCallback callback)
613 {
614 new OpenCallback(this, mode, callback).nextStage();
615 }
616
617 /***
618 * Creates this file on the remote server, provided that its parent directory
619 * exists. Sends an error message to the callback if the file already exists.
620 */
621 public void createAsync(boolean isDirectory, int permissions, int mode,
622 MessageCallback callback)
623 {
624 new CreateCallback(this, isDirectory, permissions, mode, callback).nextStage();
625 }
626
627 /***
628 * Opens or creates this file: if the file exists it will be opened with
629 * the given mode. If it does not exist it will be created, provided that
630 * the parent directory exists. Files will be created with 0666 permissions
631 * and directories with 0777, subject to the permissions of the parent
632 * directory.
633 */
634 public void openOrCreateAsync(boolean isDirectory, int mode,
635 MessageCallback callback)
636 {
637 new OpenOrCreateCallback(this, isDirectory, mode, callback).nextStage();
638 }
639
640 /***
641 * Opens or creates this file: if the file exists it will be opened with
642 * the given mode. If it does not exist it will be created, provided that
643 * the parent directory exists. Files will be created with 0666 permissions
644 * and directories with 0777, subject to the permissions of the parent
645 * directory. Blocks until the opening or creation is complete
646 */
647 public void openOrCreate(boolean isDirectory, int mode) throws StyxException
648 {
649 StyxReplyCallback callback = new StyxReplyCallback();
650 this.openOrCreateAsync(isDirectory, mode, callback);
651
652 callback.getReply();
653 }
654
655 /***
656 * Closes the file (i.e. clunks the fid). If the fid isn't set, this
657 * will do nothing. This sends the Tclunk message but does not wait for a
658 * reply (this doesn't matter because the rules of Styx say that the fid
659 * of the file is invalid as soon as the Tclunk is sent, whether the Rclunk
660 * arrives or not). I.e. this method will not block. The Rclunk message
661 * is handled in the StyxConnection class.
662 */
663 public synchronized void close()
664 {
665 if (this.fid >= 0)
666 {
667
668
669
670
671 this.conn.sendAsync(new TclunkMessage(this.fid), null);
672 }
673
674
675
676 this.fid = -1;
677 this.ioUnit = 0;
678 this.mode = -1;
679 }
680
681 /***
682 * Reads a chunk of data from the file. Reads the maximum amount of data
683 * allowed in a single message, starting at the given file offset. Blocks
684 * until the server replies with the data.
685 *
686 * When you have finished with the data in the ByteBuffer that is returned,
687 * call release() on the buffer to ensure that the buffer can be re-used.
688 *
689 * @return a ByteBuffer containing the data that have been read. The position
690 * and limit of this buffer will be set correctly. Note that the position
691 * may not necessarily be zero (the buffer may well contain the entire contents
692 * of an RreadMessage, header and all).
693 */
694 public ByteBuffer read(long offset) throws StyxException
695 {
696 StyxReplyCallback callback = new StyxReplyCallback();
697 this.readAsync(offset, callback);
698 RreadMessage rReadMsg = (RreadMessage)callback.getReply();
699 return rReadMsg.getData();
700 }
701
702 /***
703 * Reads a chunk of data from the file. Reads the maximum amount of data
704 * allowed in a single message, starting at the given file offset. If the
705 * file was not open before this method is called, the file will be opened
706 * with mode StyxUtils.OREAD (but the fileOpen() event will not be fired).
707 * Returns immediately; the dataArrived() events of registered
708 * CStyxFileChangeListeners will be called when the data arrive, and the
709 * error() events of registered CStyxFileChangeListeners will be called if
710 * an error occurs.
711 */
712 public void readAsync(long offset)
713 {
714 this.readAsync(offset, false);
715 }
716
717 /***
718 * Reads a chunk of data from the file. Reads the maximum amount of data
719 * allowed in a single message, starting at the given file offset. If the
720 * file was not open before this method is called, the file will be opened
721 * (but the fileOpen() event will not be fired).
722 * Returns immediately; the dataArrived() events of registered
723 * CStyxFileChangeListeners will be called when the data arrive, and the
724 * error() events of registered CStyxFileChangeListeners will be called if
725 * an error occurs.
726 * @param offset The index of the first byte of data in the file to read
727 * @param openForWriting If this is true, the file will be opened for reading
728 * <em>and</em> writing with truncation, <em>provided that the file is not open already</em>.
729 * If this is false, the file will be opened for reading only
730 */
731 public void readAsync(long offset, boolean openForWriting)
732 {
733 this.readAsync(offset, -1, openForWriting, null);
734 }
735
736 /***
737 * Reads a chunk of data from the file. Reads the maximum number of bytes
738 * allowed in a single message, starting at the given file offset. If the
739 * file was not open before this method is called, the file will be opened
740 * for reading only (but the fileOpen() event will not be fired).
741 * Returns immediately; the replyArrived() events of the provided callback
742 * will be called when the data arrive, and the error() method of the
743 * callback will be called if an error occurs. When the reply arrives,
744 * the data will be contained in a ByteBuffer that is part of the RreadMessage:
745 * use RreadMessage.getData() to get the buffer. After you have finished with
746 * the buffer, call release() on the buffer to return it to the pool.
747 */
748 public void readAsync(long offset, MessageCallback callback)
749 {
750 this.readAsync(offset, -1, false, callback);
751 }
752
753 /***
754 * Reads a chunk of data from the file. Sends request to read the given
755 * number of bytes, starting at the given file offset. If bytesRequired < 0,
756 * the maximum number of bytes allowed in a single message will be read. If the
757 * file was not open before this method is called, the file will be opened
758 * for reading (but the fileOpen() event will not be fired).
759 * Returns immediately; the replyArrived() events of the provided callback
760 * will be called when the data arrive, and the error() method of the
761 * callback will be called if an error occurs. When the reply arrives,
762 * the data will be contained in a ByteBuffer that is part of the RreadMessage:
763 * use RreadMessage.getData() to get the buffer. After you have finished with
764 * the buffer, call release() on the buffer to return it to the pool.
765 * @param offset The index of the first byte of data in the file to read
766 * @param bytesRequired The maximum number of bytes required
767 * @param callback The class that will be notified if the read was successful,
768 * or if there was an error
769 */
770 public void readAsync(long offset, int bytesRequired, MessageCallback callback)
771 {
772 this.readAsync(offset, bytesRequired, false, callback);
773 }
774
775 /***
776 * Reads a chunk of data from the file. Sends request to read the given
777 * number of bytes, starting at the given file offset. If bytesRequired < 0,
778 * the maximum number of bytes allowed in a single message will be read.
779 * Returns immediately; the replyArrived() events of the provided callback
780 * will be called when the data arrive, and the error() method of the
781 * callback will be called if an error occurs. When the reply arrives,
782 * the data will be contained in a ByteBuffer that is part of the RreadMessage:
783 * use RreadMessage.getData() to get the buffer. After you have finished with
784 * the buffer, call release() on the buffer to return it to the pool.
785 * @param offset The index of the first byte of data in the file to read
786 * @param bytesRequired The maximum number of bytes required
787 * @param openForWriting If this is true, the file will be opened for reading
788 * <em>and</em> writing with truncation, <em>provided that the file is not open already</em>.
789 * If this is false, the file will be opened for reading only
790 * @param callback The class that will be notified if the read was successful,
791 * or if there was an error
792 */
793 public void readAsync(long offset, int bytesRequired, boolean openForWriting,
794 MessageCallback callback)
795 {
796 new ReadCallback(this, offset, bytesRequired, openForWriting, callback).nextStage();
797 }
798
799 /***
800 * Reads the entire contents of the file and returns them as a String.
801 * This should only be used for relatively short files that can sensibly
802 * fit into a String - do not use for large files as not only will this
803 * method block until the file is read, you may run into memory problems.
804 */
805 public String getContents() throws StyxException
806 {
807 boolean wasOpen = false;
808 if (this.isOpen())
809 {
810 wasOpen = true;
811 }
812 StringBuffer strBuf = new StringBuffer();
813 ByteBuffer buf;
814 byte[] arr = null;
815 long pos = 0;
816 boolean stillReading = true;
817 while(stillReading)
818 {
819
820 buf = this.read(pos);
821 if (buf.hasRemaining())
822 {
823
824 pos += buf.remaining();
825
826 strBuf.append(StyxUtils.dataToString(buf));
827
828 buf.release();
829 }
830 else
831 {
832
833 stillReading = false;
834 }
835 }
836 if (!wasOpen)
837 {
838
839 this.close();
840 }
841 return strBuf.toString();
842 }
843
844 /***
845 * Sets the contents of the file to the given string. Overwrites anything
846 * else in the file. Writes EOF and closes the file after use unless the file was open
847 * before this method was called. If the file was open before this message
848 * is called, it must be open for writing with truncation
849 * (i.e. StyxUtils.OWRITE | StyxUtils.OTRUNC)
850 * @param str The new file contents
851 * @throws StyxException if there was an error opening or writing to the file
852 */
853 public void setContents(String str) throws StyxException
854 {
855 boolean wasOpen = true;
856 if (this.mode < 0)
857 {
858
859 this.open(StyxUtils.OWRITE | StyxUtils.OTRUNC);
860 wasOpen = false;
861 }
862
863 byte[] bytes = StyxUtils.strToUTF8(str);
864
865
866
867 this.writeAll(bytes, 0);
868
869 if (!wasOpen)
870 {
871
872 this.write(new byte[0], bytes.length, true);
873 this.close();
874 }
875 }
876
877 /***
878 * Writes a block of data to the file at the given offset. Will write the data
879 * in several separate messages if necessary. The file will be truncated at
880 * the end of the new data.
881 * @throws StyxException if there is an error writing to the file
882 */
883 public void writeAll(byte[] bytes, long offset) throws StyxException
884 {
885
886 long filePos = offset;
887
888 int pos = 0;
889 do
890 {
891
892 int bytesRemaining = bytes.length - pos;
893
894
895 int bytesToWrite = Math.min(bytesRemaining, this.ioUnit);
896
897
898 int bytesWritten = (int)this.write(bytes, pos, bytesToWrite, filePos, true);
899
900
901 pos += bytesWritten;
902 filePos += bytesWritten;
903
904 } while (pos < bytes.length);
905 }
906
907 /***
908 * Writes a block of data to the file at the given offset. Cannot write more
909 * than this.getIOUnit() bytes in a single message. Blocks until the write
910 * confirmation arrives. Does not change the offset of the file
911 * @param bytes The data to write. Will attempt to write all the data in this array.
912 * @param offset The position in the file at which to write the data
913 * @param truncate True if the file is to be truncated at the end of the
914 * new data
915 * @return The number of bytes written to the file
916 * @throws StyxException if there is an error writing to the file
917 */
918 public long write(byte[] bytes, long offset, boolean truncate) throws StyxException
919 {
920 return this.write(bytes, 0, bytes.length, offset, truncate);
921 }
922
923 /***
924 * Writes a block of data to the file at the given offset. Cannot write more
925 * than this.getIOUnit() bytes in a single message. Blocks until the write
926 * confirmation arrives. Does not change the offset of the file
927 * @param bytes The data to write.
928 * @param pos The index of the first data point in the byte array to write
929 * @param count The number of bytes from the input array to write
930 * @param offset The position in the file at which to write the data
931 * @param truncate True if the file is to be truncated at the end of the
932 * new data
933 * @return The number of bytes written to the file
934 * @throws StyxException if there is an error writing to the file
935 */
936 public long write(byte[] bytes, int pos, int count, long offset,
937 boolean truncate) throws StyxException
938 {
939 StyxReplyCallback callback = new StyxReplyCallback();
940 this.writeAsync(bytes, pos, count, offset, truncate, callback);
941 RwriteMessage rWriteMsg = (RwriteMessage)callback.getReply();
942 return rWriteMsg.getNumBytesWritten();
943 }
944
945 /***
946 * Writes a string to the file at the given offset, with truncation. When the write
947 * confirmation arrives, the dataWritten() method of any registered
948 * CStyxFileChangeListeners will be called. The file will be truncated
949 * at the end of the string.
950 */
951 public void writeAsync(String str, long offset)
952 {
953 this.writeAsync(str, offset, null);
954 }
955
956 /***
957 * Writes a string to the file at the given offset, with truncation. When the write
958 * confirmation arrives, the callback's replyArrived() method will be called.
959 * The callback's error() method will be called if an error occurs. The file
960 * will be truncated at the end of the string.
961 */
962 public void writeAsync(String str, long offset, MessageCallback callback)
963 {
964 this.writeAsync(StyxUtils.strToUTF8(str), offset, true, callback);
965 }
966
967 /***
968 * Writes a chunk of data to the file at the given file offset.
969 * Returns immediately; the dataWritten() method of any waiting change listeners
970 * will be called when the write confirmation arrives, and the error() method
971 * of any waiting change listeners will be called if an error occurs.
972 * @param bytes The byte array containing the data to write. This will attempt to
973 * write all the data in the array
974 * @param offset The position in the file at which the data will be written
975 * @param truncate True if the file should be truncated at the end of the new data
976 */
977 public void writeAsync(byte[] bytes, long offset, boolean truncate)
978 {
979 this.writeAsync(bytes, offset, truncate, null);
980 }
981
982 /***
983 * Writes a chunk of data to the file at the given file offset.
984 * Returns immediately; the callback's replyArrived() method will be called
985 * when the reply arrives and the callback's error() method will be called
986 * if an error occurs.
987 * @param bytes The array of bytes to write
988 * @param offset The position in the file at which the data will be written
989 * @param truncate If this is true, the file will be truncated at the end
990 * of the new data
991 * @param callback The replyArrived() method of this callback object will be
992 * called when the write confirmation arrives
993 */
994 public void writeAsync(byte[] bytes, long offset, boolean truncate,
995 MessageCallback callback)
996 {
997 this.writeAsync(bytes, 0, bytes.length, offset, truncate, callback);
998 }
999
1000 /***
1001 * Writes a chunk of data to the file at the given file offset.
1002 * Returns immediately; the callback's replyArrived() method will be called
1003 * when the reply arrives and the callback's error() method will be called
1004 * if an error occurs.
1005 * @param bytes The array of bytes to write
1006 * @param pos The position in the array of the first byte to write
1007 * @param count The number of bytes from the byte array to write
1008 * @param offset The position in the file at which the data will be written
1009 * @param truncate If this is true, the file will be truncated at the end
1010 * of the new data
1011 * @param callback The replyArrived() method of this callback object will be
1012 * called when the write confirmation arrives
1013 */
1014 public void writeAsync(byte[] bytes, int pos, int count, long offset,
1015 boolean truncate, MessageCallback callback)
1016 {
1017 new WriteCallback(this, bytes, pos, count, offset, truncate, callback).nextStage();
1018 }
1019
1020 /***
1021 * Refreshes the stat (DirEntry) of the file. Blocks until the operation
1022 * is complete. Does not fire the statChanged() event on any registered
1023 * change listeners.
1024 */
1025 public void refresh() throws StyxException
1026 {
1027 StyxReplyCallback callback = new StyxReplyCallback();
1028 this.refreshAsync(callback);
1029 RstatMessage rStatMsg = (RstatMessage)callback.getReply();
1030
1031 }
1032
1033 /***
1034 * Refreshes the status of the file by sending a TStatMessage. Does not wait
1035 * for a reply; when the reply arrives, the dirEntry of this file will be
1036 * set and the statChanged() event of any registered change listeners will be
1037 * fired.
1038 * @todo In the case of a directory, should this refresh the list of children?
1039 */
1040 public void refreshAsync()
1041 {
1042 this.refreshAsync(null);
1043 }
1044
1045 /***
1046 * Refreshes the status of the file by sending a TstatMessage. Does not wait
1047 * for a reply; when the reply arrives, the dirEntry of this file will be
1048 * set and the replyArrived() method of the provided callback object
1049 * will be called with an RwalkMessage as the argument.
1050 * @todo In the case of a directory, should this refresh the list of children?
1051 * @todo If there is already a Tstat message in flight, what should we do?
1052 * Does it matter much?
1053 */
1054 public void refreshAsync(MessageCallback callback)
1055 {
1056 new RefreshCallback(this, callback).nextStage();
1057 }
1058
1059 /***
1060 * Gets a reference to a file on the same connection as this CStyxFile.
1061 * This does not open, create or check the existence of the file: no
1062 * messages are sent to the server in this method so this will never block.
1063 * @param path The path of the file to be opened, <i>relative to this file</i>.
1064 * @throws InvalidPathException if the path is not valid
1065 */
1066 public CStyxFile getFile(String path)
1067 {
1068 StringBuffer fullPath = new StringBuffer(this.getPath());
1069 if (!this.getPath().endsWith("/"))
1070 {
1071 fullPath.append("/");
1072 }
1073 fullPath.append(path);
1074 return this.conn.getFile(fullPath.toString());
1075 }
1076
1077 /***
1078 * Gets a file on the same connection as this CStyxFile, then opens it. This
1079 * blocks until the file is opened.
1080 * @param path The path of the file to be opened, <i>relative to this file</i>.
1081 * @param mode The mode with which to open the file
1082 * @throws StyxException if the file could not be opened with the given mode
1083 */
1084 public CStyxFile openFile(String path, int mode) throws StyxException
1085 {
1086 CStyxFile sf = this.getFile(path);
1087 sf.open(mode);
1088 return sf;
1089 }
1090
1091 /***
1092 * Gets all the children of this directory. If this is not a directory,
1093 * this will return null (TODO: does it? Or does it throw an Exception?).
1094 * (For an empty directory, this will return a zero- length array.)
1095 * This method blocks until the directory has been read.
1096 */
1097 public CStyxFile[] getChildren() throws StyxException
1098 {
1099 StyxReplyCallback callback = new StyxReplyCallback();
1100 this.getChildrenAsync(callback);
1101
1102
1103
1104
1105 RreadMessage rReadMsg = (RreadMessage)callback.getReply();
1106
1107 return this.children;
1108 }
1109
1110 /***
1111 * Reads this directory to get all its children. When the process is completed
1112 * the childrenFound() event is fired on all registered listeners.
1113 */
1114 public void getChildrenAsync()
1115 {
1116 this.getChildrenAsync(null);
1117 }
1118
1119 /***
1120 * Reads this directory to get all its children. When the process is completed
1121 * the replyArrived() event is fired on the callback, with a zero-length
1122 * RreadMessage as the argument.
1123 */
1124 private void getChildrenAsync(MessageCallback callback)
1125 {
1126 new GetChildrenCallback(this, callback).nextStage();
1127 }
1128
1129 /***
1130 * Downloads the data from this file and writes to a local java.io.File.
1131 * If this file already exists it will be overwritten.
1132 * This method blocks; it returns when the download is complete and throws
1133 * a StyxException if an error occurs.
1134 * @param file The java.io.File to which the data will be written. If this
1135 * file already exists it will be overwritten. If this is null, the data
1136 * will be downloaded but not written to a file
1137 */
1138 public void download(File file) throws StyxException
1139 {
1140 StyxReplyCallback callback = new StyxReplyCallback();
1141 this.downloadAsync(file, callback);
1142
1143 StyxMessage message = callback.getReply();
1144 }
1145
1146 /***
1147 * Downloads the data from this file and writes to a local java.io.File.
1148 * If this file already exists it will be overwritten.
1149 * This method blocks; it returns when the download is complete and throws
1150 * a StyxException if an error occurs.
1151 * @param file The java.io.File to which the data will be written. If this
1152 * file already exists it will be overwritten. If this is null, the data
1153 * will be downloaded but not written to a file
1154 * @param numRequests The number of simultaneous read requests that will be
1155 * sent. The download may proceed faster with a larger number of simultaneous
1156 * read requests. If numRequests > 1, the file on the remote server <b>must</b>
1157 * be seekable otherwise unpredictable behaviour may result.
1158 */
1159 public void download(File file, int numRequests) throws StyxException
1160 {
1161 StyxReplyCallback callback = new StyxReplyCallback();
1162 this.downloadAsync(file, numRequests, callback);
1163
1164 StyxMessage message = callback.getReply();
1165 }
1166
1167 /***
1168 * Downloads the data from this file and writes to a local java.io.File.
1169 * This method returns immediately; when the download has finished, the
1170 * downloadComplete() event will be fired all all registered change listeners.
1171 * @param file The java.io.File to which the data will be written. If this
1172 * file already exists it will be overwritten.
1173 * @param numRequests The number of simultaneous read requests that will be
1174 * sent. The download may proceed faster with a larger number of simultaneous
1175 * read requests. If numRequests > 1, the file on the remote server <b>must</b>
1176 * be seekable otherwise unpredictable behaviour may result.
1177 */
1178 public void downloadAsync(File file, int numRequests)
1179 {
1180 this.downloadAsync(file, numRequests, null);
1181 }
1182
1183 /***
1184 * Downloads the data from this file and writes to a local java.io.File.
1185 * This method returns immediately; when the download has finished, the
1186 * downloadComplete() event will be fired all all registered change listeners.
1187 * @param file The java.io.File to which the data will be written. If this
1188 * file already exists it will be overwritten.
1189 */
1190 public void downloadAsync(File file)
1191 {
1192 this.downloadAsync(file, null);
1193 }
1194
1195 /***
1196 * Downloads the data from this file and writes to a local java.io.File.
1197 * This method returns immediately; when the download has finished, the
1198 * replyArrived() method of the given MessageCallback will be called.
1199 * @param file The java.io.File to which the data will be written. If this
1200 * file already exists it will be overwritten.
1201 */
1202 public void downloadAsync(File file, MessageCallback callback)
1203 {
1204 this.downloadAsync(file, 1, callback);
1205 }
1206
1207 /***
1208 * Downloads the data from this file and writes to a local java.io.File.
1209 * This method returns immediately; when the download has finished, the
1210 * replyArrived() method of the given MessageCallback will be called.
1211 * @param file The java.io.File to which the data will be written. If this
1212 * file already exists it will be overwritten.
1213 * @param numRequests The number of simultaneous read requests that will be
1214 * sent. The download may proceed faster with a larger number of simultaneous
1215 * read requests. If numRequests > 1, the file on the remote server <b>must</b>
1216 * be seekable otherwise unpredictable behaviour may result.
1217 * @throws IllegalArgumentException if numRequests is less than 1 or greater
1218 * than 100.
1219 */
1220 public void downloadAsync(File file, int numRequests, MessageCallback callback)
1221 {
1222 if (numRequests < 1 || numRequests > 100)
1223 {
1224 throw new IllegalArgumentException("numRequests must be between 1 and 100 inclusive");
1225 }
1226 new DownloadCallback(this, file, numRequests, callback).nextStage();
1227 }
1228
1229 /***
1230 * Uploads data from an InputStream to this file. If this file does
1231 * not exist it will be created with rw-rw-rw- (0666) permissions, subject
1232 * to the permissions of the host directory. Blocks until the file has been
1233 * uploaded, or throws a StyxException if an error occurred. After the
1234 * upload is complete, the InputStream will <b>not</b> be closed.
1235 * @param in The InputStream from which to read data to be written to this file
1236 * @todo Add a flag to prevent overwriting a file if it already exists?
1237 * @todo Allow a callback to be provided for progress monitoring?
1238 */
1239 public void upload(InputStream in) throws StyxException
1240 {
1241 StyxReplyCallback callback = new StyxReplyCallback();
1242 this.uploadAsync(in, callback);
1243
1244 StyxMessage message = callback.getReply();
1245 }
1246
1247 /***
1248 * Uploads data from an local file to this file. If this (Styx) file does
1249 * not exist it will be created with rw-rw-rw- (0666) permissions, subject
1250 * to the permissions of the host directory. Blocks until the file has been
1251 * uploaded, or throws a StyxException if an error occurred.
1252 * @param fromFile The File from which to read data to be written to this file
1253 * @todo Add a flag to prevent overwriting a file if it already exists?
1254 * @todo Allow a callback to be provided for progress monitoring?
1255 */
1256 public void upload(File fromFile) throws StyxException
1257 {
1258 StyxReplyCallback callback = new StyxReplyCallback();
1259 this.uploadAsync(fromFile, callback);
1260
1261 StyxMessage message = callback.getReply();
1262 }
1263
1264 /***
1265 * Uploads data from an InputStream to this file. If this file does
1266 * not exist it will be created with rw-rw-rw- (0666) permissions, subject
1267 * to the permissions of the host directory. When the process is finished,
1268 * the uploadComplete() event will be fired on all registered
1269 * CStyxFileChangeListeners. If an error occurs, the error() event will be
1270 * fired on registered change listeners. After the
1271 * upload is complete, the InputStream will <b>not</b> be closed.
1272 * @param in The InputStream from which to read data to be written to this file
1273 * @todo Add a flag to prevent overwriting a file if it already exists?
1274 * @todo Allow a callback to be provided for progress monitoring?
1275 */
1276 public void uploadAsync(InputStream in)
1277 {
1278 this.uploadAsync(in, null);
1279 }
1280
1281 /***
1282 * Uploads data from an InputStream to this file. If this file does
1283 * not exist it will be created with rw-rw-rw- (0666) permissions, subject
1284 * to the permissions of the host directory. When the process is finished,
1285 * the replyArrived() method of the provided callback object will be called.
1286 * If an error occurs, the error() method of the provided callback object
1287 * will be called.
1288 * @param in The InputStream from which to read data to be written to this file
1289 * @param callback The MessageCallback that will be notified when the
1290 * upload process is complete.
1291 * @todo Add a flag to prevent overwriting a file if it already exists?
1292 * @todo Allow a callback to be provided for progress monitoring?
1293 */
1294 public void uploadAsync(InputStream in, MessageCallback callback)
1295 {
1296 new UploadCallback(this, in, callback).nextStage(null, null);
1297 }
1298
1299 /***
1300 * Uploads data from a local java.io.File to this file. If this (Styx) file does
1301 * not exist it will be created with rw-rw-rw- (0666) permissions, subject
1302 * to the permissions of the host directory. When the process is finished,
1303 * the uploadComplete() event will be fired on all registered
1304 * CStyxFileChangeListeners. If an error occurs, the error() event will be
1305 * fired on registered change listeners.
1306 * @param file The File to copy/upload
1307 * @todo Add a flag to prevent overwriting a file if it already exists?
1308 * @todo Allow a callback to be provided for progress monitoring?
1309 */
1310 public void uploadAsync(File file)
1311 {
1312 this.uploadAsync(file, null);
1313 }
1314
1315 /***
1316 * Uploads data from a local java.io.File to this file. If this (Styx) file does
1317 * not exist it will be created with rw-rw-rw- (0666) permissions, subject
1318 * to the permissions of the host directory. When the process is finished,
1319 * the replyArrived() method of the provided callback object will be called.
1320 * If an error occurs, the error() method of the provided callback object
1321 * will be called.
1322 * @param file The File to copy/upload
1323 * @param callback The MessageCallback that will be notified when the
1324 * upload process is complete.
1325 * @todo Add a flag to prevent overwriting a file if it already exists?
1326 * @todo Allow a callback to be provided for progress monitoring?
1327 */
1328 public void uploadAsync(File file, MessageCallback callback)
1329 {
1330 new UploadCallback(this, file, callback).nextStage(null, null);
1331 }
1332
1333 /***
1334 * Adds a CStyxFileChangeListener to this file. The methods of the change
1335 * listener will be called when events happen, such as new data arriving
1336 * or a change in permissions, etc (note that this notification will only
1337 * happen if the changes are made by calling methods of this CStyxFile
1338 * instance. If another client makes changes, we will not be notified here).
1339 * If this listener is already registered, this method does nothing.
1340 */
1341 public void addChangeListener(CStyxFileChangeListener listener)
1342 {
1343 synchronized(this.listeners)
1344 {
1345 if (!this.listeners.contains(listener))
1346 {
1347 this.listeners.add(listener);
1348 }
1349 }
1350 }
1351
1352 /***
1353 * Removes the given change listener
1354 */
1355 public void removeChangeListener(CStyxFileChangeListener listener)
1356 {
1357 this.listeners.remove(listener);
1358 }
1359
1360 /***
1361 * Fires the error() method on all registered listeners
1362 */
1363 public void fireError(String message)
1364 {
1365 synchronized(this.listeners)
1366 {
1367 for (int i = 0; i < listeners.size(); i++)
1368 {
1369 CStyxFileChangeListener listener =
1370 (CStyxFileChangeListener)this.listeners.get(i);
1371 listener.error(this, message);
1372 }
1373 }
1374 }
1375
1376 /***
1377 * Fires the fileOpen() method on all registered listeners
1378 */
1379 public void fireOpen()
1380 {
1381 synchronized(this.listeners)
1382 {
1383 for (int i = 0; i < listeners.size(); i++)
1384 {
1385 CStyxFileChangeListener listener =
1386 (CStyxFileChangeListener)this.listeners.get(i);
1387 listener.fileOpen(this, mode);
1388 }
1389 }
1390 }
1391
1392 /***
1393 * Fires the fileCreated() method on all registered listeners
1394 */
1395 public void fireCreated()
1396 {
1397 synchronized(this.listeners)
1398 {
1399 for (int i = 0; i < listeners.size(); i++)
1400 {
1401 CStyxFileChangeListener listener =
1402 (CStyxFileChangeListener)this.listeners.get(i);
1403 listener.fileCreated(this, this.mode);
1404 }
1405 }
1406 }
1407
1408 /***
1409 * Fires the dataArrived() method on all registered listeners
1410 */
1411 public void fireDataArrived(TreadMessage tReadMsg, RreadMessage rReadMsg)
1412 {
1413 synchronized(this.listeners)
1414 {
1415 ByteBuffer data = rReadMsg.getData();
1416
1417 int pos = data.position();
1418 int limit = data.limit();
1419 for (int i = 0; i < listeners.size(); i++)
1420 {
1421 CStyxFileChangeListener listener =
1422 (CStyxFileChangeListener)this.listeners.get(i);
1423 listener.dataArrived(this, tReadMsg, data);
1424
1425
1426 data.position(pos);
1427 data.limit(limit);
1428 }
1429
1430 data.release();
1431 }
1432 }
1433
1434 /***
1435 * Fires the statChanged() method on all registered listeners
1436 */
1437 public void fireStatChanged(RstatMessage rStatMsg)
1438 {
1439 this.setDirEntry(rStatMsg.getDirEntry());
1440
1441 synchronized(this.listeners)
1442 {
1443 for (int i = 0; i < listeners.size(); i++)
1444 {
1445 CStyxFileChangeListener listener =
1446 (CStyxFileChangeListener)this.listeners.get(i);
1447 listener.statChanged(this, dirEntry);
1448 }
1449 }
1450 }
1451
1452 /***
1453 * Fires the dataWritten() method on all registered listeners
1454 */
1455 public void fireDataWritten(TwriteMessage tWriteMsg)
1456 {
1457
1458 synchronized(this.listeners)
1459 {
1460 for (int i = 0; i < listeners.size(); i++)
1461 {
1462 CStyxFileChangeListener listener =
1463 (CStyxFileChangeListener)this.listeners.get(i);
1464 listener.dataWritten(this, tWriteMsg);
1465 }
1466 }
1467 }
1468
1469 /***
1470 * Fires the childrenFound() method on all registered listeners
1471 */
1472 public void fireChildrenFound()
1473 {
1474 synchronized(this.listeners)
1475 {
1476 for (int i = 0; i < listeners.size(); i++)
1477 {
1478 CStyxFileChangeListener listener =
1479 (CStyxFileChangeListener)this.listeners.get(i);
1480 listener.childrenFound(this, this.children);
1481 }
1482 }
1483 }
1484
1485 /***
1486 * Fires the uploadComplete() method on all registered listeners
1487 */
1488 public void fireUploadComplete()
1489 {
1490 synchronized(this.listeners)
1491 {
1492 for (int i = 0; i < listeners.size(); i++)
1493 {
1494 CStyxFileChangeListener listener =
1495 (CStyxFileChangeListener)this.listeners.get(i);
1496 listener.uploadComplete(this);
1497 }
1498 }
1499 }
1500
1501 /***
1502 * Fires the downloadComplete() method on all registered listeners
1503 */
1504 public void fireDownloadComplete()
1505 {
1506 synchronized(this.listeners)
1507 {
1508 for (int i = 0; i < listeners.size(); i++)
1509 {
1510 CStyxFileChangeListener listener =
1511 (CStyxFileChangeListener)this.listeners.get(i);
1512 listener.downloadComplete(this);
1513 }
1514 }
1515 }
1516
1517 /***
1518 * Gets the canonical version of the given path. Removes all duplicate
1519 * slashes, collapses all "." and ".." and checks that the path is
1520 * valid and absolute, throwing an InvalidPathException if not. This canonical
1521 * path is used as the key for the Hashtable of known CStyxFiles.
1522 * @todo Move to StyxUtils?
1523 */
1524 private static String getCanonicalPath(String path) throws InvalidPathException
1525 {
1526 if (!path.startsWith("/"))
1527 {
1528 throw new InvalidPathException("Path must be absolute");
1529 }
1530 Vector canonicalPathEls = new Vector();
1531 String[] pathEls = path.split("/");
1532 for (int i = 0; i < pathEls.length; i++)
1533 {
1534
1535 if (!pathEls[i].equals("."))
1536 {
1537
1538 if (pathEls[i].equals(".."))
1539 {
1540 if (canonicalPathEls.size() > 0)
1541 {
1542 canonicalPathEls.remove(canonicalPathEls.size() - 1);
1543 }
1544 }
1545 else
1546 {
1547
1548 if ((pathEls[i].indexOf(" ") != -1) ||
1549 (pathEls[i].indexOf("//") != -1))
1550 {
1551 throw new InvalidPathException("'" + pathEls[i] +
1552 "' is an invalid filename");
1553 }
1554 else
1555 {
1556
1557 if (!pathEls[i].equals(""))
1558 {
1559 canonicalPathEls.add(pathEls[i]);
1560 }
1561 }
1562 }
1563 }
1564 }
1565
1566 StringBuffer canonicalPath = new StringBuffer("/");
1567 for (int i = 0; i < canonicalPathEls.size(); i++)
1568 {
1569 canonicalPath.append(canonicalPathEls.get(i));
1570 if (i < canonicalPathEls.size() - 1)
1571 {
1572 canonicalPath.append("/");
1573 }
1574 }
1575 return canonicalPath.toString();
1576 }
1577
1578 /***
1579 * @return The name of the file (not the full path).
1580 */
1581 public String toString()
1582 {
1583 return this.getName();
1584 }
1585
1586 public static void main(String[] args)
1587 {
1588 StyxConnection conn = new StyxConnection("localhost", 9999);
1589 try
1590 {
1591 conn.connect();
1592 CStyxFile file = conn.getFile("hello.txt");
1593 System.out.println(file.getContents());
1594 file.setContents("goodbye from client");
1595 }
1596 catch(StyxException se)
1597 {
1598 se.printStackTrace();
1599 }
1600 finally
1601 {
1602 conn.close();
1603 }
1604 }
1605 }