Monday, May 23, 2011

Migrating Playcount info in Clementine

Recently discovered Clementine, a fork of Amarok which used to be my music player of choice when I had a functioning linux box. It's been great. As it happens, I had to dump my database and re-scan my music collection - which causes all playcounts and scores to be lost (and other stats which I care less about). Here are the steps I took to restore them. Probably is a way to do this with less steps.


  1. I used a free trial of RazorSQL for this - it's ok. Nothing to scream about, IMO, compared to PgAdmin III which I'm used to for this sort of thing.
  2. Extract the data from your db backup with a query like: SELECT title,artist,album,playcount,score FROM songs WHERE score > 0;
  3. Export the data to a tab-separated file
  4. Convert it to a bunch of update statements with a regex
    1. First replace all ' with ''
    2. Then s/{.*}\t{.*}\t{.*}\t{.*}\t{.*}/UPDATE songs SET playcount = \4, score = \5 WHERE title='\1' AND artist='\2' AND album='\3';/
  5. Run the resulting statements on your live database.  I believe Clementine needs to not be running when you do this.

    Monday, April 25, 2011

    Spokane Hospitals vs Medicare statistics

    Via this post from the Sunlight Foundation - http://reporting.sunlightfoundation.com/2011/medical-errors-2/ - I thought I'd see how the Spokane hospitals stack up in screwing up. I filtered each of the datasets to Spokane and sorted by name, not rate.  Read the article for any disclaimers on the content of the data.

    Falls and Trauma

    Blood Infections

    Urinary tract infections

    Bed Sores

    Poor Glycemic Control

    Foreign objects left in body

    Our hospitals each reported zero Air Embolisms and Wrong blood type.

    Wednesday, April 20, 2011

    C# 4 Dynamics and XML

    I've seen a couple of cool libraries for using dynamics to magically access your JSON. I knew similar things must be available for XML, and went looking. At this point, my usage is focused on read-only access to xml, of varying styles. Values may arbitrarily be in attributes or child nodes.

    The two that I found that and decided to evaluate were Anoop Madhusudanan's ElasticObject and Aaron Powell's Dynamics library.

    I set up a test application looking to evaluate performance and syntax. I accessed a handful of nodes, including one array of nodes and it's children. For the hand-built version, I set up a simple set of objects with simple properties and wrote code to extract values from XElements.

    I had a bit of trouble with the API for the Dynamics library, but I saw some performance promise, so I went ahead to make modifications and support my needs. I went ahead and forked the project on bitbucket to share my changes here.

    For the performance check, my Hand-Built code ended up at 6 milliseconds, Dynamics at 75 ms, and Elastic at 237 milliseconds (I pre-loaded the xml into an XElement and timed the parsing). So, the tradeoff here is time spent coding vs runtime. I don't expect my real usage to have a significant impact on the user's experience, so I'm ok with Dynamic's 12x slowdown for now. If it becomes a problem, I'll have a nice bottleneck to remove for version 2 :).

    Here are some syntax comparisons:
    Attribute Access
    Hand-Built
    if (t.MizConfig.InstrumentPlatform != "TC")
    ElasticObject
    if (t.MizConfig.InstrumentPlatform != "TC")
    Dynamics plus mod Multi-level nesting was not possible as written, MizConfig was returning a string. Changed code to allow
    if (t.MizConfig.InstrumentPlatform != "TC")
    Iterating multiple same-named elements
    Hand-Built
    foreach (var ch in t.MizConfig.HWChannels)
    ElasticObject
    foreach (var ch in t.MizConfig["HWChannel"])
    Dynamics
    foreach (var ch in t.MizConfig.HWChannels)
    Accessing Value (non-iterator) as a double. Note: hand-built is able to hide extraneous nodes from me
    Hand-Built
    d = t.PusherConfiguration.MaxForwardSpeed;
    ElasticObject
    d = double.Parse(~t.PusherConfiguration.Speeds.Record_Speed.Max);
    Dynamics
    d = double.Parse(t.PusherConfiguration.Speeds.Record_Speed.Max.Value);
    Dynamics plus mod
    d = t.PusherConfiguration.Speeds.Record_Speed.Max.To();
    Conditionally dealing with optional children
    Hand-Built
    if(xelement.Element("YoMama") != null)

    during parsing
    ElasticObject
    if (~dyn.MizConfig.YoMama != null)
    Dynamics plus mod Didn't find an easy was as implemented, probably would have had to do dyn.MizConfig.Element.Element("YoMama") mixing in XElement access. Instead, I modified the code to allow
    if (dyn.MizConfig.YoMama != null)

    Tuesday, January 04, 2011

    Updates to Kent Boogaart's Resizer control

    I've used and abandoned the Resizer control a few times over the last couple years for various reasons. This time it seems it will be finding a permanent home in one of my apps. I had to make a modification to it and Blogger says I have too much text for a comment, so I'll make a new post.

    I'm using Resizer inside a docking panel, so I wanted the width to stay tied to its parent panel. With that use case, I've added North, South, East, and West to the ResizeDirection enum. It's a pretty simple change, so I'll just post a few parts that people might miss if they wanted to reproduce it.


    private static void OnUpdateSizeCommand(object sender, ExecutedRoutedEventArgs e)
    {
    Resizer resizer = sender as Resizer;
    Debug.Assert(resizer != null);

    if (resizer._frameworkElement != null)
    {
    Point point = resizer._frameworkElement.PointToScreen(Mouse.GetPosition(resizer._frameworkElement));
    //If we're not adjusting the width or height, we want to leave it alone so it has its default sizing characteristics
    double? widthDelta = null;
    double? heightDelta = null;

    switch (resizer.ResizeDirection)
    {
    case ResizeDirection.North:
    heightDelta = resizer._resizeOrigin.Y - point.Y;
    break;
    case ResizeDirection.East:
    widthDelta = point.X - resizer._resizeOrigin.X;
    break;
    case ResizeDirection.South:
    heightDelta = point.Y - resizer._resizeOrigin.Y;
    break;
    case ResizeDirection.West:
    widthDelta = resizer._resizeOrigin.X - point.X;
    break;
    case ResizeDirection.NorthEast:
    widthDelta = point.X - resizer._resizeOrigin.X;
    heightDelta = resizer._resizeOrigin.Y - point.Y;
    break;
    case ResizeDirection.NorthWest:
    widthDelta = resizer._resizeOrigin.X - point.X;
    heightDelta = resizer._resizeOrigin.Y - point.Y;
    break;
    case ResizeDirection.SouthEast:
    widthDelta = point.X - resizer._resizeOrigin.X;
    heightDelta = point.Y - resizer._resizeOrigin.Y;
    break;
    case ResizeDirection.SouthWest:
    widthDelta = resizer._resizeOrigin.X - point.X;
    heightDelta = point.Y - resizer._resizeOrigin.Y;
    break;
    default:
    Debug.Fail("Unexpected ResizeDirection: " + resizer.ResizeDirection);
    break;
    }

    //update the width and height, making sure we don't set to below zero
    if (widthDelta != null)
    resizer.Width = Math.Max(0, resizer._originalWidth + widthDelta.Value);
    if (heightDelta != null)
    resizer.Height = Math.Max(0, resizer._originalHeight + heightDelta.Value);
    }

    e.Handled = true;
    }



    in GripAlignmentConverter.Convert:


    switch (orientation)
    {
    case Orientation.Horizontal:
    if (resizeDirection == ResizeDirection.NorthEast ||
    resizeDirection == ResizeDirection.SouthEast ||
    resizeDirection == ResizeDirection.South ||
    resizeDirection == ResizeDirection.North)
    {
    return HorizontalAlignment.Right;
    }
    else
    {
    return HorizontalAlignment.Left;
    }
    case Orientation.Vertical:
    if (resizeDirection == ResizeDirection.NorthEast ||
    resizeDirection == ResizeDirection.NorthWest ||
    resizeDirection == ResizeDirection.North)
    {
    return VerticalAlignment.Top;
    }
    else
    {
    return VerticalAlignment.Bottom;
    }
    }



    new cases in GripCursorConverter.Convert


    case ResizeDirection.North:
    case ResizeDirection.South:
    return Cursors.SizeNS;
    case ResizeDirection.West:
    case ResizeDirection.East:
    return Cursors.SizeWE;



    in GripRotationConverter:


    switch (resizeDirection)
    {
    case ResizeDirection.SouthWest:
    return 90;
    case ResizeDirection.NorthWest:
    case ResizeDirection.West:
    return 180;
    case ResizeDirection.NorthEast:
    case ResizeDirection.North:
    return 270;
    }



    If I've left something out or you're having problems, let me know.