blob: 20b0154435c280f001a3ac71d52f81c7e7470b16 [file] [log] [blame]
Constantin Kaplinsky2859fdc2008-06-19 16:07:52 +00001//
2// Copyright (C) 2008 Wimba, Inc. All Rights Reserved.
3//
4// This is free software; you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation; either version 2 of the License, or
7// (at your option) any later version.
8//
9// This software is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this software; if not, write to the Free Software
16// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17// USA.
18//
19
20//
21// FbsConnection.java
22//
23
24package com.tightvnc.rfbplayer;
25
26import java.io.*;
27import java.net.*;
28import java.applet.Applet;
29
30public class FbsConnection {
31
32 URL fbsURL;
33 URL fbiURL;
34 URL fbkURL;
35
Constantin Kaplinskyf338d2c2008-06-20 11:53:19 +000036 /** Index data loaded from the .fbi file. */
Constantin Kaplinsky5ba167a2008-06-20 12:24:52 +000037 FbsEntryPoint[] indexData;
Constantin Kaplinskybe68e7f2008-06-20 12:21:57 +000038 int numIndexRecords;
Constantin Kaplinskyade425a2008-06-20 06:44:03 +000039
Constantin Kaplinsky2859fdc2008-06-19 16:07:52 +000040 FbsConnection(String fbsLocation, String indexLocationPrefix, Applet applet)
41 throws MalformedURLException {
42
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +000043 // Construct URLs from strings.
Constantin Kaplinsky2859fdc2008-06-19 16:07:52 +000044 URL base = null;
45 if (applet != null) {
46 base = applet.getCodeBase();
47 }
48 fbsURL = new URL(base, fbsLocation);
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +000049 fbiURL = fbkURL = null;
Constantin Kaplinsky2859fdc2008-06-19 16:07:52 +000050 if (indexLocationPrefix != null) {
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +000051 try {
52 fbiURL = new URL(base, indexLocationPrefix + ".fbi");
53 fbkURL = new URL(base, indexLocationPrefix + ".fbk");
54 } catch (MalformedURLException e) {
55 fbiURL = fbkURL = null;
56 }
Constantin Kaplinsky2859fdc2008-06-19 16:07:52 +000057 }
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +000058
59 // Try to load the .fbi index file.
Constantin Kaplinsky5ba167a2008-06-20 12:24:52 +000060 indexData = null;
Constantin Kaplinskybe68e7f2008-06-20 12:21:57 +000061 numIndexRecords = 0;
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +000062 loadIndex();
Constantin Kaplinsky2859fdc2008-06-19 16:07:52 +000063 }
64
65 FbsInputStream connect(long timeOffset) throws IOException {
Constantin Kaplinsky1c2865f2008-06-20 18:50:27 +000066 FbsInputStream fbs = null;
67
Constantin Kaplinsky38f044f2008-06-20 12:57:37 +000068 // Try efficient seeking first.
69 if (timeOffset > 0 && indexData != null && numIndexRecords > 0) {
70 int i = 0;
71 while (i < numIndexRecords && indexData[i].timestamp <= timeOffset) {
72 i++;
73 }
74 if (i > 0) {
75 FbsEntryPoint entryPoint = indexData[i - 1];
76 if (entryPoint.key_size < entryPoint.fbs_fpos) {
Constantin Kaplinsky1c2865f2008-06-20 18:50:27 +000077 try {
78 fbs = openFbsFile(entryPoint);
79 } catch (IOException e) {
80 System.err.println(e);
81 }
82 if (fbs == null) {
83 System.err.println("Could not open FBS file at entry point " +
84 entryPoint.timestamp + " ms");
85 }
Constantin Kaplinsky38f044f2008-06-20 12:57:37 +000086 }
87 }
88 }
89
Constantin Kaplinsky1c2865f2008-06-20 18:50:27 +000090 // Fallback to the dumb version of openFbsFile().
91 if (fbs == null) {
92 fbs = openFbsFile();
93 }
Constantin Kaplinsky2859fdc2008-06-19 16:07:52 +000094
Constantin Kaplinsky1c2865f2008-06-20 18:50:27 +000095 // Seek to the specified position.
96 fbs.setTimeOffset(timeOffset);
Constantin Kaplinsky2859fdc2008-06-19 16:07:52 +000097 return fbs;
98 }
99
Constantin Kaplinskyf338d2c2008-06-20 11:53:19 +0000100 /**
Constantin Kaplinsky1c2865f2008-06-20 18:50:27 +0000101 * Load index data from .fbi file to {@link #indexData}.
Constantin Kaplinskyf338d2c2008-06-20 11:53:19 +0000102 */
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +0000103 private void loadIndex() {
104 // Loading .fbi makes sense only if both .fbi and .fbk files are available.
105 if (fbiURL != null && fbkURL != null) {
Constantin Kaplinskyf338d2c2008-06-20 11:53:19 +0000106 FbsEntryPoint[] newIndex;
107 int numRecordsRead = 0;
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +0000108 try {
109 // Connect.
110 URLConnection connection = fbiURL.openConnection();
111 connection.connect();
112 DataInputStream is = new DataInputStream(connection.getInputStream());
113
114 // Check file signature.
115 byte[] b = new byte[12];
116 is.readFully(b);
117 if (b[0] != 'F' || b[1] != 'B' || b[2] != 'I' || b[3] != ' ' ||
118 b[4] != '0' || b[5] != '0' || b[6] != '1' || b[7] != '.' ||
119 b[8] < '0' || b[8] > '9' || b[9] < '0' || b[9] > '9' ||
120 b[10] < '0' || b[10] > '9' || b[11] != '\n') {
Constantin Kaplinskyf338d2c2008-06-20 11:53:19 +0000121 System.err.println("Could not load index: bad .fbi file signature");
122 return;
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +0000123 }
124
Constantin Kaplinskyf338d2c2008-06-20 11:53:19 +0000125 // Read the record counter and allocate index array.
126 int numRecords = is.readInt();
127 if (numRecords <= 0) {
128 System.err.println("Could not load index: bad .fbi record counter");
129 return;
130 }
131 newIndex = new FbsEntryPoint[numRecords];
132
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +0000133 // Load index from the .fbi file.
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +0000134 try {
Constantin Kaplinskyf338d2c2008-06-20 11:53:19 +0000135 for (int i = 0; i < numRecords; i++) {
136 FbsEntryPoint record = new FbsEntryPoint();
137 record.timestamp = (long)is.readInt() & 0xFFFFFFFFL;
138 record.key_fpos = (long)is.readInt() & 0xFFFFFFFFL;
139 record.key_size = (long)is.readInt() & 0xFFFFFFFFL;
140 record.fbs_fpos = (long)is.readInt() & 0xFFFFFFFFL;
141 record.fbs_skip = (long)is.readInt() & 0xFFFFFFFFL;
142 newIndex[i] = record;
143 numRecordsRead++;
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +0000144 }
145 } catch (EOFException e) {
Constantin Kaplinskyf338d2c2008-06-20 11:53:19 +0000146 System.err.println("Preliminary end of .fbi file");
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +0000147 } catch (IOException e) {
Constantin Kaplinskyf338d2c2008-06-20 11:53:19 +0000148 System.err.println("Ignored exception: " + e);
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +0000149 }
Constantin Kaplinskyf338d2c2008-06-20 11:53:19 +0000150 if (numRecordsRead == 0) {
151 System.err.println("Could not load index: failed to read .fbi data");
152 return;
153 } else if (numRecordsRead != numRecords) {
154 System.err.println("Warning: read not as much .fbi data as expected");
155 }
156 } catch (FileNotFoundException e) {
157 System.err.println("Could not load index: .fbi file not found: " +
158 e.getMessage());
159 return;
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +0000160 } catch (IOException e) {
Constantin Kaplinskyf338d2c2008-06-20 11:53:19 +0000161 System.err.println(e);
162 System.err.println("Could not load index: failed to load .fbi file");
163 return;
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +0000164 }
Constantin Kaplinsky5ba167a2008-06-20 12:24:52 +0000165 indexData = newIndex;
Constantin Kaplinskybe68e7f2008-06-20 12:21:57 +0000166 numIndexRecords = numRecordsRead;
Constantin Kaplinskyf338d2c2008-06-20 11:53:19 +0000167 System.err.println("Loaded index data, " + numRecordsRead + " records");
Constantin Kaplinsky5f1f8862008-06-20 05:17:39 +0000168 }
169 }
170
Constantin Kaplinsky1c2865f2008-06-20 18:50:27 +0000171 /**
172 * Open FBS file identified by {@link #fbsURL}. The file is open at its very
173 * beginning, no seek is performed.
174 *
175 * @return a newly created FBS input stream.
176 * @throws java.io.IOException if an I/O exception occurs.
177 */
178 private FbsInputStream openFbsFile() throws IOException {
179 return new FbsInputStream(fbsURL.openStream());
180 }
181
182 /**
183 * Open FBS file identified by {@link #fbsURL}. The stream is
184 * positioned at the entry point described by <code>entryPoint</code>.
185 *
186 * @param entryPoint entry point information.
187 *
188 * @return a newly created FBS input stream on success, <code>null</code> if
189 * any error occured and the FBS stream is not opened.
190 * @throws java.io.IOException if an I/O exception occurs.
191 */
192 private FbsInputStream openFbsFile(FbsEntryPoint entryPoint)
193 throws IOException {
Constantin Kaplinskyd3a2df12008-06-20 20:12:22 +0000194
195 // Make sure the protocol is HTTP.
196 if (!fbkURL.getProtocol().equalsIgnoreCase("http") ||
197 !fbsURL.getProtocol().equalsIgnoreCase("http")) {
198 return null;
199 }
200
201 // Prepare URLConnection to the right part of the .fbk file.
202 URLConnection fbkConn = fbkURL.openConnection();
203 long firstByteOffset = entryPoint.key_fpos;
204 long lastByteOffset = entryPoint.key_fpos + entryPoint.key_size - 1;
205 String rangeSpec = "bytes=" + firstByteOffset + "-" + lastByteOffset;
206 System.err.println("Range: " + rangeSpec);
207 fbkConn.setRequestProperty("Range", rangeSpec);
208 fbkConn.connect();
209 DataInputStream is = new DataInputStream(fbkConn.getInputStream());
210
211 // Load keyframe data from the .fbk file.
212 int keyDataSize = is.readInt();
213 byte[] keyData = new byte[keyDataSize];
214 is.readFully(keyData);
215 is.close();
216
217 // Open the FBS stream.
218 URLConnection fbsConn = fbsURL.openConnection();
219 fbsConn.connect();
220 return new FbsInputStream(fbsConn.getInputStream(), entryPoint.timestamp,
221 keyData, entryPoint.fbs_skip);
Constantin Kaplinsky1c2865f2008-06-20 18:50:27 +0000222 }
223
Constantin Kaplinsky2859fdc2008-06-19 16:07:52 +0000224}