Thursday, August 4, 2011

Published My First App

So I finally got around to publishing a tool I made for the WindowsPhone on the marketplace.

I've been writing little tools for myself for awhile now, but the marketplace is a little thin when it comes to tools for Magic the Gathering. I tried a couple of the tools there out and didn't really like how the interface/features worked and decided to write one for myself.

Anywho, it's called MTG Buddy, it's available on the marketplace now and you can buy it for .99 cents if you want here: http://www.microsoft.com/windowsphone//s?appid=550219d5-e78a-442c-807e-cf4eb26188a4

Putting it together wasn't all that difficult - I think the most frustrating part was dealing with the myriad of APIs and stuff available for getting card data. In the end I put together my own web service and database so I wouldn't have to worry about things disappearing or breaking.

I'm looking forward to the 7.5 release as they'll allow you to have databases in IsolatedStorage - converting jpegs to byte arrays and back is costly and so is storing those byte arrays in memory!

Friday, June 24, 2011

Tail!

Quick and dirty tail application for your command line. Just create a console application, paste this code in, build it and you should be good to go.





Imports System
Imports System.IO
Imports System.Collections
Imports System.Threading
Imports System.Text
Imports Microsoft.VisualBasic
Imports System.Net

Module Module1
Dim tempstr As String

Sub Main(ByVal args() As String)
tailLog(Environment.CurrentDirectory & "\" & args(0))
End Sub



Sub tailLog(ByVal logName As String)
Dim filename As String = logName
Dim lastLogEntry As DateTime = DateTime.Now
Dim line As String
Dim filePath As New FileInfo(logName)

Try
Using logReader As New StreamReader(New FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
'start at the end of the file

'This is the end of the file, send what's before it.
Dim logOffset As Long = logReader.BaseStream.Length

While True
Thread.Sleep(750)

'If the file size is the same nothing has changed in the log file and we can just loop again.
If logReader.BaseStream.Length = logOffset Then
Continue While
End If

'If the stream length changed it probably means the file was truncated or the file length changed
'Send the last line and reset the offset to the current length of the file.

'seek to the last max offset
logReader.BaseStream.Seek(logOffset, SeekOrigin.Begin)

'read out of the file until the EOF
While (helper(line, logReader.ReadLine())) IsNot Nothing
If line = "" Then
'If the line has no contents, skip it.
Else
Console.WriteLine(line)
End If

End While

logOffset = logReader.BaseStream.Position
End While
End Using
Catch ta As ThreadAbortException
'Thread abort exception will get thrown when the thread gets aborted by the expired timer, if anything needs to be done do it here.
'EventLog.WriteEntry(My.Application.Info.AssemblyName, ta.Message.ToString)
Catch ex As Exception
EventLog.WriteEntry(My.Application.Info.AssemblyName, & ex.Message.ToString)
End Try

End Sub

Function helper(Of T)(ByRef target As T, ByVal value As T) As T
target = value
Return value
End Function

HTTP Listener

I put this together to play with the possibility of receiving the XML logs that the SmoothStreamingMediaElement can send. I wasn't thrilled with some of the limitations that the built-in log receiver in IIS had. This HTTP listener was very focused and shouldn't be confused with a true webserver. This was built to eat POSTs as quickly as it could and deal with them as a secondary job.

You could have your worker thread dumping them to MQ or doing bulk inserts into something like InfoBright or MySQL.

It's been so long since I looked at this that I can't remember how many requests per second I was handling - I think it was a measly 10,000 or something. Either way, if you're looking for an asynchronous HTTP client in VB.NET - look no further.

As an afterthought, I'm not sure that you need to synclock the jobQueue object. Since it was created as a shared object, I believe that will handle the synclocking for you.




Imports System.Net.HttpListener
Imports System.Threading
Imports System.Net
Imports System
Imports System.Text
Imports System.IO



Public Class httpListen
Dim parentListener As Net.HttpListener
Public Shared jobQueue As New Queue(10, 1.6)
Dim accessPolicy As Byte() = System.Text.Encoding.UTF8.GetBytes("<?xml version=""1.0"" encoding=""utf-8""?><access-policy><cross-domain-access><policy><allow-from http-request-headers=""*""><domain uri=""*""/></allow-from><grant-to><resource path=""/"" include-subpaths=""true""/></grant-to></policy></cross-domain-access></access-policy>")
Dim readyForNextReq As System.Threading.AutoResetEvent = New System.Threading.AutoResetEvent(False)

Sub Dispose()

End Sub

Sub abort()
parentListener.Abort()
End Sub

Sub start()
parentListener = New Net.HttpListener
parentListener.Prefixes.Add("http://*/")
parentListener.Start()
Try
Dim logWriter As New logWriter
Catch ex As Exception
EventLog.WriteEntry(My.Application.Info.AssemblyName, ex.ToString)
End Try

System.Threading.ThreadPool.QueueUserWorkItem(New System.Threading.WaitCallback(AddressOf ProcessRequest))
End Sub

Sub postJobCount()
Console.WriteLine(jobQueue.Count)
End Sub

Public Sub ProcessRequest()
While parentListener.IsListening
parentListener.BeginGetContext(New AsyncCallback(AddressOf ListenerCallback), parentListener)
readyForNextReq.WaitOne()
End While
End Sub



Sub ListenerCallback(ByVal result As IAsyncResult)
Dim context As System.Net.HttpListenerContext = TryCast(result.AsyncState, System.Net.HttpListener).EndGetContext(result)
Dim requestString() As Byte

readyForNextReq.Set()
Dim request As System.Net.HttpListenerRequest = context.Request
Try
If request.HttpMethod = "POST" Then
If request.HasEntityBody Then
Using requestReader As New BinaryReader(context.Request.InputStream)
Try

requestString = combineBytes(convertAddress(request.RemoteEndPoint.Address.ToString, request.Url.Segments.Last.ToString.Split(".")(0)), requestReader.ReadBytes(5096))

Catch ne As System.Net.HttpListenerException
'The request stream was cut off for some reason
requestReader.Close()
requestString = Nothing
request = Nothing
context = Nothing
Exit Sub
End Try

'Lock the queue so we can drop a logline into it.
SyncLock jobQueue.SyncRoot
jobQueue.Enqueue(requestString)
End SyncLock
requestReader.Close()
requestString = Nothing

Using response As System.Net.HttpListenerResponse = context.Response
'All done, send back a 200
responseCode(context.Response, 200)
End Using
End Using
Else
context.Response.ContentLength64 = accessPolicy.Length
context.Response.OutputStream.Write(accessPolicy, 0, accessPolicy.Length)
'This wasn't an HTTP post.. ignore it.
responseCode(context.Response, 200)
End If
Else
context.Response.ContentLength64 = accessPolicy.Length
context.Response.OutputStream.Write(accessPolicy, 0, accessPolicy.Length)
'This wasn't an HTTP post.. ignore it.
responseCode(context.Response, 200)
End If


request = Nothing
context = Nothing
Catch ex As Exception
EventLog.WriteEntry(My.Application.Info.AssemblyName, "Error in async operation: " & ex.ToString)
End Try

End Sub

Private Function responseCode(ByVal context As System.Net.HttpListenerResponse, ByVal returnCode As Integer) As Boolean
Using response As System.Net.HttpListenerResponse = context
Try
'This wasn't a HTTP POST, ignore it.
response.StatusCode = returnCode
response.OutputStream.Close()
Catch sn As System.Net.HttpListenerException
'The request was terminated before I could close it myself - avoid a crash in this case.
End Try
End Using
End Function

Public Function convertAddress(ByVal ipAddress As String, ByVal uriRequest As String) As Byte()
Dim ipLong As Long = 0
Dim ipBytes As String()

ipBytes = ipAddress.Split("."c)
For i As Integer = ipBytes.Length - 1 To 0 Step -1
ipLong += ((Integer.Parse(ipBytes(i)) Mod 256) * Math.Pow(256, (3 - i)))
Next
'Add a bell character to the string so we can split out the client IP later for DB insertion.
Return System.Text.Encoding.ASCII.GetBytes(ipLong.ToString & Chr(7) & uriRequest & Chr(7))
End Function

Public Shared Function combineBytes(ByVal array1 As Byte(), ByVal array2 As Byte()) As Byte()
Dim bytes As Byte() = New Byte(array1.Length + (array2.Length - 1)) {}
Array.Copy(array1, bytes, array1.Length)
Array.Copy(array2, 0, bytes, array1.Length, array2.Length)
Return bytes
End Function




End Class

Wednesday, June 22, 2011

Predicates and VB.NET

When I talk to people about .NET and that I like to code with VB.NET I always have to endure the hazing that comes along with it. One of the points that C# developers always seem to bring up is that VB.NET sucks when it comes to predicates.

I suppose it's hard to show or explain out of context, but I'll give it a shot anyway.

Let's say you have a dictionary of string,string files as a manifest. The key is the full path of the file and the value is the name of the file or some meta data about it. The dictionary is populated by an initial scan of the file system and then updated as events fire from a filesystemwatcher.

Now, you rename a directory - and your file manifest needs to be updated. Here's how you could do that with a predicate using the filesystemwatcher.renamed event:




Sub fileRenamed(ByVal source As Object, ByVal e As RenamedEventArgs)
Try
If e.FullPath.Contains("$RECYCLE") Or e.FullPath.Contains("System Volume") Or e.FullPath.Contains("DfsrPrivate") Then
Exit Sub
End If
'A directory was renamed, we need to modify all keys and values with the old path to the new one - since they don't exist under the old path anymore.

If Directory.Exists(e.FullPath) Then
Dim oldKeys As IEnumerable(Of KeyValuePair(Of String, String)) = fileDict.Where(Function(grepDict) Regex.Match(grepDict.Key, e.OldFullPath).Success).ToList

For Each s As KeyValuePair(Of String, String) In oldKeys
'Remove the old key.
fileDict.Remove(s.Key)

'Add the new key with the updated path.
fileDict.Add(Regex.Replace(s.Key, e.OldFullPath, e.FullPath), Regex.Replace(s.Value, oldMetaData, newMetaData))
Next
Else
fileDict.Remove(e.OldFullPath)
fileDict.Add(e.FullPath, fileNameOrMetaData)
End If

Catch ex As Exception
EventLog.WriteEntry(My.Application.Info.ToString, ex.Message)
End Try

End Sub

LDAP Authentication with VB.NET

This was kind of tricky for me to figure out. LDAP examples with VB.NET aren't really existent, or, at least they weren't when I put this together. Needed an LDAP authentication library to use with ASPX.

All you have to do with this class is construct it with the FQDN of your LDAP server and call the functions like so:





<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Default.aspx.vb" %>

<%@ Import Namespace="ldapAuth" %>
<%@ Import Namespace="System.DirectoryServices" %>
<%@ Import Namespace="System.DirectoryServices.Protocols" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>LDAP Example</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<%
Dim ldap As New ldapClass("YOURLDAPSERVERFQDN")
Dim username As String = "YOURUSERNAME" 'Username statically declared for a fast example, can be fetched by any method
Dim password As String = "YOURPASSWORD"
Dim ldapConnection As LdapConnection
ldapConnection = ldap.connectSSL(ldap.getUserDN(username), password)
Response.Write("Hostname: " & ldapConnection.SessionOptions.HostName & "<br>")
Response.Write("SSL: " & ldapConnection.SessionOptions.SecureSocketLayer & "<br>")
Response.Write("DN: " & ldap.getUserDN(username))

Using ldapConnection
Dim ldapSearch As SearchRequest = New SearchRequest("dc=YOUR,dc=DOMAIN", "(&(objectClass=YOURUSERFILTER)(UID=" & username & "))", DirectoryServices.Protocols.SearchScope.Subtree)
Dim ldapResponse As SearchResponse = LdapConnection.SendRequest(ldapSearch)

Response.Write("<ul>")
For Each dirEntry As SearchResultEntry In ldapResponse.Entries
For Each attribute As System.Collections.DictionaryEntry In dirEntry.Attributes
Response.Write("<li>" & attribute.Key.ToString & ": " & attribute.Value.item(0).ToString & "</li>")
Next
Next
End Using
%>
</div>
</form>
</body>
</html>





Imports System.DirectoryServices
Imports System.DirectoryServices.Protocols
Imports System.Security.Cryptography
Imports System.Security.Cryptography.X509Certificates

Public Class ldapClass
Private _ldapServer As String
Dim ldapDirectory As DirectoryEntry
Dim ldapSearcher As DirectorySearcher

Public Property ldapServer()
Get
Return _ldapServer
End Get
Set(ByVal name)
_ldapServer = name
End Set
End Property

Public Sub New(ByVal ldapServerName As String)
ldapServer = ldapServerName
End Sub

Public Function getUserDN(ByVal username As String) As String
Dim ldapConnection As New LdapConnection(ldapServer.ToString)
Dim ldapOptions As LdapSessionOptions = ldapConnection.SessionOptions

ldapOptions.ProtocolVersion = 3
ldapConnection.Credential = New Net.NetworkCredential(String.Empty, String.Empty)
ldapConnection.AuthType = AuthType.Anonymous

ldapConnection.Bind()
Using ldapConnection
Dim ldapSearch As SearchRequest = New SearchRequest("dc=YOURDOMAIN,dc=COM", "(&(objectClass=YOURSEARCHFILTER)(uid=" & username & "))", DirectoryServices.Protocols.SearchScope.Subtree)
Dim ldapResponse As SearchResponse = ldapConnection.SendRequest(ldapSearch)
If ldapResponse.Entries.Count = 0 Then ' No user was found.
Return "Could not find the username requested"
End If
Dim entry As SearchResultEntry = ldapResponse.Entries(0)
Return entry.DistinguishedName
End Using
End Function

Public Function connectSSL(ByVal username As String, ByVal password As String) As LdapConnection
Dim ldapConnection As New LdapConnection(ldapServer.ToString)
Dim ldapOptions As LdapSessionOptions = ldapConnection.SessionOptions

ldapOptions.ProtocolVersion = 3
ldapConnection.SessionOptions.VerifyServerCertificate = New VerifyServerCertificateCallback(AddressOf verifyCallback)
ldapConnection.Credential = New Net.NetworkCredential(username, password)
ldapConnection.SessionOptions.StartTransportLayerSecurity(Nothing)
ldapConnection.AuthType = AuthType.Basic
ldapConnection.Bind()
Return ldapConnection
End Function


Public Function verifyCallback(ByVal connection As LdapConnection, ByVal certificate As X509Certificate) As Boolean
'Do some stuff here to verify the server certificate, if you choose.
Return True
End Function


End Class

Yelp API Business Review Searches

Going along with the whole DataContractSerializer thing in .NET I thought I would share the class I use to deserialize business review searches with the Yelp API. As an added bonus, the Yelp API doesn't return any URL for a business so that you can easily navigate to the page to write a review for that business. It's easy to figure out though. The review URL uh, key, for a lack of better terms is just the business ID.

So here it goes, some VB.NET code to get businesses that match some common terms within a 1 mile(?) radius of a given latitude and longitude.



Public Sub getNearbyLocations(ByVal myLatitude As Long, ByVal myLongitude As Long)
If gps_allowed = True Then
myThrobber.Visibility = Visibility.Visible
myWebClient = New WebClient()
myWebClient.OpenReadAsync(New Uri("http://api.yelp.com/business_review_search?term=food%20bars%20pizza%20bar%20diner%20bar&lat=" & myLatitude & "&long=" & myLongitude & "&radius=1&limit=20&ywsid=YOURYELPAPIKEY"))
AddHandler myWebClient.OpenReadCompleted, AddressOf gotJson
Else
MessageBox.Show("You need to allow this application to use the GPS in the settings panel to use this function!")
End If
End Sub

Private Sub gotJson(ByVal sender As Object, ByVal e As OpenReadCompletedEventArgs)
For Each businessObject As yelpBusiness In placesMainPanel.Children
businessObject = Nothing
Next

placesMainPanel.Children.Clear()

Dim sr As New StreamReader(e.Result)
jsonString = sr.ReadToEnd
Dim jsonThing As New jsonStuff(jsonString)
For Each returnedBusiness As yelpResponse.business In jsonThing.myYelpResponse.businesses
Dim myNewBusiness As New yelpBusiness
myNewBusiness.lblBusinessName.Text = returnedBusiness.name
myNewBusiness.lblAddress1.Text = returnedBusiness.address1
myNewBusiness.lblAddress2.Text = returnedBusiness.city & ", " & returnedBusiness.state_code & " " & returnedBusiness.zip
myNewBusiness.lblReviewCount.Text = returnedBusiness.review_count & " Reviews"
myNewBusiness.imgRatingStars.Source = New BitmapImage(New Uri(returnedBusiness.rating_img_url))
myNewBusiness.imgPlacePhoto.Source = New BitmapImage(New Uri(returnedBusiness.photo_url))
myNewBusiness.lblReadReviews.NavigateUri = New Uri(returnedBusiness.url, UriKind.Absolute)
myNewBusiness.lblReadReviews.TargetName = "_blank"
myNewBusiness.btnLeaveReview.NavigateUri = New Uri("http://www.yelp.com/writeareview/biz/" & returnedBusiness.id)
myNewBusiness.btnLeaveReview.TargetName = "_blank"
placesMainPanel.Children.Add(myNewBusiness)
Next

myThrobber.Visibility = Visibility.Collapsed
End Sub




Imports System.Runtime.Serialization.Json
Imports System.Runtime.Serialization

<DataContract()> _
Partial Public Class yelpResponse

<DataMember()> _
Public Property message As yelpMessage

<DataMember()> _
Public Property businesses As List(Of business)

Public Class yelpMessage

Public Property text As String
Public Property code As Integer
Public Property version As String
End Class

Public Class business
Public Property rating_img_url As String
Public Property country_code As String
Public Property id As String
Public Property is_closed As Boolean
Public Property city As String
Public Property mobile_url As String
Public Property review_count As Integer
Public Property zip As String
Public Property state As String
Public Property latitude As Double
Public Property rating_img_url_small As String
Public Property address1 As String
Public Property address2 As String
Public Property address3 As String
Public Property phone As String
Public Property state_code As String
Public Property categories As List(Of category)
Public Property photo_url As String
Public Property distance As Double
Public Property name As String
Public Property neighborhoods As List(Of neighborhood)
Public Property url As String
Public Property country As String
Public Property avg_rating As Double
Public Property longitude As Double
Public Property nearby_url As String
Public Property reviews As List(Of review)
Public Property photo_url_small As String

End Class

Public Class category
Public Property category_filter As String
Public Property search_url As String
Public Property name As String

End Class

Public Class neighborhood
Public Property url As String
Public Property name As String


End Class

Public Class review
Public Property rating_img_url_small As String
Public Property user_photo_url_small As String
Public Property rating_img_url As String
Public Property rating As Integer
Public Property mobile_uri As String
Public Property url As String
Public Property user_url As String
Public Property text_excerpt As String
Public Property user_photo_url As String
Public Property date1 As String
Public Property user_name As String
Public Property id As String

End Class
End Class




Tuesday, June 21, 2011

Using DataContractSerializers with the Eve Online API



After browsing the Eve Online forums I came across a project someone was writing in VB.NET to make their industry life easier.

The problem, I thought, was that they weren't treating the response from the API as an object which made it difficult to read the code and debug it. I got to thinking, and came up with this bit of code to parse the Characters.xml response. You just have to construct the class with the response you get after you call Characters.xml.


Imports System.IO

Imports System.Xml.Schema
Imports System.Xml.Serialization
Imports System.Runtime.Serialization

Public Class xmlParser
Public api As eveapi

Public Sub New(ByVal eveApiXmlResponse As String)
api = parseXml(eveApiXmlResponse)

For Each charList As characterList In api.characterLists
For Each eveChar As eveCharacter In charList.characters
MessageBox.Show(eveChar.name)
Next
Next
End Sub

Private Function parseXml(ByVal eveResponse As String) As eveapi
Dim xmlRoot As XmlRootAttribute = New XmlRootAttribute
xmlRoot.ElementName = "eveapi"
xmlRoot.IsNullable = True
Dim serializer As New XmlSerializer(GetType(eveapi), xmlRoot)
Dim ms As New MemoryStream(System.Text.Encoding.UTF8.GetBytes(eveResponse))

Return CType(serializer.Deserialize(ms), eveapi)
End Function
End Class


Partial Public Class eveapi
Public Property currentTime As String
Public Property cachedUntil As String

<System.Xml.Serialization.XmlArrayAttribute("result", Form:=XmlSchemaForm.Unqualified), _
System.Xml.Serialization.XmlArrayItemAttribute("rowset", GetType(characterList), Form:=XmlSchemaForm.Unqualified)> _
Public Property characterLists As List(Of characterList)

Public Property version As String

End Class

<XmlTypeAttribute(AnonymousType:=True)> _
Partial Public Class characterList
<System.Xml.Serialization.XmlElementAttribute("row", Form:=XmlSchemaForm.Unqualified)> _
Public Property characters As List(Of eveCharacter)

Public Property name As String
Public Property key As String
Public Property columns As String
End Class

<XmlTypeAttribute(AnonymousType:=True)> _
Partial Public Class eveCharacter

<XmlAttributeAttribute()> _
Public Property name As String

<XmlAttributeAttribute()> _
Public Property characterID As String

<XmlAttributeAttribute()> _
Public Property corporationName As String

<XmlAttributeAttribute()> _
Public Property corporationID As String

End Class