カテゴリー「技術情報もしくは仕事ネタ」の31件の記事

2013年1月26日 (土)

MS-AccessでリンクしているPostgreSQLのテーブルの内容を更新すると「データの競合」表示される場合の対処方法

MS-Accessで、リンクしているPostgreSQLのテーブルの内容を更新すると「データの競合」表示される場合があります。

どーやら、データ型で浮動小数点を利用している物や、timestamp型があったりすると「データの競合」を誤検知するよーです。

その場合、データソースの設定画面から行ける「設定2」ダイアログの「バ-ジョン列表示」を有効にして、再度リンクし直す(リンクテーブルマネージャーの更新ではダメ)と各テーブルに「xmin」って列が追加されて、その列のバージョンとして利用できるようになり、「データの競合」を誤検知を回避する事ができます。

「バ-ジョン列表示」オプションは「row versioning」オプションに相当するよーです。

現在のPostgreSQL用ODBCドライバの設定は「バ-ジョン列表示」の日本語表記のみになってるので「バ-ジョン列表示」オプションと「row versioning」オプションが同じ機能なのかググってもなかなか気付かないのでメモ代わりに書いておきます。

あと、その他のオプションの日本語による説明については、コチラに書かれていますが、バージョン7.03.0200の時なので注意が必要です。

ちょっとしたデータの保守や内容確認のためのクエリを作る際にMS-AccessのUIは非常に使いやすいので保守ツールとして結構多用してたりします。

ちなみにMS-Accessなんて無いよって方は「A5:SQL Mk-2」と言う非常に素晴らしいツールがあります。
実はソフト開発時のデータ保守ツールとしては「A5:SQL Mk-2」の方が優れてたりします。

| | コメント (0) | トラックバック (0)

2011年10月20日 (木)

フレームセットの子フレームに親フレーム側からPOSTする

「今更フレームセットを使うのかよ」って話ですが、場合によっては便利なので使う機会がありました。

で、親フレーム側で受け取ったパラメータを子フレームへ渡すのにはGetパラメータかサーバサイドの仕組みが持ってるセッション変数ぐらいしかありません。

IEの場合は<head>要素に<form>を書いて<frameset>のonLoadイベントでPOSTする事も可能なんですが、ChromeやFirefoxだと<head>要素内の<form>は無視しちゃうため、「Formオブジェクトが無い」とエラーになってしまいます。

じゃ、後からFormオブジェクトを生成してやれ!
って事でコードを書いてみたら上手く行きました。

Formオブジェクトにぶら下がる各パラメータも生成しないとダメですが、この方法であれば、親フレーム側で受け取ったパラメータを子フレームへPOSTする事が可能です。

ちなみに生成したFormオブジェクトは、document.bodyの下に置かないとフォームとして機能しないのでご注意を・・・。

↓以下、サンプルコードです。

<html>
<head>
<title>子フレームPostテスト</title>
<script language='JavaScript'>
<!--//
    function framePost(){
       
        //Formオブジェクトの作成
        var MyForm = document.createElement("FORM");
        document.body.appendChild(MyForm);
       
        //パラメータ用オブジェクトの生成
        var MyInput = document.createElement("INPUT");
        MyInput.setAttribute("type","hidden");
        MyInput.setAttribute("name","value1");
        MyForm.appendChild(MyInput);
       
        //パラメータを子フレームへPOST
        MyInput.value = "上フレーム";
        with(MyForm) {
            action = "test_F1.asp";
            target = "topFrame";
            submit();
        }
        MyInput.value = "下フレーム";
        with(MyForm) {
            action = "test_F1.asp";
            target = "downFrame";
            submit();
        }
    }
//-->
</script>
</head>

<frameset rows="50%,*" onLoad="framePost();">
    <frame name="topFrame">
    <frame name="downFrame">
</frameset>

</html>

| | コメント (0) | トラックバック (0)

2010年8月24日 (火)

図書館のWebページに1秒に1回アクセスしてたら逮捕されたでござるって話

Anonymous FTPで公開されていたGlobal.asaが示すもの 岡崎図書館事件(6)

自作プログラムで岡崎市中央図書館への大量アクセスで逮捕された通称「librahack事件」についての話。

セキュリティ専門家の高木浩光氏が岡崎市中央図書館と同じシステムを使っていた図書館サイトが偶然にもAnonymous FTPでGlobal.asaを公開しちゃっているのを見つけちゃったので内容を確認してみたそーです。

