| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 | /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"use strict";module.metadata = {  "stability": "experimental"};const {Cc,Ci,Cu,components} = require("chrome");var NetUtil = {};Cu.import("resource://gre/modules/NetUtil.jsm", NetUtil);NetUtil = NetUtil.NetUtil;// NetUtil.asyncCopy() uses this buffer length, and since we call it, for best// performance we use it, too.const BUFFER_BYTE_LEN = 0x8000;const PR_UINT32_MAX = 0xffffffff;const DEFAULT_CHARSET = "UTF-8";exports.TextReader = TextReader;exports.TextWriter = TextWriter;/** * An input stream that reads text from a backing stream using a given text * encoding. * * @param inputStream *        The stream is backed by this nsIInputStream.  It must already be *        opened. * @param charset *        Text in inputStream is expected to be in this character encoding.  If *        not given, "UTF-8" is assumed.  See nsICharsetConverterManager.idl for *        documentation on how to determine other valid values for this. */function TextReader(inputStream, charset) {  const self = this;  charset = checkCharset(charset);  let stream = Cc["@mozilla.org/intl/converter-input-stream;1"].               createInstance(Ci.nsIConverterInputStream);  stream.init(inputStream, charset, BUFFER_BYTE_LEN,              Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);  let manager = new StreamManager(this, stream);  /**   * Reads a string from the stream.  If the stream is closed, an exception is   * thrown.   *   * @param  numChars   *         The number of characters to read.  If not given, the remainder of   *         the stream is read.   * @return The string read.  If the stream is already at EOS, returns the   *         empty string.   */  this.read = function TextReader_read(numChars) {    manager.ensureOpened();    let readAll = false;    if (typeof(numChars) === "number")      numChars = Math.max(numChars, 0);    else      readAll = true;    let str = "";    let totalRead = 0;    let chunkRead = 1;    // Read in numChars or until EOS, whichever comes first.  Note that the    // units here are characters, not bytes.    while (true) {      let chunk = {};      let toRead = readAll ?                   PR_UINT32_MAX :                   Math.min(numChars - totalRead, PR_UINT32_MAX);      if (toRead <= 0 || chunkRead <= 0)        break;      // The converter stream reads in at most BUFFER_BYTE_LEN bytes in a call      // to readString, enough to fill its byte buffer.  chunkRead will be the      // number of characters encoded by the bytes in that buffer.      chunkRead = stream.readString(toRead, chunk);      str += chunk.value;      totalRead += chunkRead;    }    return str;  };}/** * A buffered output stream that writes text to a backing stream using a given * text encoding. * * @param outputStream *        The stream is backed by this nsIOutputStream.  It must already be *        opened. * @param charset *        Text will be written to outputStream using this character encoding. *        If not given, "UTF-8" is assumed.  See nsICharsetConverterManager.idl *        for documentation on how to determine other valid values for this. */function TextWriter(outputStream, charset) {  const self = this;  charset = checkCharset(charset);  let stream = outputStream;  // Buffer outputStream if it's not already.  let ioUtils = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);  if (!ioUtils.outputStreamIsBuffered(outputStream)) {    stream = Cc["@mozilla.org/network/buffered-output-stream;1"].             createInstance(Ci.nsIBufferedOutputStream);    stream.init(outputStream, BUFFER_BYTE_LEN);  }  // I'd like to use nsIConverterOutputStream.  But NetUtil.asyncCopy(), which  // we use below in writeAsync(), naturally expects its sink to be an instance  // of nsIOutputStream, which nsIConverterOutputStream's only implementation is  // not.  So we use uconv and manually convert all strings before writing to  // outputStream.  let uconv = Cc["@mozilla.org/intl/scriptableunicodeconverter"].              createInstance(Ci.nsIScriptableUnicodeConverter);  uconv.charset = charset;  let manager = new StreamManager(this, stream);  /**   * Flushes the backing stream's buffer.   */  this.flush = function TextWriter_flush() {    manager.ensureOpened();    stream.flush();  };  /**   * Writes a string to the stream.  If the stream is closed, an exception is   * thrown.   *   * @param str   *        The string to write.   */  this.write = function TextWriter_write(str) {    manager.ensureOpened();    let istream = uconv.convertToInputStream(str);    let len = istream.available();    while (len > 0) {      stream.writeFrom(istream, len);      len = istream.available();    }    istream.close();  };  /**   * Writes a string on a background thread.  After the write completes, the   * backing stream's buffer is flushed, and both the stream and the backing   * stream are closed, also on the background thread.  If the stream is already   * closed, an exception is thrown immediately.   *   * @param str   *        The string to write.   * @param callback   *        An optional function.  If given, it's called as callback(error) when   *        the write completes.  error is an Error object or undefined if there   *        was no error.  Inside callback, |this| is the stream object.   */  this.writeAsync = function TextWriter_writeAsync(str, callback) {    manager.ensureOpened();    let istream = uconv.convertToInputStream(str);    NetUtil.asyncCopy(istream, stream, function (result) {        let err = components.isSuccessCode(result) ? undefined :        new Error("An error occured while writing to the stream: " + result);      if (err)        console.error(err);      // asyncCopy() closes its output (and input) stream.      manager.opened = false;      if (typeof(callback) === "function") {        try {          callback.call(self, err);        }        catch (exc) {          console.exception(exc);        }      }    });  };}// This manages the lifetime of stream, a TextReader or TextWriter.  It defines// closed and close() on stream and registers an unload listener that closes// rawStream if it's still opened.  It also provides ensureOpened(), which// throws an exception if the stream is closed.function StreamManager(stream, rawStream) {  const self = this;  this.rawStream = rawStream;  this.opened = true;  /**   * True iff the stream is closed.   */  stream.__defineGetter__("closed", function stream_closed() {    return !self.opened;  });  /**   * Closes both the stream and its backing stream.  If the stream is already   * closed, an exception is thrown.  For TextWriters, this first flushes the   * backing stream's buffer.   */  stream.close = function stream_close() {    self.ensureOpened();    self.unload();  };  require("../system/unload").ensure(this);}StreamManager.prototype = {  ensureOpened: function StreamManager_ensureOpened() {    if (!this.opened)      throw new Error("The stream is closed and cannot be used.");  },  unload: function StreamManager_unload() {    // TextWriter.writeAsync() causes rawStream to close and therefore sets    // opened to false, so check that we're still opened.    if (this.opened) {      // Calling close() on both an nsIUnicharInputStream and      // nsIBufferedOutputStream closes their backing streams.  It also forces      // nsIOutputStreams to flush first.      this.rawStream.close();      this.opened = false;    }  }};function checkCharset(charset) {  return typeof(charset) === "string" ? charset : DEFAULT_CHARSET;}
 |