PowerShellで作るdeliciousクライアント その2

前回の続きです。

ブックマーク一覧の取得 - dirコマンドへの対応

まずはブックマーク一覧の取得機能を実装します。操作的には「delicious」ドライブに移動して、dirコマンド(Get-ChildItemコマンドレット)を実行するとブックマークの一覧が表示されるといった具合です。

テストしやすくする為にdeliciousにアクセスする機能を別クラスとして定義する事にします。

「Delicious.vb」というファイル名でクラスファイルを追加します。

Delicious.vb
Public NotInheritable Class Delicious
     Private _credential As Net.ICredentials

     Sub New(ByVal credential As Net.ICredentials)
         _credential = credential
     End Sub

End Class

deliciousにアクセスする為には認証が必要になるので、コンストラクタでICredentialsインスタンスを受け取るようにします。
これは後述するPowerShellで使用される認証クラスであるPSCredentialGetNetworkCredentialメソッドでNetworkCredentialクラスのインスタンスを返すのでこのようにしています。

次に、deliciousブックマークの情報を格納する為のクラスを「DeliciousBookmark.vb」というファイル名のクラスファイルとして追加します。

DeliciousBookmark.vb

<Serializable()> _
<DebuggerStepThrough()> _
Public Class DeliciousBookmark
     Private _title As String
     Public Property Title() As String
         Get
             Return _title
         End Get
         Set(ByVal value As String)
             _title = value
         End Set
     End Property

     Private _url As String
     Public Property Url() As String
         Get
             Return _url
         End Get
         Set(ByVal value As String)
             _url = value
         End Set
     End Property

     Private _tags As String()
     Public Property Tags() As String()
         Get
             Return _tags
         End Get
         Set(ByVal value As String())
             _tags = value
         End Set
     End Property

     Private _comment As String
     Public Property Comment() As String
         Get
             Return _comment
         End Get
         Set(ByVal value As String)
             _comment = value
         End Set
     End Property

     Private _date As DateTime
     Public Property [Date]() As DateTime
         Get
             Return _date
         End Get
         Set(ByVal value As DateTime)
             _date = value
         End Set
     End Property

     Public Overrides Function ToString() As String
         Return String.Format("Title = {0}, Url = {1}, Tags = {2}", Title, Url, String.Join(",", Tags))
     End Function
End Class

プロパティがいくつか定義されていますか、だいたい名前から想像できると思います。

では、ブックマークの一覧を取得する機能を実装します。
GetBookmarksというメソッドで、引数として、取得する開始位置と件数を受け取ります。 返り値としてList(Of DeliciousBookmark)型を返します。

Delicious.vb

Function GetBookmarks(ByVal start As Integer?, ByVal count As Integer?) As List(Of DeliciousBookmark)
     Dim address = "https://api.del.icio.us/v1/posts/all"
     Dim queries = New Dictionary(Of String, Object)()
     If start.HasValue Then queries.Add("start", start.Value)
     If count.HasValue Then queries.Add("results", count.Value)

     Dim wc = New Net.WebClient()
     wc.Credentials = _credential
     wc.Encoding = Text.Encoding.UTF8

     Dim contents = wc.DownloadString(JoinQueries(address, queries))
     Dim xml = XDocument.Parse(contents)
     Dim results = From ele In xml.Descendants("post") _
                   Select New DeliciousBookmark With { _
                     .Title = ele.Attribute("description"), _
                     .Url = ele.Attribute("href"), _
                     .Tags = CType(ele.Attribute("tag"), String).Split(" "), _
                     .Comment = ele.Attribute("extended"), _
                     .Date = ele.Attribute("time") _
                   }

     Return New List(Of DeliciousBookmark)(results)
End Function

Private Shared Function JoinQueries(ByVal address As String, ByVal queries As IDictionary(Of String, Object))
     If queries.Count = 0 Then Return address

     Return address + "?" + String.Join( _
         "&", queries.Select(Function(q) String.Format("{0}={1}", q.Key, q.Value)).ToArray() _
     )
End Function

処理的には、与えられた引数でURLを組み立て、WebClientクラスを使ってリクエストを送っています。

レスポンスはXMLとして返ってくるので、それをLINQ to XMLを使ってパースを行い、DeliciousBookmark型に変換しています。

使い方としては以下のようになります。

Program.vb
Sub Main()
     Dim obj = New Delicious(New Net.NetworkCredentials("ユーザ名", "パスワード"))
     ' 10件だけ取ってくる
     Dim bookmarks = obj.GetBookmarks(0, 10)
     ' do something
End Sub

次は、このメソッドをプロバイダー側から呼び出すようにします。

そのためにはまず、プロバイダーに認証情報が渡されたかどうかをチェックする必要があります。
認証情報はNewDriveメソッド(New-PSDriveコマンドレットに対応)の引数として渡されるPSDriveInfoクラスのCredentialプロパティからアクセスできます。

DeliciousProvider.vb

Protected Overrides Function NewDrive(ByVal drive As PSDriveInfo) As PSDriveInfo
     If drive.Credential Is PSCredential.Empty Then Throw New ArgumentException("認証情報[-Credential]を指定して下さい。")

     Return MyBase.NewDrive(drive)
End Function

PSCredentialクラスのEmptyプロパティと比較する事で、認証情報が指定されているかどうかを調べられます。

認証情報を指定してドライブを作る時は以下のようにします。

PS > New-PSDrive delicious Delicious c:\temp -Credential ユーザ名

というコマンドを実行すると、以下のパスワードを入力するダイアログが表示されます。

