Dloader.java
上传用户:liming6160
上传日期:2022-06-07
资源大小:785k
文件大小:18k
源码类别:

J2ME

开发平台:

Java

  1. /*
  2.  *    Copyright (C) 2001 - 2007 Mobicom-Kavkaz, Inc
  3.  *    MFRadio - stream radio client for Java 2 Micro Edition
  4.  *    
  5.  *    Visit the project page at: http://mfradio.sourceforge.net
  6.  *
  7.  *    This program is free software; you can redistribute it and/or modify
  8.  *    it under the terms of the GNU General Public License as published by
  9.  *    the Free Software Foundation; either version 2 of the License, or
  10.  *    (at your option) any later version.
  11.  *
  12.  *    This program is distributed in the hope that it will be useful,
  13.  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15.  *    GNU General Public License for more details.
  16.  *
  17.  *    You should have received a copy of the GNU General Public License
  18.  *    along with this program; if not, write to the Free Software
  19.  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20.  *
  21.  *    Java (TM) and all Java (TM)-based marks are a trademark or 
  22.  *    registered trademark of Sun Microsystems, Inc, in the United States 
  23.  *    and other countries.
  24.  */
  25. package ru.mobicomk.mfradio.util;
  26. import java.io.DataInputStream;
  27. import java.io.DataOutputStream;
  28. import java.io.IOException;
  29. import java.io.InputStream;
  30. import java.io.OutputStream;
  31. import java.util.Hashtable;
  32. import javax.microedition.io.Connector;
  33. import javax.microedition.io.StreamConnection;
  34. import ru.mobicomk.mfradio.Constants;
  35. import ru.mobicomk.mfradio.controller.UIController;
  36. /**
  37.  * Socket-based downloader class. Used for access to audio stream data 
  38.  * through socket connection.
  39.  *
  40.  * <p>For connection information (site name, port number, etc.) this class uses
  41.  * URL of a resource. After establish connection ({@link #connect}) class uses
  42.  * HTTP 1.1 protocol commands.</p>
  43.  *
  44.  * <p>Example of use:
  45.  * 
  46.  * <code><pre>
  47.  * // ...
  48.  * // create new instance 
  49.  * Dloader dloader = new Dloader(controller); 
  50.  *     // controller is an application controller object.
  51.  * 
  52.  * // connect
  53.  * if (!dloader.connect(url)) {
  54.  *     throw new IOException("Can't connect!");
  55.  * }
  56.  *
  57.  * // read responce code
  58.  * String[] resp = dloader.readResponceCode();
  59.  * if (!"200".equals(resp[1])) {
  60.  *    throw new IOException("HTTP response code " + resp[1] + " " + resp[2]);
  61.  * }
  62.  * 
  63.  * // read headers for streamType
  64.  * String streamType;
  65.  * Hashtable headers = dloader.readHeaders();
  66.  * if (headers.containsKey("content-type")) { 
  67.  *     streamType = (String) headers.get("content-type");
  68.  * } else {
  69.  *     streamType = "unknown";
  70.  * }
  71.  * 
  72.  * // read data
  73.  * byte[] buffer = new byte[BUFFER_SIZE];
  74.  * int startOffset = 0;
  75.  * int bytesReaded = dloader.read(buffer, startOffset);
  76.  * dloader.disconnect();
  77.  * dloader = null;
  78.  *
  79.  * if (bytesReaded > 0) {
  80.  *     // ...use data from buffer...
  81.  * }
  82.  * // ...
  83.  * </pre></code>
  84.  * </p>
  85.  *
  86.  * @author  Roman Bondarenko
  87.  */
  88. public class Dloader
  89.     implements Runnable {
  90.     /** 
  91.      * Controller must setting up this flag to <b>true</b> for stop thread 
  92.      * execution. 
  93.      * 
  94.      * <p>Example: <code>
  95.      * <pre>
  96.      * Dloader dloader = new Dloader(..);
  97.      * // ...
  98.      * dloader.StopFlag.set();
  99.      * // ...
  100.      * </pre></code></p>
  101.      */
  102.     public BooleanFlag StopFlag;
  103.     
  104.     private UIController controller_;
  105.     private String site_; // "site:port"
  106.     private String file_; // "file/name"
  107.     
  108.     private int buffIdx_;
  109.     
  110.     private StreamConnection conn_;
  111.     private InputStream is_;
  112.     private OutputStream os_;
  113.     private DataInputStream dis_;    
  114.     private DataOutputStream dos_;    
  115.     private Object streamSync_;
  116.     
  117.     private static final int WAIT_SLEEPS_COUNT = 120;
  118.     private static final int WAIT_SLEEP = 500; // msec
  119.         // NOTE: wait timeout = (WAIT_SLEEPS_COUNT * WAIT_SLEEP) msec
  120.     
  121.     /**
  122.      * Create and initialize new instance of the downloader class.
  123.      * @param controller Application controller object. 
  124.      */
  125.     public Dloader(UIController controller) {
  126.         controller_ = controller;
  127.         streamSync_ = new Object();
  128.         StopFlag = new BooleanFlag(false);
  129.         buffIdx_ = 1;
  130.     }
  131.     
  132.     /**
  133.      * Get downloader connection state.
  134.      * @return <b>true</b> if the downloader currenly connected to resource, 
  135.      * <b>false</b> otherwise.
  136.      */
  137.     public boolean isConnected() {
  138.         return (null != dis_);
  139.     }
  140.     
  141.     /**
  142.      * Re-connect to current resource.
  143.      * @throws java.lang.InterruptedException if user breaks download operation. 
  144.      * @throws ru.mobicomk.mfradio.util.AppException if site name or file name not 
  145.      * specified or is empty.
  146.      * @return <b>true</b> if connected, <b>false</b> otherwise.
  147.      */
  148.     public boolean reconnect() throws InterruptedException, AppException {
  149.         disconnect();
  150.         return connect();
  151.     }
  152.     
  153.     /**
  154.      * Connect to a given resource.
  155.      * @param url URL of a resource to connection.
  156.      * @throws java.lang.InterruptedException if user breaks download operation. 
  157.      * @throws ru.mobicomk.mfradio.util.AppException if site name or file name not 
  158.      * specified or is empty.
  159.      * @throws ru.mobicomk.mfradio.util.UrlFormatException if error during URL parsing.
  160.      * @return <b>true</b> if connected, <b>false</b> otherwise.
  161.      */
  162.     public boolean connect(String url) throws InterruptedException, AppException, UrlFormatException {
  163.         String[] urlParts = Utils.splitURL(url);
  164.         site_ = urlParts[1] + ":" + urlParts[2]; // host:port
  165.         urlParts[0] = "";
  166.         urlParts[1] = "";
  167.         urlParts[2] = "";
  168.         file_ = Utils.mergeURL(urlParts);
  169.         return connect();
  170.     }
  171.     
  172.     /**
  173.      * Thread-safe breaking of the current connection. 
  174.      */
  175.     public void disconnect() {
  176.         synchronized (streamSync_) {
  177.             // free up I/O streams and close the socket connection
  178.             try { if (dis_ != null) dis_.close();  dis_ = null; } catch (Exception ignored) {}
  179.             try { if (dos_ != null) dos_.close();  dos_ = null; } catch (Exception ignored) {}
  180.             try { if (os_ != null) os_.close();     os_ = null; } catch (Exception ignored) {}
  181.             try { if (is_ != null) is_.close();     is_ = null; } catch (Exception ignored) {}
  182.             try { if (conn_ != null) conn_.close(); conn_ = null; } catch (Exception ignored) {}
  183.         }
  184.     }
  185.     /**
  186.      * Read resource data to the buffer.
  187.      *
  188.      * <p><b>NOTE:</b> buffer.length must be divisible by chunkSize.</p>
  189.      *
  190.      * @param buffer buffer into which the data is read.
  191.      * @param startOffset The start offset of the data.
  192.      * @throws java.io.IOException if I/O error occurs.
  193.      * @throws java.lang.InterruptedException if user breaks download process.
  194.      * @return Readed bytes count, or -1 if there is no more data because the 
  195.      * end of the stream has been reached.
  196.      * @see java.io.DataInputStream
  197.      * @see java.io.DataInputStream#read
  198.      */
  199.     public int read(byte[] buffer, int startOffset) throws IOException, InterruptedException {
  200.         synchronized (streamSync_) {
  201.             int progress = startOffset;
  202.             int chunkSize = 512;
  203.             int length = 0;
  204.             int stepsFor10Percents = ((buffer.length - startOffset) / chunkSize) / 10;
  205.             int step = 0;
  206.             long beginTime = System.currentTimeMillis();
  207.             long kbps = 0;
  208.             long percents = 0;
  209.             long deltaTime = 0;
  210.             final Locale locale = controller_.getLocale();
  211.     try {
  212.                 if (dis_ != null && buffer.length > 0 && !StopFlag.value()) {
  213.                     //controller_.log("read >> start dload " + buffer.length + " byte(s)");
  214.                     do {
  215.                         if ((progress + chunkSize) > buffer.length) {
  216.                             chunkSize = buffer.length - progress;
  217.                         }
  218.                         ////controller_.log("read >> progress: " + progress + "; chunkSize: " + chunkSize);
  219.                         length = dis_.read(buffer, progress, chunkSize);
  220.                         ////controller_.log("read >> readed: " + length);
  221.                         if (length == -1) {
  222.                             break;
  223.                         }
  224.                         
  225.                         progress += length;
  226.                         step++;
  227.                         //controller_.log("read >> progress: " + progress);
  228.                         if (step == stepsFor10Percents) {
  229.                             if (buffer.length > 0) {
  230.                                 kbps = 0;
  231.                                 percents = 0;
  232.                                 deltaTime = System.currentTimeMillis() - beginTime;
  233.                                 if (deltaTime > 0) {
  234.                                     //kbps = ((progress - startOffset) / 128) / (deltaTime / 1000);
  235.                                     kbps = ((progress - startOffset) * 125) / (16 * deltaTime);
  236.                                     percents = (100 * progress) / buffer.length;
  237.                                     
  238.                                     /*
  239.                                     controller_.progressMessage(controller_.getLocale.getString(Constants.STR_Buffer)
  240.                                         + " " + buffIdx_ + ": " + percents + "% "
  241.                                         +controller_.getLocale()e.getString(Constants.STR_Speed + " " + kbps + "kbps");
  242.                                     */
  243.                                     
  244.                                     controller_.progressMessage(locale.getString(Constants.STR_Buffer)
  245.                                         + " " + buffIdx_ + ": " + percents + "% / "
  246.                                         + kbps + "kbps");
  247.                                 }
  248.                             } else {
  249.                                 controller_.progressMessage(locale.getString(Constants.STR_Buffer)
  250.                                         + " " + buffIdx_ + ": 0%");
  251.                             }
  252.                             controller_.updateProgress();
  253.                             step = 0;
  254.                         }
  255.                     } while(/*(length != -1) &&*/ (progress < buffer.length) && !StopFlag.value());
  256.                     buffIdx_ = (buffIdx_ == 1) ? 2 : 1;
  257.                 }
  258.             } catch (IOException ex) {
  259.                 //controller_.log("read >> " + ex.toString() + " >> bufLen: "+buffer.length+"; length: " + length + "; progress: " + progress + "; chunk: " + chunkSize);
  260.             }
  261.             //controller_.log("read >> finish dload. dload "+progress+" byte(s)");
  262.             controller_.progressMessage(locale.getString(Constants.STR_Prefetched));
  263.             return (progress - startOffset);
  264.         }
  265.     }
  266.     /**
  267.      * Get HTTP-response code for previous HTTP-request.
  268.      *
  269.      * <p>Using:
  270.      * <code><pre>
  271.      * // ...
  272.      * // read responce code
  273.      * String[] resp = dloader.readResponceCode();
  274.      * if (!"200".equals(resp[1])) {
  275.      *    throw new IOException("HTTP response code " + resp[1] + " " + resp[2]);
  276.      * }
  277.      * </pre></code>
  278.      * </p>
  279.      *
  280.      * @throws java.io.IOException if I/O error occurs.
  281.      * @return String array with response code and text representation of the 
  282.      * response. 
  283.      * <p>Structure of the result (example is <code>"HTTP/1.0 200 OK"</code>) :
  284.      * <table border="1"><tr><td>Index</td><td>Description</td><td>Example</td></tr><tr><td>
  285.      * 0  </td><td>  Protocol                         </td><td><code>  HTTP/1.1 </code></td></tr></tr><tr><td>
  286.      * 1  </td><td>  Response code                    </td><td><code>  200      </code></td></tr></tr><tr><td>
  287.      * 2  </td><td>  Text description of the response </td><td><code>  OK       </code></td></tr></tr>
  288.      * </table>
  289.      * </p>
  290.      */
  291.     public String[] readResponseCode() throws IOException {
  292.         String[] ret = new String[3];
  293.         String line = readLine();
  294.         
  295.         int first = 0;
  296.         int last = line.indexOf(' ');
  297.         int idx = 0;
  298.         
  299.         while (last != -1 && idx < 3) {
  300.             ret[idx] = line.substring(first, last);
  301.             first = last + 1;
  302.             last = line.indexOf(' ', first);
  303.             idx++;
  304.         }
  305.         
  306.         return ret;
  307.     }
  308.     /**
  309.      * Get all HTTP-response headers.
  310.      *
  311.      * <p>Using:
  312.      * <code><pre>
  313.      * // ...
  314.      * // read headers for streamType
  315.      * String streamType;
  316.      * Hashtable headers = dloader.readHeaders();
  317.      * if (headers.containsKey("content-type")) {
  318.      *     streamType = (String) headers.get("content-type");
  319.      * } else {
  320.      *     streamType = "unknown";
  321.      * }
  322.      * // ...
  323.      * </pre></code>
  324.      * </p>
  325.      *
  326.      * @throws java.io.IOException  if I/O error occurs.
  327.      * @return Hashtable with all headers: keys is a header names, 
  328.      * values is a headers values.
  329.      * <p>Structure of the result :
  330.      * <table border="1"><tr><td>Key</td><td>Value</td></tr><tr><td>
  331.      * content-type  </td><td>  audio/mpeg  </td></tr></tr><tr><td>
  332.      * icy-name  </td><td>  Sample station  </td></tr></tr><tr><td>
  333.      * icy-genre </td><td>  all  </td></tr></tr><tr><td>
  334.      * ...  </td><td>  ... </td></tr></tr>
  335.      * </table>
  336.      * </p>
  337.      *
  338.      * @see java.util.Hashtable
  339.      */
  340.     public Hashtable readHeaders() throws IOException {
  341.         String key;
  342.         String val;
  343.         int idx = -1;
  344.         Hashtable hash = new Hashtable(10);
  345.         
  346.         String line = readLine();
  347.         while (!"".equals(line)) {
  348.             idx = line.indexOf(':');
  349.             key = line.substring(0, idx).trim().toLowerCase();
  350.             val = line.substring(idx + 1).trim();
  351.             hash.put(key, val);
  352.             line = readLine();
  353.         }
  354.         
  355.         return hash;
  356.     }
  357.     
  358.     // Runnable iplementation //////////////////////////////////////////////////
  359.     
  360.     /** 
  361.      * {@link Runnable} interface implementation. Entry point of the dowloader 
  362.      * thread.
  363.      * @see java.lang.Runnable
  364.      * @see java.lang.Runnable#run
  365.      */
  366.     public void run() {
  367.         _connect();
  368.     }
  369.     
  370.     // Privates ////////////////////////////////////////////////////////////////
  371.     
  372.     /*
  373.      * Connect to specified resource.
  374.      * @throws java.lang.InterruptedException if user breaks download operation. 
  375.      * @throws megafon.sap.one.util.AppException if site name or file name not 
  376.      * specified or is empty.
  377.      * @return 
  378.      */
  379.     private boolean connect() throws InterruptedException, AppException {
  380.         final Locale locale = controller_.getLocale();
  381. if (site_ == null 
  382.             || site_.length() == 0 
  383.             || file_ == null 
  384.             || file_.length() == 0) {
  385.             throw new AppException(locale.getString(Constants.STR_Invalid_agument));
  386.         }
  387.         if (is_ != null) {
  388.             disconnect();
  389.         }
  390.         
  391.         //controller_.log("connect >> start connection thread...");
  392.         Thread t = new Thread(this);
  393.         //t.setPriority(Thread.MAX_PRIORITY);
  394.         t.start(); // connect
  395.         //controller_.log("connect >> wait for connection...");
  396.         try {
  397.             for (int step = 0; step < WAIT_SLEEPS_COUNT; step++) {
  398.                 if (isConnected() || !t.isAlive()) {
  399.                     break;
  400.                 }
  401.                 controller_.updateProgress(); // exception
  402.                 Thread.sleep(WAIT_SLEEP);
  403.             }
  404.         } catch (InterruptedException ex) {
  405.             //controller_.progressMessage("interrupt - 1");
  406.             //t.interrupt(); // WARNING: CLDC 1.1
  407.             
  408.             // for CLDC 1.0
  409.             int step = 0;
  410.             controller_.progressMessage(locale.getString(Constants.STR_Stop));            
  411.             while (t.isAlive() && (step < 3)) {
  412.                 controller_.updateProgress(); // exception
  413.                 Thread.sleep(1000); // sleep 1 second
  414.                 step++;
  415.             }
  416.             
  417.             t = null;
  418.             disconnect();
  419.         }
  420.         
  421.         return isConnected();
  422.     }
  423.     
  424.     private void _connect() {
  425.         synchronized (streamSync_) {
  426.             try {
  427.                 //controller_.progressMessage("sc://" + site_ + file_);
  428.                 // establish a socket connection with remote server
  429.                 conn_ = (StreamConnection)Connector.open("socket://" + site_);
  430.                 // create DataOuputStream on top of the socket connection
  431.                 os_ = conn_.openOutputStream();
  432.                 dos_ = new DataOutputStream(os_);
  433.                 
  434.                 // send the HTTP request
  435.                 String req = "GET "+file_+" HTTP/1.1rnUser-Agent: Profile/MIDP-1.0 Configuration/CLDC-1.0rnrn";
  436.                 dos_.write(req.getBytes());
  437.                 // dos_.writeChars("GET /" + file_ + "n");
  438.                 dos_.flush();
  439.                 
  440.                 // create DataInputStream on top of the socket connection
  441.                 is_ = conn_.openInputStream();
  442.                 dis_ = new DataInputStream(is_);
  443.                 //controller_.progressMessage("connected!");
  444.                 
  445.             } catch (Exception ex) {
  446.                 //controller_.progressMessage("sc:ex:"+ex.getMessage());
  447.                 disconnect();
  448.             }
  449.         }
  450.     }
  451.     
  452.     private String readLine() throws IOException {
  453.         StringBuffer sb = new StringBuffer();
  454.         int ch;
  455.         char waitFor = 'r';
  456.         
  457.         while ((ch = dis_.read()) != -1) {
  458.             if ('r' == (char)ch) {
  459.                 waitFor = 'n';
  460.                 continue; // skip char
  461.             } else if (waitFor == (char)ch) {
  462.                 break;
  463.             }
  464.             sb.append((char)ch);
  465.         }
  466.         return sb.toString();
  467.     }
  468. }