View Javadoc

1   /*
2    * Copyright (c) 2005 The University of Reading
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    * 1. Redistributions of source code must retain the above copyright
9    *    notice, this list of conditions and the following disclaimer.
10   * 2. Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in the
12   *    documentation and/or other materials provided with the distribution.
13   * 3. Neither the name of the University of Reading, nor the names of the
14   *    authors or contributors may be used to endorse or promote products
15   *    derived from this software without specific prior written permission.
16   *
17   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   */
28  
29  package uk.ac.rdg.resc.jstyx.client.callbacks;
30  
31  import java.io.File;
32  import java.nio.channels.FileChannel;
33  import java.io.RandomAccessFile;
34  import java.io.FileNotFoundException;
35  import java.io.IOException;
36  
37  import uk.ac.rdg.resc.jstyx.client.StyxConnection;
38  import uk.ac.rdg.resc.jstyx.client.CStyxFile;
39  import uk.ac.rdg.resc.jstyx.client.MessageCallback;
40  
41  import uk.ac.rdg.resc.jstyx.messages.StyxMessage;
42  import uk.ac.rdg.resc.jstyx.messages.RopenMessage;
43  import uk.ac.rdg.resc.jstyx.messages.TreadMessage;
44  import uk.ac.rdg.resc.jstyx.messages.RreadMessage;
45  
46  import uk.ac.rdg.resc.jstyx.StyxUtils;
47  
48  /***
49   * Callback that is used when downloading a file from the server. Contains 
50   * state that needs to persist between message exchanges with the server. See
51   * CStyxFile.downloadAsync()
52   *
53   * @author jdb
54   * $Revision: 442 $
55   * $Date: 2005-11-03 07:39:45 +0000 (Thu, 03 Nov 2005) $
56   * $Log$
57   * Revision 1.2  2005/11/03 07:39:45  jonblower
58   * Bug fixes
59   *
60   * Revision 1.1  2005/08/05 13:46:40  jonblower
61   * Factored out all callback objects from CStyxFile into separate classes
62   *
63   */
64  
65  public class DownloadCallback extends MessageCallback
66  {
67      private MessageCallback callback;
68      private File localFile;
69      private int numRequests;
70      private FileChannel fout;
71      private long offset;
72      private boolean eof;
73      private int numOutstandingMessages;
74      private long bytesDownloaded;
75      private CStyxFile file;
76      private StyxConnection conn;
77  
78      public DownloadCallback(CStyxFile file, File localFile, int numRequests,
79          MessageCallback callback)
80      {
81          this.file = file;
82          this.conn = this.file.getConnection();
83          this.callback = callback;
84          this.localFile = localFile;
85          this.numRequests = numRequests;
86          this.fout = null;
87          this.offset = 0;
88          this.eof = false;
89          this.numOutstandingMessages = 0;
90          this.bytesDownloaded = 0;
91      }
92  
93      public void nextStage()
94      {
95          if (this.file.isOpen())
96          {
97              try
98              {
99                  // The file is open. Open the output stream for the local file
100                 if (this.fout == null)
101                 {
102                     if (this.localFile != null)
103                     {
104                         this.fout = new RandomAccessFile(this.localFile, "rw").getChannel();
105                         // This truncation is not necessary if we have just
106                         // created the file
107                         this.fout.truncate(0);
108                     }
109                 }
110                 // Now send a bunch of Tread messages to start the ball rolling
111                 for (int i = 0; i < this.numRequests; i++)
112                 {
113                     this.readNextChunk();
114                 }
115             }
116             catch (FileNotFoundException fnfe)
117             {
118                 this.error("cannot open " + this.localFile + " for writing", null);
119             }
120             catch (IOException ioe)
121             {
122                 this.error("cannot truncate " + this.localFile + ": " +
123                     ioe.getMessage(), null);
124             }
125         }
126         else
127         {
128             // We must open the file
129             this.file.openAsync(StyxUtils.OREAD, this);
130         }
131     }
132 
133     private synchronized void readNextChunk()
134     {
135         if (!this.eof)
136         {
137             // Read the maximum number of bytes allowed in a single message
138             // (i.e. ioUnit)
139             this.file.readAsync(this.offset, this);
140             this.offset += this.file.getIoUnit();
141             this.numOutstandingMessages++;
142         }
143     }
144 
145     public void replyArrived(StyxMessage rMessage, StyxMessage tMessage)
146     {
147         if (rMessage instanceof RopenMessage)
148         {
149             // We have just opened the file.
150             this.nextStage();
151         }
152         else if (rMessage instanceof RreadMessage)
153         {
154             this.numOutstandingMessages--;
155             RreadMessage rReadMsg = (RreadMessage)rMessage;
156             TreadMessage tReadMsg = (TreadMessage)tMessage;
157             if (rReadMsg.getCount() != 0)
158             {
159                 this.bytesDownloaded += rReadMsg.getCount();
160                 try
161                 {
162                     if (rReadMsg.getCount() == tReadMsg.getCount())
163                     {
164                         // We have got all the bytes we asked for so read the
165                         // next chunk
166                         this.readNextChunk();
167                     }
168                     else
169                     {
170                         // We didn't get all the bytes we asked for so we'll
171                         // have to make another request for the remaining
172                         // bytes.  Hopefully this won't happen very often
173                         long pos = tReadMsg.getOffset().asLong() + rReadMsg.getCount();
174                         int bytesRequired = tReadMsg.getCount() - rReadMsg.getCount();
175                         synchronized (this)
176                         {
177                             this.file.readAsync(pos, bytesRequired, this);
178                             this.numOutstandingMessages++;
179                         }
180                     }
181                     if (this.fout != null)
182                     {
183                         // Write the data to the output file at the right file position
184                         fout.write(rReadMsg.getData().buf(), tReadMsg.getOffset().asLong());
185                     }
186                 }
187                 catch(IOException ioe)
188                 {
189                     this.error("error writing to " + this.file + ": " +
190                         ioe.getMessage(), null);
191                 }
192             }
193             else
194             {
195                 //System.err.println("Got Rread message with zero bytes");
196                 // We have reached EOF
197                 this.eof = true;
198             }
199             if (this.eof && this.numOutstandingMessages == 0)
200             {
201                 //System.err.println("Bytes downloaded: " + this.bytesDownloaded);
202                 // There are no more outstanding messages
203                 this.closeFile();
204                 if (this.callback == null)
205                 {
206                     this.file.fireDownloadComplete();
207                 }
208                 else
209                 {
210                     callback.replyArrived(rMessage, tMessage);
211                 }
212             }
213         }
214     }
215 
216     private void closeFile()
217     {
218         if (this.fout != null)
219         {
220             try
221             {
222                 this.fout.close();
223             }
224             catch(IOException ioe)
225             {
226                 CStyxFile.getLogger().debug("IOException when closing " +
227                     this.file.getPath() + ": " + ioe.getMessage());
228             }
229         }
230     }
231 
232     public void error(String message, StyxMessage tMessage)
233     {
234         // TODO: must stop error() being called multiple times
235         this.closeFile();
236         String errMsg = "Error downloading from " + this.file.getPath() + ": " + message;
237         if (this.callback == null)
238         {
239             this.file.fireError(errMsg);
240         }
241         else
242         {
243             this.callback.error(errMsg, tMessage);
244         }
245     }
246 }