Constantin Kaplinsky | 2859fdc | 2008-06-19 16:07:52 +0000 | [diff] [blame] | 1 | // |
| 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 | |
| 24 | package com.tightvnc.rfbplayer; |
| 25 | |
| 26 | import java.io.*; |
| 27 | import java.net.*; |
| 28 | import java.applet.Applet; |
| 29 | |
| 30 | public class FbsConnection { |
| 31 | |
| 32 | URL fbsURL; |
| 33 | URL fbiURL; |
| 34 | URL fbkURL; |
| 35 | |
Constantin Kaplinsky | f338d2c | 2008-06-20 11:53:19 +0000 | [diff] [blame] | 36 | /** Index data loaded from the .fbi file. */ |
Constantin Kaplinsky | 5ba167a | 2008-06-20 12:24:52 +0000 | [diff] [blame] | 37 | FbsEntryPoint[] indexData; |
Constantin Kaplinsky | be68e7f | 2008-06-20 12:21:57 +0000 | [diff] [blame] | 38 | int numIndexRecords; |
Constantin Kaplinsky | ade425a | 2008-06-20 06:44:03 +0000 | [diff] [blame] | 39 | |
Constantin Kaplinsky | 2859fdc | 2008-06-19 16:07:52 +0000 | [diff] [blame] | 40 | FbsConnection(String fbsLocation, String indexLocationPrefix, Applet applet) |
| 41 | throws MalformedURLException { |
| 42 | |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 43 | // Construct URLs from strings. |
Constantin Kaplinsky | 2859fdc | 2008-06-19 16:07:52 +0000 | [diff] [blame] | 44 | URL base = null; |
| 45 | if (applet != null) { |
| 46 | base = applet.getCodeBase(); |
| 47 | } |
| 48 | fbsURL = new URL(base, fbsLocation); |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 49 | fbiURL = fbkURL = null; |
Constantin Kaplinsky | 2859fdc | 2008-06-19 16:07:52 +0000 | [diff] [blame] | 50 | if (indexLocationPrefix != null) { |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 51 | 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 Kaplinsky | 2859fdc | 2008-06-19 16:07:52 +0000 | [diff] [blame] | 57 | } |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 58 | |
| 59 | // Try to load the .fbi index file. |
Constantin Kaplinsky | 5ba167a | 2008-06-20 12:24:52 +0000 | [diff] [blame] | 60 | indexData = null; |
Constantin Kaplinsky | be68e7f | 2008-06-20 12:21:57 +0000 | [diff] [blame] | 61 | numIndexRecords = 0; |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 62 | loadIndex(); |
Constantin Kaplinsky | 2859fdc | 2008-06-19 16:07:52 +0000 | [diff] [blame] | 63 | } |
| 64 | |
| 65 | FbsInputStream connect(long timeOffset) throws IOException { |
Constantin Kaplinsky | 1c2865f | 2008-06-20 18:50:27 +0000 | [diff] [blame] | 66 | FbsInputStream fbs = null; |
| 67 | |
Constantin Kaplinsky | 38f044f | 2008-06-20 12:57:37 +0000 | [diff] [blame] | 68 | // 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 Kaplinsky | 1c2865f | 2008-06-20 18:50:27 +0000 | [diff] [blame] | 77 | 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 Kaplinsky | 38f044f | 2008-06-20 12:57:37 +0000 | [diff] [blame] | 86 | } |
| 87 | } |
| 88 | } |
| 89 | |
Constantin Kaplinsky | 1c2865f | 2008-06-20 18:50:27 +0000 | [diff] [blame] | 90 | // Fallback to the dumb version of openFbsFile(). |
| 91 | if (fbs == null) { |
| 92 | fbs = openFbsFile(); |
| 93 | } |
Constantin Kaplinsky | 2859fdc | 2008-06-19 16:07:52 +0000 | [diff] [blame] | 94 | |
Constantin Kaplinsky | 1c2865f | 2008-06-20 18:50:27 +0000 | [diff] [blame] | 95 | // Seek to the specified position. |
| 96 | fbs.setTimeOffset(timeOffset); |
Constantin Kaplinsky | 2859fdc | 2008-06-19 16:07:52 +0000 | [diff] [blame] | 97 | return fbs; |
| 98 | } |
| 99 | |
Constantin Kaplinsky | f338d2c | 2008-06-20 11:53:19 +0000 | [diff] [blame] | 100 | /** |
Constantin Kaplinsky | 1c2865f | 2008-06-20 18:50:27 +0000 | [diff] [blame] | 101 | * Load index data from .fbi file to {@link #indexData}. |
Constantin Kaplinsky | f338d2c | 2008-06-20 11:53:19 +0000 | [diff] [blame] | 102 | */ |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 103 | private void loadIndex() { |
| 104 | // Loading .fbi makes sense only if both .fbi and .fbk files are available. |
| 105 | if (fbiURL != null && fbkURL != null) { |
Constantin Kaplinsky | f338d2c | 2008-06-20 11:53:19 +0000 | [diff] [blame] | 106 | FbsEntryPoint[] newIndex; |
| 107 | int numRecordsRead = 0; |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 108 | 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 Kaplinsky | f338d2c | 2008-06-20 11:53:19 +0000 | [diff] [blame] | 121 | System.err.println("Could not load index: bad .fbi file signature"); |
| 122 | return; |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 123 | } |
| 124 | |
Constantin Kaplinsky | f338d2c | 2008-06-20 11:53:19 +0000 | [diff] [blame] | 125 | // 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 Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 133 | // Load index from the .fbi file. |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 134 | try { |
Constantin Kaplinsky | f338d2c | 2008-06-20 11:53:19 +0000 | [diff] [blame] | 135 | 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 Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 144 | } |
| 145 | } catch (EOFException e) { |
Constantin Kaplinsky | f338d2c | 2008-06-20 11:53:19 +0000 | [diff] [blame] | 146 | System.err.println("Preliminary end of .fbi file"); |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 147 | } catch (IOException e) { |
Constantin Kaplinsky | f338d2c | 2008-06-20 11:53:19 +0000 | [diff] [blame] | 148 | System.err.println("Ignored exception: " + e); |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 149 | } |
Constantin Kaplinsky | f338d2c | 2008-06-20 11:53:19 +0000 | [diff] [blame] | 150 | 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 Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 160 | } catch (IOException e) { |
Constantin Kaplinsky | f338d2c | 2008-06-20 11:53:19 +0000 | [diff] [blame] | 161 | System.err.println(e); |
| 162 | System.err.println("Could not load index: failed to load .fbi file"); |
| 163 | return; |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 164 | } |
Constantin Kaplinsky | 5ba167a | 2008-06-20 12:24:52 +0000 | [diff] [blame] | 165 | indexData = newIndex; |
Constantin Kaplinsky | be68e7f | 2008-06-20 12:21:57 +0000 | [diff] [blame] | 166 | numIndexRecords = numRecordsRead; |
Constantin Kaplinsky | f338d2c | 2008-06-20 11:53:19 +0000 | [diff] [blame] | 167 | System.err.println("Loaded index data, " + numRecordsRead + " records"); |
Constantin Kaplinsky | 5f1f886 | 2008-06-20 05:17:39 +0000 | [diff] [blame] | 168 | } |
| 169 | } |
| 170 | |
Constantin Kaplinsky | 1c2865f | 2008-06-20 18:50:27 +0000 | [diff] [blame] | 171 | /** |
| 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 Kaplinsky | d3a2df1 | 2008-06-20 20:12:22 +0000 | [diff] [blame] | 194 | |
| 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. |
Constantin Kaplinsky | d2af082 | 2008-06-21 11:41:42 +0000 | [diff] [blame] | 202 | InputStream is = |
| 203 | openByteRange(fbkURL, entryPoint.key_fpos, entryPoint.key_size); |
| 204 | DataInputStream dis = new DataInputStream(is); |
Constantin Kaplinsky | d3a2df1 | 2008-06-20 20:12:22 +0000 | [diff] [blame] | 205 | |
| 206 | // Load keyframe data from the .fbk file. |
Constantin Kaplinsky | d2af082 | 2008-06-21 11:41:42 +0000 | [diff] [blame] | 207 | int keyDataSize = dis.readInt(); |
Constantin Kaplinsky | d3a2df1 | 2008-06-20 20:12:22 +0000 | [diff] [blame] | 208 | byte[] keyData = new byte[keyDataSize]; |
Constantin Kaplinsky | d2af082 | 2008-06-21 11:41:42 +0000 | [diff] [blame] | 209 | dis.readFully(keyData); |
| 210 | dis.close(); |
Constantin Kaplinsky | d3a2df1 | 2008-06-20 20:12:22 +0000 | [diff] [blame] | 211 | |
| 212 | // Open the FBS stream. |
| 213 | URLConnection fbsConn = fbsURL.openConnection(); |
| 214 | fbsConn.connect(); |
| 215 | return new FbsInputStream(fbsConn.getInputStream(), entryPoint.timestamp, |
| 216 | keyData, entryPoint.fbs_skip); |
Constantin Kaplinsky | 1c2865f | 2008-06-20 18:50:27 +0000 | [diff] [blame] | 217 | } |
| 218 | |
Constantin Kaplinsky | 3e51615 | 2008-06-21 13:29:03 +0000 | [diff] [blame^] | 219 | private static InputStream openByteRange(URL url, long offset, long length) |
Constantin Kaplinsky | d2af082 | 2008-06-21 11:41:42 +0000 | [diff] [blame] | 220 | throws IOException { |
| 221 | URLConnection conn = url.openConnection(); |
| 222 | long lastByteOffset = offset + length - 1; |
| 223 | String rangeSpec = "bytes=" + offset + "-" + lastByteOffset; |
| 224 | System.err.println("Range: " + rangeSpec); |
| 225 | conn.setRequestProperty("Range", rangeSpec); |
| 226 | conn.connect(); |
| 227 | return conn.getInputStream(); |
| 228 | } |
| 229 | |
Constantin Kaplinsky | 2859fdc | 2008-06-19 16:07:52 +0000 | [diff] [blame] | 230 | } |