namespace RLib.NiconicoAPI
#light
open RLib.NiconicoAPI.Xml
open System
open System.IO
open System.Net.Sockets
open System.Text
open System.Threading
TODO
///送受信データの判別共用体
type CommentData =
///受信エラー
| ReceiveNG of Exception * int * int
///正常受信
| ReceiveOK of string * int * int
///送信エラー
| SendNG of Exception * int * int
///正常送信
| SendOK of string * int * int
///<summary>ニコ生コメントサーバの為の非同期送受信を提供します。</summary>
///<param name="setting">CommentClientの設定情報</param>
type CommentClient (statusType:GetPlayerStatus) =
let _enc = Encoding.UTF8
let _commentThreadID = "<thread thread=\"{0}\" version=\"20061206\" res_from=\"-200\"/>"
let _commentSendTemp = @"<chat thread=""{0}"" ticket=""{1}"" vpos=""{2}"" postkey=""{3}"" mail=""{4}"" user_id=""{5}"" premium=""{6}"">{7}</chat>"
let _client = new TcpClient(statusType.Ms.Addr, statusType.Ms.Port)
let _stream = _client.GetStream()
let _mainThreadID = Thread.CurrentThread.ManagedThreadId
let _threadID = statusType.Ms.Thread
/// 送信文字列をbyte配列に変換してNULLを付け足す。
let toByte (comment:string) =
Array.append
<| _enc.GetBytes(comment)
<| Array.zeroCreate 1
///キャンセルトークン
let token = Async.DefaultCancellationToken
///非同期送信イベント
let transceiverEvent = new Event<CommentData>()
///送受信イベントを提供する。
[<CLIEventAttribute>]
member x.Transceiver = transceiverEvent.Publish
///<summary>スレッドIDを送信する。</summary>
///<param name="threadid">ニコ生のスレッドID</param>
member x.SendThreadId (threadid:string) =
x.Send
<| String.Format(_commentThreadID, threadid)
///CommentClientSettingで設定したスレッドIDを送信する。
member x.SendThreadId () =
x.SendThreadId _threadID
///<summary>コメントXMLに変換後に送信する。</summary>
///<param name="threadid">コメント</param>
member x.SendComment comment =
let comment = Commons.toHtmlDecode comment
x.Send
<| String.Format(_commentSendTemp, _threadID)
///<summary>コメントを非同期で送信する。</summary>
///<param name="comment">コメント</param>
member x.Send (comment:string) =
let asyncSend =
async {
let threadID = Thread.CurrentThread.ManagedThreadId
try
let b = toByte comment
do! _stream.AsyncWrite(b, 0, b.Length)
transceiverEvent.Trigger
<| CommentData.SendOK(comment, _mainThreadID, threadID)
with
| ex ->
transceiverEvent.Trigger
<| CommentData.SendNG(ex, _mainThreadID, threadID)
}
Async.Start(asyncSend, token)
///<summary>同期的に1回分のコメントを取得する。</summary>
member x.GetCooment() =
let rec startmsg l =
try
match _stream.ReadByte() with
| 0 ->
let comment =
l
|> List.map(char>>string)
|> List.reduce((+))
CommentData.ReceiveOK(comment, _mainThreadID, -1)
| x -> startmsg (l@[x])
with
| ex -> CommentData.ReceiveNG(ex, _mainThreadID, -1)
startmsg []
///<summary>永続的に非同期コメント受信を行う。</summary>
member x.ReceiveStart() =
// 永続非同期受信を行う
let rec loop arr = async {
let threadID = Thread.CurrentThread.ManagedThreadId
try
let! data = _stream.AsyncRead(1)
let nextdata = Array.append arr data
match data with
| [| 0uy |] ->
//コメントに変換
let comment =
nextdata
|> Array.filter ((<>)0uy)
|> _enc.GetString
//受信イベントの発行
transceiverEvent.Trigger
<| CommentData.ReceiveOK(comment, _mainThreadID, threadID)
| _ ->
//次の受信待ちを呼び出す。
do! loop nextdata
with
| ex ->
transceiverEvent.Trigger
<| CommentData.ReceiveNG(ex, _mainThreadID, threadID)
do! loop Array.empty
}
Async.Start(loop Array.empty, token)
interface IDisposable with
/// CommentClientに割り当てられたリソースをすべて解放する。
member x.Dispose() =
Async.CancelDefaultToken()
try
_stream.Dispose()
_client.Close()
// 必要? 念のため相手側のサーバのコネクションを
// 完全に消すために使用。
GC.Collect()
with | _ -> ()