Anonymous FTPで公開されちゃってたとは言え、一歩間違うと不正アクセス禁止法違反スレスレの行為(だと思う)ですが、おかげで原因がハッキリしました。

原因はSession_OnStartイベントでDBへの接続をやっちゃっているためにセッションタイムアウトが発生するまでDB接続が保持されてしまうせーでした。

コレは非常にマズイ作りです。

複数端末からやってくるリクエストによりDB接続が行われる一方でDB接続の開放がタイムアウトになるまで行われないため、そこそこのリクエスト数があるとそのうちDB接続数が上限に達します。

今回の逮捕容疑となった自動情報収集プログラムについては逮捕されたご本人がこのページにまとめられていますが、想像するに自動情報収集プログラムはcookieを受け入れなかったんじゃないかな?

セッション管理には大抵cookieを使うはずなんですが、cookieを受け入れずにリクエストを発行すると全て新規セッション扱いされてアッと言う間にDB接続数を食い尽くしたって感じの気がする。

ご本人曰く、自動情報収集プログラムは、同時アクセスやリトライはしてなかったって話なのでそーなのかなと。

まぁ、cookieを受け入れてもそこそこの数のアクセスがあれば障害が発生しちゃう事には代わりがありませんが・・・。

恐らく接続する端末数も決まっているイントラネット上だったら問題にはならなかったはずなので、マズイ作りでも開発中は気付かないかもしんない。
でも、負荷テストを行えば、すぐに発覚すると思うので提供側ベンダーの品質は問われるよーな気がします。

ちなみに朝日新聞が今回の逮捕について問題提起しています。

自分も含めて趣味でプログラミングする人間にとっては他人事じゃない話。
Webサーバ側の地雷を踏んじゃった事が業務妨害容疑になって、しかも「嫌疑なし」「嫌疑不十分」じゃないくて「起訴猶予処分」で前歴が残っちゃうんですから・・・。

つーか、担当している警察側の知識次第で対応が180度変わりそーな危うさが何とも・・・。

| | コメント (0) | トラックバック (0)

2010年7月13日 (火)

[VBScript]VBScriptでPOSTしてデータをダウンロードする

Webアプリの動作確認用に作ったものの久しぶりすぎて手間取ったのでメモ。

下記スクリプトはWSHで動作するVBScriptです。
テキストファイルとしてコピペしてファイルの拡張子を.vbsにすると実行できるよーになります。

指定したURLへ複数のパラメータをPOSTしてダウンロードした内容をカレントフォルダに保存しています。

ポイントとしては、複数パラメータのPOSTとServerXMLHTTPを使って動作確認のためタイムアウトを設定してやっているぐらい。

マルチバイトの文字列をPOSTする場合はもちろんエンコードする必要があります。
(その場合は、VBScriptからJScriptのencodeURI関数を呼び出すのが簡単だと思う)

Dim objweb
Dim objADO
Dim res
Dim strFname

Dim PARA1
Dim PARA2
Dim strPara

Dim startTime

'接続先
strURL = "http://hoggehoge/hogehoge.php"

'パラメータの設定
PARA1  = InputBox("パラメータ1","パラメータ設定","")
PARA2  = InputBox("パラメータ2","パラメータ設定","")

strPara = "PARA1=" & PARA1
strPara = strPara & "&" & "PARA2=" & PARA2

'保存するパスの編集
strFname = "ダウンロードデータ.dat"

Set objweb = CreateObject("MSXML2.ServerXMLHTTP")

'タイムアウトを設定(ms)
Dim intTimeOut
intTimeOut = 60 * 60 * 1000
objweb.setTimeouts intTimeOut, intTimeOut, intTimeOut, intTimeOut

'POSTする
startTime = now
objweb.Open "POST", strURL, False
objweb.setRequestHeader "Content-Type", " application/x-www-form-urlencoded"
objweb.setRequestHeader "Content-Length", "length"
objweb.Send strPara

'レスポンス内容を取得
res = objweb.responseBody

'レスポンス内容を保存
set objADO = CreateObject("ADODB.Stream")
objADO.Type = 1 'BINARY
objADO.Open()
objADO.Write(res)
objADO.SaveToFile strFname,2
objADO.Close

wscript.echo "接続ステータス : " & objweb.Status & " (" & objweb.statusText & ")" & vbCrLf & strFname & " に保存しました" & vbCrLf & vbCrLf & "開始時刻:" & startTime & vbCrLf & "終了時刻:" & now

Set objADO = Nothing
Set objweb = Nothing

| | コメント (0) | トラックバック (0)

2010年1月22日 (金)

Baby Care Report ~我が子2号、7ヶ月に編~