ここでパスワードを入力して、「OK」ボタンをクリックすればプロバイダーに認証情報を渡す事ができます。

後は、GetChildItemGet-ChildItemコマンドレットに対応)メソッドをオーバーライドして、その中でGetBookmarksメソッドを呼び出し、WriteItemObjectメソッドでオブジェクトをPowerShellのパイプラインに出力します。

DeliciousProvider.vb
Private _delicious As Delicious
Public ReadOnly Property Delicious() As Delicious
     Get
         If _delicious Is Nothing Then
             _delicious = New Delicious(Me.PSDriveInfo.Credential.GetNetworkCredential())
         End If
         Return _delicious
     End Get
End Property

Protected Overrides Sub GetChildItems(ByVal path As String, ByVal recurse As Boolean)
     For Each bookmark In Me.Delicious.GetBookmarks(0, 20)
         WriteItemObject(bookmark, path, False)
     Next
End Sub

ちなみにDeliciousクラスのインスタンスをプロパティ経由でインスタンス化しているのは、プロバイダークラスはコマンドレットが呼び出される度にインスタンス化が行われる為、クラスのフィールドに格納してもそれが保持され続けるわけでは無いからです。

ここまでで一度ビルドして、PowerShellを起動して「delicious」ドライブにアクセスしてみましょう。

dirコマンドを実行すると以下のように表示されるはずです。

PS delicious:\> dir


PSPath        : Delicious\Delicious::c:\temp\
PSParentPath  :
PSChildName   : temp
PSDrive       : delicious
PSProvider    : Delicious\Delicious
PSIsContainer : False
Title         : Microsoft Semblio - Home Page
Url           : http://www.microsoft.com/learningspace/semblio/Default.aspx
Tags          : {Microsoft, Silverlight}
Comment       : 学習教材を作るサイト
Date          : 2009/03/11 6:10:36
....

見づらいですね。Format-Tableなどの整形系のコマンドレットを使えば見易くなりますが、毎回指定するのは面倒なのでオブジェクトの書式設定ファイルを用意します。

psdelicious.format.ps1xml

<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
     <ViewDefinitions>
         <View>
             <Name>Tags</Name>
             <ViewSelectedBy>
                 <TypeName>PSDelicious.DeliciousBookmark</TypeName>
             </ViewSelectedBy>
             <ListControl>
                 <ListEntries>
                     <ListEntry>
                         <ListItems>
                             <ListItem>
                                 <Label>タイトル</Label>
                                 <PropertyName>Title</PropertyName>
                             </ListItem>
                             <ListItem>
                                 <Label>URL</Label>
                                 <PropertyName>Url</PropertyName>
                             </ListItem>
                             <ListItem>
                                 <Label>コメント</Label>
                                 <PropertyName>Comment</PropertyName>
                             </ListItem>
                             <ListItem>
                                 <Label>タグ</Label>
                                 <PropertyName>Tags</PropertyName>
                             </ListItem>
                             <ListItem>
                                 <Label>更新日付</Label>
                                 <PropertyName>Date</PropertyName>
                             </ListItem>
                         </ListItems>
                     </ListEntry>
                 </ListEntries>
             </ListControl>
         </View>
     </ViewDefinitions>
</Configuration>

この書式設定ファイルを以下のようにして、読み込みます。

PS > Update-FormatData -prependPath psdelicious.format.ps1xml

この状態で、再度dirコマンドを実行すると、

PS delicious:\> dir


タイトル : Microsoft Semblio - Home Page
URL      : http://www.microsoft.com/learningspace/semblio/Default.aspx
コメント : 学習教材を作るサイト
タグ     : {Microsoft, Silverlight}
更新日付 : 2009/03/11 6:10:36
...

とりあえず、これで20件だけですがブックマークの一覧を表示する事ができました。

せっかくなので、オプションを指定して取得する開始位置と件数を指定できるようにしておきましょう。

DeliciousProviderクラスのインナークラスとして、以下のクラスを定義します。

DeliciousProvider.vb
Private Class GetChildItemsParameters
     Private _start As Integer
     <Parameter()> _
     Public Property Start() As Integer
         Get
             Return _start
         End Get
         Set(ByVal value As Integer)
             _start = value
         End Set
     End Property

     Private _count As Integer = 20
     <Parameter()> _
     Public Property Count() As Integer
         Get
             Return _count
         End Get
         Set(ByVal value As Integer)
             _count = value
         End Set
     End Property
End Class

StartCountというプロパティを持つだけのクラスです。プロパティはそれぞれParameterAttribute属性でマークしておきます。

GetChildItemsDynamicParametersメソッドをオーバーライドして、このクラスのインスタンスを返しておきます。

DeliciousProvider.vb

Protected Overrides Function GetChildItemsDynamicParameters(ByVal path As String, ByVal recurse As Boolean) As Object
     Return New GetChildItemsParameters()
End Function

後は、GetChildItemsメソッドを以下のように変更しておきます。

DeliciousProvider.vb
Protected Overrides Sub GetChildItems(ByVal path As String, ByVal recurse As Boolean)
     Dim dynaParams = CType(Me.DynamicParameters, GetChildItemsParameters)

     For Each bookmark In Me.Delicious.GetBookmarks(dynaParams.Start, dynaParams.Count)
         WriteItemObject(bookmark, path, False)
     Next
End Sub

これで、以下のように開始位置と取得件数を指定して、dirコマンドを実行することができるようになります。

PS delicious:\> dir -Start 10 -Count 100

次回に続く・・・