無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

複数のデータを連結するIOもどきを修正。

昨日作成した、複数のデータをboundaryで連結するIOもどきをちょっと修正。

  • サイズを得るAPIを追加。
    • ただし、IOからはサイズがとれないので、IOが追加されている場合、エラーとなる。
  • add_file()の時点でファイルを開くのはもったいないため、必要になった場合のみ、開くように修正。
  • グローバル名前空間を汚さないため、モジュールで囲むようにした。

使い方。だいたい同じです。

data = nil
begin
  data = MultipartFormData::Data.new "test"

  # 文字列データを追加。
  data.add( "aaaa", {
    "content-disposition"=>'form-data; name="pics"; filename="file1.txt"',
    "Content-Type"=>"text/plain"
  } )
  data.add( "xxxx", {
    "Content-Type"=>"text/plain"
  } )
  # IOを追加。("./data.dat"の中身は「hogehoge」)
  data.add( File.open("./data.txt"), {
    "Content-Type"=>"application/octet-stream"
  } )
  # ローカルファイルを追加。
  data.add_file( "./data.txt", {
    "Content-Type"=>"application/octet-stream"
  } )
  
  # サイズ取得
  #puts data.size

  # 読み込み
  puts data.read
  
  #while ( str = data.read(10) )
  #  puts str
  #end

ensure
  data.close if data!= nil # 必ず実行すること。
end

実装。

require "stringio"

module MultipartFormData

# multipart/form-dataの送信のため、複数のデータをつなぐIOもどき。
# read(length)のみ実装。
class Data

  def initialize ( boundary )
    @boundary = boundary
    @datas = []
    @index = -1
    @io = nil
  end

  # IO or 文字列を追加する。
  def add( data, headers )
    if data.is_a? String
      src = StringSource.new data
    elsif data.is_a? IO
      src = IOSource.new data
    elsif data.is_a? Source
      src = data
    else
      raise ArgumentError.new
    end

    header = "--#{@boundary}\r\n"
    headers.each {|k,v|
      header << "#{k.to_s}: #{v.to_s}\r\n"
    }
    header << "\r\n"

    @datas << StringSource.new(header)
    @datas << src
    @datas << StringSource.new("\r\n")
  end

  # ファイルを追加する。
  def add_file( file_path, headers )
    add( FileSource.new(file_path), headers)
  end

  def read( length=nil )
    buff = ""
    while ( length==nil || buff.length < length )
      @io = next_io() if @io == nil
      return buff.length > 0 ? buff : nil if @io == nil

      data = @io.read( length==nil ? nil : length - buff.length )
      if ( @io.eof? )
        @io.close
        @io = nil
      end
      buff += data if data != nil
    end
    return buff
  end

  # サイズを得る。
  def size
    total = 0
    @datas.each { |data|
      total += data.size
    }
    total + @boundary.length + 6
  end

  # リソースを破棄する。
  # 不要になった際に、必ず実行すること。
  def close
   begin
     if (@io != nil)
       @io.close if !@io.closed?
       @io = nil
     end
   ensure
     @datas.each { |data|
        if data.is_a? IO
        data.close if !data.closed?
      end
     }
   end
  end

private
  def next_io
    @index+=1
    if ( @index == 0)
      @datas <<  StringSource.new("--#{@boundary}--\r\n")
    end
    if ( @index < @datas.length )
     return @datas[@index].get_io
    else
      return nil
    end
  end
end

class Source
  def get_io; end
  def size;   end
end

class StringSource < Source
  def initialize ( data )
    @data = data
  end
  def get_io
    StringIO.new(@data)
  end
  def size
    @data.length
  end
end

class FileSource < Source
  def initialize ( file_path )
    @file_path = file_path
  end
  def get_io
    File.open(@file_path)
  end
  def size
    File.stat(@file_path).size
  end
end

class IOSource < Source
  def initialize ( data )
    @data = data
  end
  def get_io
    @data
  end
  def size
    raise "unsupported operation."
  end
end

修正 (2007-10-11)

サイズに最後の"--#{@boundary}--\r\n"の分が入っていなかった・・orz ので修正。