我が子2号も生後7ヶ月を過ぎました。

下の前歯2本が生えてきましたし、寝返りは左右ゴロゴロしてます。
ズリバイは頑張って挑戦しているよーですがまだまだって感じ。
独りお座りは安定感がイマイチでしたがココ1週間でかなり安定してきました。

20100122_baby_01
スクートルバグにアンパンマンのハンドルを取り付けて乗せてみたらご機嫌でした。
ちょっと分かり辛いですが下の前歯2本が生えています。

20100122_baby_02
ミルクの飲みっぷりも良い感じ。

それよりも甘えんぼモードに突入して「抱っこしろー!」って泣きまくったり、自分の思うよーにならないとやっぱり泣きまくったりって感じ。(^^;A
オマケに抱っこしないと寝なくなってます。
なのでちょっと困ってたりして・・・。

つーか、仕事から帰宅した私と目が合ってから「抱っこしろー!」って泣いてるよな?
それまで1人で遊んでるくせに・・・。(笑)

目の前にある物への興味は凄くて胡坐の上に座らせて食事しよーものなら手が出まくって落ち着いて食事することが出来ません。
で、オモチャを与えるんですが見慣れたヤツへの反応はイマイチ。うー、手強いぜぃ。(^^;A

ミルクは150~200mlを飲むおかげで、体重はすでに8.9kg。
たしか、同時期の我が子1号より重いはず。

体重が予想より増えてるので我が子2号用のチャイルドシートをそろそろ考えなきゃなぁ。( ̄▽ ̄;A

離乳食も始めてまして、リンゴが大好物のよーです。

ちなみにココに同時期の我が子1号の時の「Baby Care Report」がありますが、そこそこ違いがありますなぁ・・・。

つーか、我が子1号の歯の生えるのはホントに早かったんだな。

| | コメント (0) | トラックバック (0)

2009年10月 3日 (土)

今時の職業プログラマー見習いの人達は自分たちがそうだった頃より恵まれていないのかもしれない

これからのネット初心者は自分たちがそうだった頃より恵まれていないのかもしれないと思った話

上記はネット黎明期から共に育ってきた人はネットの一般化の流れに合わせて学習する時間があったけど、今時の人はいきなり広大なネット世界に放り込まれるし、以前より悪意のある人が流入してるので大変だろーなって話。

これってプログラマーにも同じよーな事が言えると思います。
今時のプログラマーに求められるスキルは技術革新によりかなり広くそして深くなったと思います。

私がバブル期にこの世界に入った時は業務用途でPCは異端でしたし、メインフレームでCOBOLとJCLとSORTユーティリティの使い方とISAMファイルの扱い方を理解してれば十分仕事が出来ました。
プログラムのアルゴリズムもNewKeyとOldKeyを比較しながらループでぶん回すマッチング処理を綺麗にコーディングできれば一人前でした。

そして技術革新をリアルタイムで体験&学習してきたことでスキルを積み重ねる時間的余裕がありました。
で、気がつくと今時のシステムはたくさんの技術を組み合わせて駆使しないと成立しないよーになってしまいました。

なので、これからの(お金を貰う"プロ"としての)「職業プログラマー」として入ってくる人は、いきなり今のIT業界の技術レベルに合わせる必要があるので大変だなぁと。
就職前から趣味プログラマーである人は勝手にバリバリやってくので例外でしょーけど・・・。

あと、今後増えるであろうコンピュータ体験がケータイ中心の世代だと「プログラミング」って行為自体が疎遠になっていくよーな気がするし・・・。
(iPhoneのよーなユーザがアプリを追加できるスマートフォンがIT世界の中心になってくるとプログラミング行為が復権してくるかもしんないけど)

以前にも似たよーな事を書いてる気がしてますが、職業プログラマー見習いの人達が一人前になるのが以前よりもハードルがかなり上がってて危機感を持ってる人は結構居ると思います。
しかし、教える側も業務に忙殺されているのが現状・・・。

なので、自分のスキルに自信が無い方はせめてもの足掻きとして「他人のコードを読んで理解する」事を心がけて欲しいし、さらに参考にしたコードをコピペするだけで終わらせるだけではなく、必ず弄って自分なりに改良して「スマートに実装できた」と自己満足に浸って欲しいです。

「綺麗なコードは雄弁」です。
そんな雄弁なコードを見つけたら、そのコード(=プログラマーの意図)と"対話"して「そのコードの存在意義」を確認してみてください。
日々の業務に追われて中で対応する仕様書をチラ見しても内容を理解するのは大変かも知れませんが、プログラマーとして価値ある行為だと思います。

多分、連結テストを経たプログラムだったらある程度綺麗なコードになってるんじゃないかな?(←無責任)

| | コメント (0) | トラックバック (0)

2009年8月21日 (金)

[VB.NET]DPAPIを利用して手軽に暗号化を実装する

まず、「DPAPI」は「Data Protection API」の略でWindows2000以降で提供されているデータの暗号化と復号化を行うためのWin32 APIです。

暗号化やその復号には暗号キーの管理がセキュリティ上非常に重要ですが、保存場所に気を使う必要があり、実装する場合に結構面倒な場合があります。

で、「DPAPI」はWindowsのユーザアカウントのパスワードを使用して、暗号化キーを生成し、その暗号キーをWindowsがアプリに代わって管理してくれる事で「暗号化キーの保護」と言う面倒な事を肩代わりしてくれます。

以前にご紹介した暗号化方法はキーの保存が面倒でしたので「Windowsアプリでパスワードなどをアプリ上で簡単に保存したい」って場合はDPAPIを利用した方が便利でしょう。

で、ソースは以下の通りとなります。

Public Class DPAPIClass

    Public Shared Function ProtectedString(ByVal strSrc As String) As String

        Try
            '文字列をバイト型配列に変換
            Dim srcData As Byte() = System.Text.Encoding.UTF8.GetBytes(strSrc)

            'DPAPIで暗号化(暗号化用の追加バイト配列は未使用・スコープはユーザ)
            Dim returnValue As Byte() = System.Security.Cryptography.ProtectedData.Protect(srcData, Nothing, System.Security.Cryptography.DataProtectionScope.CurrentUser)

            '暗号化されたデータをBASE64で文字列に変換
            ProtectedString = System.Convert.ToBase64String(returnValue)

        Catch ex As Exception
            ProtectedString = ""
        End Try

    End Function

    Public Shared Function UnProtectedString(ByVal strSrc As String) As String

        Try
            'BASE64の文字列になってる暗号化データに戻す
            Dim srcData As Byte() = System.Convert.FromBase64String(strSrc)

            'DPAPIで復号化(暗号化用の追加バイト配列は未使用・スコープはユーザ)
            Dim returnValue As Byte() = System.Security.Cryptography.ProtectedData.Unprotect(srcData, Nothing, System.Security.Cryptography.DataProtectionScope.CurrentUser)

            '復号化されたデータを文字列に変換
            UnProtectedString = System.Text.Encoding.UTF8.GetString(returnValue)

        Catch ex As Exception
            UnProtectedString = ""
        End Try

    End Function

End Class

実行するには、System.Securityの参照が必要となります。

DPAPIClass.ProtectedStringメソッドに暗号化したい文字列を渡すと暗号化した文字列が返ってきて、DPAPIClass.UnProtectedStringメソッドに暗号化された文字列を渡すと復号した文字列が返ってきます。
(便宜的にエラーが発生した場合は長さゼロの文字列を返しています)

ちなみにSystem.Security.Cryptography.ProtectedDataのProtectメソッドやUnprotectメソッドの第2引数(サンプルではNothingになってる箇所)は暗号化のための追加バイト配列で同一ユーザや同一マシンでも追加バイト配列の中身を知らないと元に戻せないよーにする事も可能です。

また、同メソッドの第3引数(サンプルではSystem.Security.Cryptography.DataProtectionScope.CurrentUserになってる箇所)は暗号化の保護範囲を示すもので、CurrentUserであれば現在のユーザー、LocalMachineであればコンピュータ単位となります。

ご察しの通り、「DPAPI」は暗号キーの管理をWindowsに依存するため、PCが吹っ飛んでWindowsを再インストールした場合、復号が行えなくなる可能性が高いです。(試してないので不明)
なので「DPAPI」の使用はアプリに必要とされる耐障害性と照らし合わせて決める必要があります。

| | コメント (0) | トラックバック (0)

2009年6月 3日 (水)

[VB.NET]行儀の悪いWebサーバのせーで「サーバーによってプロトコル違反が発生しました」が発生する場合の回避方法

System.Net.WebClientやSystem.Net.WebRequestを利用していて、ある特定のWebサーバとのやり取りすると・・・

サーバーによってプロトコル違反が発生しました. Section=ResponseStatusLine

と、WebExceptionがスローされる場合があります。
念のためWebExceptionのStatusプロパティを確認してみると"ServerProtocolViolation"となってたりします。

つまり、応答したWebサーバのレスポンスがHTTP的に正しくないため、.NET Frameworkの厳密なHTTPの検証に引っかかってしまってエラーとなるよーです。

メッセージ中のSectionが示している通り「ステータス行の不正」らしいので確認してみるとmsdn曰く本来のHTTPのステータス行は

HTTP/1.1 200 OK

と、ステータスコード(200)だけでなくステータスの説明(OK)も必要となるみたい。
ですが、今回の現象が起きたWebサーバは

HTTP/1.1 200

と、ステータスコードしか返さないためダメだったよーです。

.NET Frameworkがセキュリティを考慮して厳密なチェックを行っている訳ですのでサーバ側に改善を求めたいところですが、そーも言ってられないので、.NET Framework version 2.0以降は、上記エラーを無視できるよーになっています。

で、とりあえず今回は手軽なアプリケーション構成ファイル(App.config)で設定を行います。

おそらくStandard Editionなら「新しい項目に追加」でテンプレート内に「アプリケーション構成ファイル」があって追加できると思いますが、当方はMicrosoft Visual Basic 2008 Express Editionだったのでテンプレート内にアプリケーション構成ファイルがありませんでした。

なので、ソースが収められているディレクトリに空のApp.configファイルを作成して「既存の項目の追加」でプロジェクトに追加しました。

そして、App.configの内容を以下の様にして、httpWebRequest要素(ネットワーク設定)のuseUnsafeHeaderParsing属性をtrueにして厳密なHTTP検証を行わないようにします。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.net>
    <settings>
      <httpWebRequest useUnsafeHeaderParsing = "true" />
    </settings>
  </system.net>
</configuration>

この後にビルドやデバックを行うと、App.configがexeファイルと同じフォルダに「アプリ名.exe.config」としてコピーされて実行時に利用されました。

なので、配布時にはexeファイルと同時にApp.configの分身であるexe.configファイルも配布する必要があります。

ちなみに、HttpWebRequestElementクラスにUseUnsafeHeaderParsingプロパティが用意されててプログラムコードからでも上記設定を変更できるよーですが、にわか仕込みの私ではどーやって実装するのか良く分かりませんでした。_| ̄|○

| | コメント (2) | トラックバック (0)

2009年6月 2日 (火)

マイミク&マイミクx2一覧を改良してみた

マイミクのマイミクまでを含めたマイミク一覧表示してる自作mixiアプリですが、前回から数回ほどちょこちょこと以下の改良を加えました。

  • ページオーナーのマイミクやそのマイミクのマイミクに閲覧者本人や閲覧者のマイミクが居た場合に備考を付けるよーにした。
  • サーバやブラウザに負荷をなるべくかけないよーにマイミクのマイミク取得はマイミク1人単位で処理するよーにした。
  • IE6でも動くよーにした。

まぁ、「IE6対応」と言ってもIE6が対応していないArrayのforEachメソッドを使っちゃってたので、for文に改めただけですが・・・。(^^;A

で、コードは↓こんな感じです。(前回とは全然構造が変わってます)

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="FriendList">
    <Require feature="opensocial-0.8"/>
  </ModulePrefs>
  <Content type="html"><![CDATA[

<STYLE type="text/css">
.fixinline {
    display:-moz-inline-box;
    -moz-box-align: center;
    display: inline-block;
    vertical-align: middle;
    width:90px;
}
.thumimg {
    margin: 5px;
    vertical-align: middle;
}
</STYLE>

<div id="friendlist">
マイミク一覧を取得しています・・・
</div>

<script type="text/javascript">
var vfid = [];
var isOwner = false;
var owners;
var viewers;

gadgets.util.registerOnLoadHandler(function () {
   
    //VIEWERのプロフィールとOWNERとVIEWERのマイミク一覧を取得
   
    //opensocial.DataRequestオブジェクトを生成
    var request = opensocial.newDataRequest();
   
    //VIEWERのプロフィール取得リクエストを追加
    request.add(request.newFetchPersonRequest(opensocial.IdSpec.PersonId.VIEWER),"viewer_data");
   
    //newFetchPeopleRequestのオプションを指定
    var fetchOpt = [];
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.MAX ] = 1000;
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.SORT_ORDER] = opensocial.DataRequest.SortOrder.NAME;
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = [opensocial.Person.Field.PROFILE_URL];

   
    //OWNERのマイミク取得リクエストを追加
    var param = [];
    param[opensocial.IdSpec.Field.USER_ID] = opensocial.IdSpec.PersonId.OWNER;
    param[opensocial.IdSpec.Field.GROUP_ID] = "FRIENDS";
    var friends = opensocial.newIdSpec(param);
    request.add(request.newFetchPeopleRequest(friends,fetchOpt),"owner_friends_data");
   
    //VIEWERのマイミク取得リクエストを追加
    var param = [];
    param[opensocial.IdSpec.Field.USER_ID] = opensocial.IdSpec.PersonId.VIEWER;
    param[opensocial.IdSpec.Field.GROUP_ID] = "FRIENDS";
    var friends = opensocial.newIdSpec(param);
    request.add(request.newFetchPeopleRequest(friends,fetchOpt),"viewer_friends_data");
   
    //リクエストの送信
    request.send(get_response_level1);
});

function get_response_level1(response) {
   
    var item;
    var i;
   
    //取得したVIEWERのプロフィールでOWNERと同一人物かチェック
    item = response.get("viewer_data");
    if (item.hadError()) {
        if (window.console) console.log("VIEWERプロフィールの取得に失敗:" + item.getErrorMessage());
        return;
    }
    isOwner = item.getData().isOwner();
    if (window.console) console.log("isOwner:" + isOwner);
   
    //OWNERのマイミク一覧を取得
    item = response.get("owner_friends_data");
    if (item.hadError()) {
        if (window.console) console.log("OWNERのマイミク取得に失敗:" + item.getErrorMessage());
        return;
    }
    owners = item.getData().asArray();
    if (window.console) console.log("対象:OWNER");
    if (window.console) console.log("取得数:" + owners.length);
   
    //VIEWERのマイミク一覧を取得
    item = response.get("viewer_friends_data");
    if (item.hadError()) {
        if (window.console) console.log("VIEWERのマイミク取得に失敗:" + item.getErrorMessage());
        return;
    }
    viewers = item.getData().asArray();
    if (window.console) console.log("対象:VIEWER");
    if (window.console) console.log("取得数:" + viewers.length);
   
    //比較用にVIEWERのマイミクIDを配列にセット
    for (i = 0; i < viewers.length; i++) {
        vfid[viewers[i].getId()] = true;
    };
   
    //メッセージを消去
    document.getElementById("friendlist").innerHTML = "";
   
    //OWNERのマイミク一覧を表示
    show_friends(owners,1,"friendlist");
   
    //OWNERのマイミクのマイミクを取得し表示
    setTimeout("get_friends_level2(0)",0);
}

function get_friends_level2(ocnt) {
   
    //全てのOWNERのマイミクの処理を終えたら終了
    if (owners.length <= ocnt) {
        if (window.console) console.log("【処理終了!!】");
        return;
    }
   
    //opensocial.DataRequestオブジェクトを生成
    var request = opensocial.newDataRequest();
   
    //newFetchPeopleRequestのオプションを指定
    var fetchOpt = [];
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.MAX ] = 1000;
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.SORT_ORDER] = opensocial.DataRequest.SortOrder.NAME;
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = [opensocial.Person.Field.PROFILE_URL];
   
    //OWNERのマイミクのマイミク取得リクエストを追加
    var ofid = owners[ocnt].getId();
    var param = [];
    param[opensocial.IdSpec.Field.USER_ID] = ofid;
    param[opensocial.IdSpec.Field.GROUP_ID] = "FRIENDS";
    var friends = opensocial.newIdSpec(param);
    request.add(request.newFetchPeopleRequest(friends,fetchOpt),"friends_data");
   
    if (window.console) console.log("--------------------");
    if (window.console) console.log("Index:" + ocnt + ":(" + owners[ocnt].getDisplayName() + ")");
    if (window.console) console.log(ofid);
   
    //リクエストの送信
    request.send(function (response) {
        var item = response.get("friends_data");
        if (item.hadError()) {
            // エラー処理
            if (window.console) console.log("OWNERのマイミクのマイミク取得に失敗:" + item.getErrorMessage());
            return;
        }
       
        //マイミクの配列をセット
        var people = item.getData().asArray();
        if (window.console) console.log("対象:" + ofid);
        if (window.console) console.log("取得数:" + people.length);
       
        //OWNERのマイミクのマイミク一覧を表示
        show_friends(people,2,"fid" + ofid);
       
        //次のOWNERのマイミクのマイミクの処理を行う
        setTimeout("get_friends_level2(" + (ocnt + 1) + ")",0);
    });
}

//マイミク一覧の設定
function show_friends(people,level,elementId) {
   
    //マイミク一覧の表示要素を設定
    var element = document.getElementById(elementId);
   
    //マイミク一覧の表示
    var fcnt = 0;
    var i;
    for (i = 0; i < people.length; i++) {
        //IDの取得
        var gid = people[i].getId();
        //ニックネームの取得
        var nickname = people[i].getDisplayName();
        //プロフィールURLの取得
        var prourl = people[i].getField(opensocial.Person.Field.PROFILE_URL);
        //サムネイルURLの取得
        var thumurl = people[i].getField(opensocial.Person.Field.THUMBNAIL_URL);
       
        if (level == 1) {
            //閲覧者または共通の友人用メッセージの設定
            var msg = "";
            if (isOwner == false){
                if (people[i].isViewer()){
                    msg = " (←あなた)";
                } else if (gid in vfid){
                    msg = " (←共通の友人)";
                }
            }
            //マイミク一覧の生成(マイミク用)
            fcnt = fcnt + 1 ;
            var ol = document.createElement('ol');
            ol.id = "fid" + gid;
            var div = document.createElement('div');
            if (fcnt%2 == 0) {
                div.style.backgroundColor = '#FFFFFF';
            } else {
                div.style.backgroundColor = '#FFF8E9';
            }
            div.innerHTML = "<span class='fixinline'><img src='" + thumurl + "' class='thumimg'></span><b><a href='" + prourl + "' target='_blank'>" + nickname + "</a></b>" + msg;
            div.appendChild(ol);
            element.appendChild(div);
        } else {
            //閲覧者または共通の友人用メッセージの設定
            var msg = "";
            if (people[i].isViewer()){
                msg = " (←あなた)";
            } else if (gid in vfid){
                msg = " (←共通の友人)";
            }
            //マイミク一覧の生成(マイミクのマイミク用)
            var li = document.createElement('li');
            li.innerHTML = "<span class='fixinline'><img src='" + thumurl + "' class='thumimg'></span><a href='" + prourl + "' target='_blank'>" + nickname + "</a>" + msg;
            element.appendChild(li);
        }
    };
}
</script>

]]></Content>
</Module>

