はじめに
今回は、ポータルサイトのトップ画面にあるRSSリーダーと、スケジューラーに実装したメッセージャーについて書いていきます。RSSリーダーは、RSS情報を非同期で取得して、記事を表示する際は文字をスクロールさせて表示しています。メッセンジャーは、CurlのUDPソケットの機能を使用して実装しています。
これまでの記事
RSSリーダー
RSSリーダーには、主に以下の機能があります。
- ヘッドラインをティッカー表示
- ヘッドラインの一覧表示
- ヘッドラインをクリックすると目的の記事をブラウザを起動して表示
- フィードの登録・削除、記事の更新間隔・文字のスクロール速度の設定
ポータルサイトを起動すると、RSS情報を一定時間ごとに自動的にダウンロードして、記事をティッカー表示するようにしました。RSS情報をダウンロードする際は、非同期で取得するようにして、ダウンロード中に画面が固まらないようにしています。
{define-proc public {my-async-read-open p_src: Url, p_after-access-proc: {proc-type {#TextInputStream}: void}, error-proc: #{proc-type {Exception}: void} = null }: void let v_file: HttpFile = {p_src.instantiate-File} asa HttpFile {with-file-caching-style FileCachingStyle.resynchronize do let v_async-file-opener: AsyncFileOpener = {v_file.async-read-open {on t: AsyncFileOpenEvent do let v_stream: #TextInputStream = null {try {if-non-null t.exception then {if-non-null error-proc then {error-proc t.exception asa Exception} } else {if v_async-file-opener.done? and not t.canceled? then set v_stream = t.stream asa TextInputStream } {p_after-access-proc v_stream} } finally {if v_stream != null and v_stream.open? then {v_stream.close} } } } } } } ||RSS情報を非同期で取得する {my-async-read-open {url "http://rss.rssad.jp/rss/codezine/new/20/index.xml"}, {proc {p_in: #TextInputStream}: void ||取得完了したときに呼ばれる {if-non-null p_in then let (v_buf: StringBuf, v_num: int) = {p_in.read-one-string} : 省 略 : } }, error-proc = {proc {e: Exception}: void ||エラーが発生したときに呼ばれる } }
RSSリーダーの表示
RSSリーダーで情報を表示する部分は部品化して作成しました。文字列のスクロールスピード(普通/速い/遅い)、スクロール種類(文字列が見えなくなるまでスクロール/右端にきたらスクロールを止める/すべて表示されたらスクロールを止める)、次の文字列を表示するまでの間隔を指定できるようにしました。コントロールのソースコードを簡単に説明します。コントロールのソースコードはダウンロードして実行できます。
コントロールはグラフィカルな子を1つ格納するコンテナの基本クラスBaseFrameクラスを継承して作成しました。
{define-class public MyScrollMessageFrame {inherits BaseFrame} : 省 略 : }
コンストラクタは、「①スクロールスピード」「②スクロール種類」「③次の文字列を表示するまでの間隔」「④表示を繰り返すか?」「⑤画面に表示されたら自動的にスクロールを再開するか?」を引数で受け取るようにしました。
{constructor public {default scroll-speed: int = MyScrollMessageFrame.NORMAL, ||①スクロールスピード type: int = MyScrollMessageFrame.SCROLL, ||②スクロール種類 delay: int = 20, ||③次の文字列を表示するまでの間隔 repeat?: bool = false, ||④表示を繰り返すか? auto-resume?: bool = false, ...} ||⑤画面に表示されたら自動的にスクロールを再開するか? {construct-super ...} : 省 略 : }
文字列のティッカー表示はタイマーを使用しています。タイマーはTimerクラスを使用するば簡単に実現できます。
self._timer = {Timer enabled? = false, interval = 100ms, delay = 0ms, {on t: TimerEvent do ||タイムアウト {if-non-null self.layout then {self.on-timeout-timer t} } } }
文字列を描画している部分です。drawメソッドは描画が必要になったときに自動的に呼ばれるメソッドです。drawメソッドをオーバーライドして現在のスクロール位置に文字列を直接描画しています。
{method public {draw r2d: Renderer2d}: void {with-render-properties font = {Font self.font-family, {self.any-to-distance self.font-size, {self.get-display-context} }, weight = self.font-weight, style = self.font-style }, fill-pattern = self.color, translation-x = self._message-xpos, translation-y = {r2d.get-font-ascent} on r2d do {r2d.render-string 0pt, 0pt, self._message } } }
コントロールを使用している部分です。コントロールを生成してappendメソッドで表示する文字列を登録します。startメソッドでティッカー表示が始まります。
{let v_frame: MyScrollMessageFrame = {MyScrollMessageFrame width = 500pt, height = 15pt, background = "silver", font-size = 12pt, type = MyScrollMessageFrame.STOP-IN-MESSAGE-END, repeat? = false, auto-resume? = true} } {do ||表示する文字列をセット {v_frame.append "Windows PowerShell 活用編(3) ファイル操作 3"} {v_frame.append "「会社の成長」と「自身の成長」を実感できるカカクコム"} {v_frame.append "VB.NETで入力エラーチェック機能を持ったフォームを作成する"} {v_frame.append "PR: 迷惑メール対策なら信頼のマトリックススキャンAPEX"} ||表示の開始 {v_frame.start} }
メッセンジャー
メッセンジャーには、主に以下の機能があります。
- スケジューラーを起動している者同士で、簡単なメッセージのやり取りをリアルタイムに行える。
- 相手が不在の時にメッセージをデータベースに残すことができる。不在の人がスケジューラーを起動した際に、メッセージがあることを通知して後からメッセージを見ることができる。
UDPソケットの生成
UDPで送受信を行うときは、UDPパケットの送受信のためのクラスであるUDPSocketを使います。引数で使用するポート番号、タイムアウト時間、データを受信した時に呼ばれるイベントハンドラを指定します。bindメソッドを実行すると送受信できる状態になります。
let v_socket: UDPSocket= {UDPSocket local-port = 5000, timeout = Socket.zero-timeout, readable-handler = {on t: ReadableStreamEvent do ||データを受信したときに呼ばれる {recv-data-proc} } } {v_socket.bind}
UDPで文字列を送信
文字列を送信するには、UDPSocketクラスのwrite-packetメソッドを使用します。write-packetメソッドで送信できるデータ形式はバイト配列です。よって、送信する前に文字列をencode-charactersプロシージャでバイト配列に変換しています。
{define-proc {my-udp-send-string? p_socket: #UDPSocket, p_ip: String, p_port: uint16, p_data: String }: bool {if p_socket == null or not p_socket.open? then {return false} } let v_enc: CharEncoding = {get-character-encoding-by-name "shift-jis"}, v_outsize: int = p_data.size * v_enc.transcode-max-expansion-factor, v_out: ByteVec = {ByteVec max-size = v_outsize}, (v_used: int, v_outmade: int) = {encode-characters p_data, v_out, v_enc}, v_temp: {Array-of byte} = {new {Array-of byte}} {for v_byte: byte in v_out do {v_temp.append v_byte} } {return {p_socket.write-packet v_temp, remote-address = {SocketInetAddress p_ip}, remote-port = p_port } } } ||192.168.1.123に"あいうえお"を送信する {my-udp-send-string? v_socket, "192.168.1.123", 5000, "あいうえ" }
UDPで送信された文字列の受信
データを受信するときは、UDPSocketクラスを生成するときのreadable-handlerに指定したイベントハンドラで行います。 UDPSocketクラスのread-packetメソッドで、受信したデータを取得します。バイト配列で取得するので、decode- charactersプロシージャを使ってバイト配列から文字列に変換しています。
{define-proc {recv-data-proc}: void {try let (v_out: #{Array-of byte}, v_length: int, v_remote-address: #SocketInetAddress, v_remote-port: uint16) = {v_socket.read-packet} {if-non-null v_out then let v_temp: ByteVec = {ByteVec max-size = v_out.size} {for v_byte: byte in v_out do {v_temp.append v_byte} } let (v_num-bytes-decoded: int, v_recv: String) = {decode-characters v_temp, {get-character-encoding-by-name "shift-jis"} } : 省 略 : } catch e: ConnectionAbortedSocketException do } }
メッセンジャーの機能を応用して、グループメンバのスケジューラーの起動の有無を名前の前のアイコンで分かるようにしました。スケジューラーを起動すると、グループメンバのスケジューラーとメッセージの送受信を開始します。応答がある/なしによってアイコンの切り替えを行っています。
まとめ
Curlで、デスクトップアプリケーションのようなRSSリーダー・メッセンジャーが簡単に作成できることがお分かりいただけたと思います。
3回にわたり、Curlで「ポータル・スケジューラー」を開発するにあたり苦労した点、工夫した点などを書いてきました。本連載で少しでもCurlの良さを伝えることができたならば幸いです。ありがとうございました。