やってみて思ったんですが、閲覧者=オーナーなのか判断するために、わざわざ閲覧者のプロフィール取得しなきゃいけないのは、ちと面倒ですな。( ̄з ̄)

インディーズアプリとして下記URLで公開していますので、興味のある方は試してみてください。
http://platform001.mixi.jp/view_appli.pl?id=1377

あと、UIを改良したいと思ってるんですが、それにはFlash化するしか無さそうな感じ。
Flash未経験なのでチャレンジするかは微妙だったりします。(^^;A

あぁ、日記に関するAPIを提供して欲しいなぁ・・・。
マイミクのマイミクさんが「友達の友達まで公開」に設定されている日記の更新状況を知りたくないですか?
そーすれば、マイミクにこだわる事無く緩く繋がれると思うので。
(案外みんな「友達まで公開」にしか設定してないかもしんないけど・・・。(^^;A)


【2009.06.18追記】

  • 何となくメモリリークしている気がするので、get_friends_level2関数はsetTimeout関数経由で呼び出すよーに変更しました。
  • mixiアプリの仕様変更でPROFILE_URLはPROFILE_DETAILSを指定しないと取得できなくなったので対応しました。

| | コメント (0) | トラックバック (1)

2009年4月24日 (金)

mixiアプリを作ってみた、その2(マイミクのマイミクまでを含めた一覧表示)

前回、マイミクのマイミクまでを含めた一覧を表示するmixiアプリを作ってみたもののAPIの動きが安定してない感じで取得できない不具合が続いてましたが、先日mixi側が対応したらしくマイミク一覧を取得できるよーになってました。

で、動かしてみた様子が↓こんな感じ。

20090423_mixiapp_01
プライバシーの関係でモザイクだらけだけなのは勘弁。m(_ _)m

チャンとニックネームやプロフィール画面へのリンクも取得できてて動作するっぽい。
ただ、1回に取得できるのは20人までとなっている模様。

で、調べてみるとnewFetchPeopleRequestにオプションのMAXを指定しないとデフォルトの20が適用されちゃうらしい。

で、改良してみたコードが↓こんな感じ。

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="FriendList">
    <Require feature="opensocial-0.8"/>
  </ModulePrefs>
  <Content type="html" preferred_height="400"><![CDATA[

<STYLE type="text/css">
.fixinline {
    display:-moz-inline-box;
    -moz-box-align: center;
    display: inline-block;
    vertical-align: middle;
    width:90px;
}
.thumimg {
    margin: 5px;
    vertical-align: middle;
}
</STYLE>

<div id="friendlist">
</div>

<script type="text/javascript">

//マイミク一覧の表示
gadgets.util.registerOnLoadHandler(function () {
    try {
        //アプリを組み込んだオーナーのマイミクから取得開始
        get_friends(opensocial.IdSpec.PersonId.OWNER,1,"friendlist");
    } catch(e) {
        element.innerHTML = element.innerHTML + "error!:" + e.description + "<br>";
    }
});

function get_friends(pId,level,elementId) {
   
    //マイミク一覧の表示要素を設定
    var element = document.getElementById(elementId);
   
    //opensocial.DataRequestオブジェクトを生成
    var request = opensocial.newDataRequest();
   
    //取得元となる人と取得内容がマイミクである事を指定
    var param = {};
    param[opensocial.IdSpec.Field.USER_ID] = pId;
    param[opensocial.IdSpec.Field.GROUP_ID] = "FRIENDS";
    var friends = opensocial.newIdSpec(param);
   
    //フェッチする際のオプションを指定
    var fetchOpt = {};
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.MAX ] = 1000;
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.SORT_ORDER] = opensocial.DataRequest.SortOrder.NAME;
   
    //マイミクを取得
    request.add(request.newFetchPeopleRequest(friends,fetchOpt), "friends_data");
    request.send(function (response) {
        var item = response.get("friends_data");
        if (item.hadError()) {
            // エラー処理
            element.innerHTML = element.innerHTML + "error!:" + item.getErrorMessage() + "<br>";
            return;
        }
       
        //マイミクのコレクションを返す
        var people = item.getData();
       
        //デバッグ用表示
        if (window.console) console.log("マイミクの全体数:" + people.getTotalSize());
        if (window.console) console.log("マイミクの取得数:" + people.size());
        if (window.console) console.log("階層      :" + level);
        if (window.console) console.log("要素ID     :" + elementId);
        if (window.console) console.log(people);
       
        //マイミクの表示処理
        var fcnt = 0;
        if (people.size() > 0) {
            people.each(function (person) {
                //IDの取得
                var gid = person.getId();
                //ニックネームの取得
                var nickname = person.getDisplayName();
                //プロフィールURLの取得
                var prourl = person.getField(opensocial.Person.Field.PROFILE_URL);
                //サムネイルURLの取得
                var thumurl = person.getField(opensocial.Person.Field.THUMBNAIL_URL);
                               
                if (level == 1) {
                    //マイミク一覧の生成(マイミク用)
                    fcnt = fcnt + 1 ;
                    var ol = document.createElement('ol');
                    ol.id = "fid" + gid;
                    var div = document.createElement('div');
                    if (fcnt%2 == 0) {
                        div.style.backgroundColor = '#FFFFFF';
                    } else {
                        div.style.backgroundColor = '#FFF8E9';
                    }
                    div.innerHTML = "<span class='fixinline'><img src='" + thumurl + "' class='thumimg'></span><b><a href='" + prourl + "' target='_blank'>" + nickname + "</a></b>";
                    div.appendChild(ol);
                    element.appendChild(div);
                   
                    //マイミクのマイミクを取得
                    get_friends(gid,2,ol.id);
                } else {
                    //マイミク一覧の生成(マイミクのマイミク用)
                    var li = document.createElement('li');
                    li.innerHTML = "<span class='fixinline'><img src='" + thumurl + "' class='thumimg'></span><a href='" + prourl + "' target='_blank'>" + nickname + "</a>";
                    element.appendChild(li);
                }
            });
        } else {
            element.innerHTML = element.innerHTML + "友達いないよ・・・(´・ω・`)";
        }
    });
}
</script>

]]></Content>
</Module>

一応、並びを一定にするためにSORT_ORDERを指定して名前で並ぶよーにしているけど・・・ニックネーム表示じゃ、あんまり意味が無いかもね。(^^;A

あと、再帰処理の実装がかなり泥臭いですが気にしないで下さい。( ̄▽ ̄;A

ちなみにプロフィール画像のサムネイルのサイズが揃ってないために、ニックネームの表示開始位置が揃わない事を防ぐため、ココに記載されているCSSテクニックの「インライン要素に固定幅を与える」を利用しています。

インディーズアプリとして下記URLで公開していますので、興味のある方は試してみてください。
http://platform001.mixi.jp/view_appli.pl?id=1377

【2009.06.02追記】

この後、数回改良を加えてます。その詳細はコチラです。

| | コメント (0) | トラックバック (2)