{"id":1559,"date":"2012-03-06T21:04:09","date_gmt":"2012-03-07T03:04:09","guid":{"rendered":"http:\/\/www.wiredprairie.us\/blog\/?p=1559"},"modified":"2012-03-06T21:04:09","modified_gmt":"2012-03-07T03:04:09","slug":"some-code-from-snugup-browser-an-album-browser-for-smugmug","status":"publish","type":"post","link":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1559","title":{"rendered":"Some code from SnugUp.Browser (an album browser for SmugMug)"},"content":{"rendered":"

I\u2019ve been doing some tinkering recently with SmugMug again.<\/p>\n

Through testing SnugUp<\/a> for the past 4 years, I\u2019ve made quite the mess of my SmugMug account. Literally hundreds of poorly organized and often completely junk albums. I\u2019ve been wanting to clean it up, but SmugMug\u2019s UI for that is so obnoxiously slow and tedious that I decided I wanted to write a tool to make it easier to manage. <\/p>\n

Admittedly, given the amount of time I\u2019ve spent on writing the tool (which I\u2019m not yet finished with), I could have cleaned up my SmugMug account dozens of times. However, with my developer hat on, I thought, how fun would that be? \"Smile\"<\/p>\n

 <\/p>\n

\"image\"<\/a> <\/p>\n

So, I\u2019ve started to create the SnugUp.Browser. It\u2019s a bit of an interesting beast in that it is a Windows application that hosts a web browser to display its UI. I\u2019d tried using WPF\/XAML and just couldn\u2019t get the look I was wanting in a reasonable amount of time and effort. <\/p>\n

<RANT>WPF desperately needs a VirtualizingWrapPanel. Microsoft needs to ship it. <\/strong><\/RANT><\/p>\n

So, I created a WinForms project to host the IE Web Browser. (Arrgh, the WPF WebBrowser sucks still. It\u2019s not nearly as feature complete as the one from WinForms!). Internally, the web pages are served via an HttpListener. <\/p>\n

\n
try<\/span>\n{\n <\/span>int<\/span> portSuggest <\/span>=<\/span> GetAvailablePort();\n portSuggest <\/span>=<\/span> <\/span>40000<\/span>;\n _listener.Prefixes.Add(<\/span>string<\/span>.Format(<\/span>"<\/span>http:\/\/localhost:{0}\/<\/span>"<\/span>, portSuggest));\n _listener.Start();\n <\/span>while<\/span> (maxConnections<\/span>--<\/span> <\/span>><\/span> <\/span>0<\/span>)\n {\n _listener.BeginGetContext(HandleRequest, _listener); \n }\n}\n<\/span>catch<\/span> (Exception ex)\n{\n Debug.WriteLine(ex);\n}<\/span><\/div><\/pre>\n

<\/div>\n

The HandleRequest method responds in two ways:<\/p>\n

    \n
  1. Respond with JSON data (as if it were a web service). 99% of the code needed to access SmugMug\u2019s APIs was already written in a C# library I wrote for SnugUp.<\/li>\n
  2. Respond with binary data, providing a \u201cproxy\u201d to a service. This was needed to handle downloading images from the application. When the request was directly made from the WebBrowser to SmugMug, it was refused as the http-referer header was not a valid source apparently (SmugMug didn\u2019t like \u201chttp:\/\/localhost:####\u201d as the referrer. <\/li>\n<\/ol>\n
    \n
    resourceRequested <\/span>=<\/span> context.Request.Url.LocalPath.Replace(<\/span>'<\/span>\/<\/span>'<\/span>, <\/span>'<\/span>.<\/span>'<\/span>);\n<\/span>if<\/span> (resourceRequested.StartsWith(<\/span>"<\/span>.<\/span>"<\/span>)) { resourceRequested <\/span>=<\/span> resourceRequested.Substring(<\/span>1<\/span>); }\nDebug.WriteLine(<\/span>string<\/span>.Format(<\/span>"<\/span>Requested: {0}<\/span>"<\/span>, resourceRequested));\n<\/span>using<\/span> (Stream stream <\/span>=<\/span> Assembly.GetExecutingAssembly().GetManifestResourceStream(<\/span>string<\/span>.Format(<\/span>"<\/span>SnugUp.Resources.{0}<\/span>"<\/span>, resourceRequested)))\n{\n <\/span>if<\/span> (stream <\/span>==<\/span> <\/span>null<\/span>)\n {\n <\/span>\/\/<\/span>context.Response.StatusCode = 404;<\/span>\n<\/span>\n <\/span>if<\/span> (url.LocalPath.StartsWith(<\/span>"<\/span>\/proxy<\/span>"<\/span>))\n {\n var proxyUrl <\/span>=<\/span> url.Query.Substring(<\/span>"<\/span>?url=<\/span>"<\/span>.Length);\n extension <\/span>=<\/span> Path.GetFileName(proxyUrl);\n <\/span>try<\/span>\n {\n WebClient client <\/span>=<\/span> <\/span>new<\/span> WebClient();\n <\/span>byte<\/span>[] buffer <\/span>=<\/span> client.DownloadData(proxyUrl);\n context.Response.Headers.Add(<\/span>"<\/span>Expires<\/span>"<\/span>, DateTime.UtcNow.AddDays(<\/span>30<\/span>).ToString(<\/span>"<\/span>R<\/span>"<\/span>)); \n context.Response.ContentType <\/span>=<\/span> GetContentType(context, extension);\n context.Response.OutputStream.Write(buffer, <\/span>0<\/span>, buffer.Length); \n }\n <\/span>catch<\/span>\n {\n\n context.Response.StatusCode <\/span>=<\/span> <\/span>404<\/span>;\n }\n }\n\n }\n <\/span>else<\/span>\n {\n <\/span>string<\/span> contentType <\/span>=<\/span> GetContentType(context, extension);\n\n context.Response.ContentType <\/span>=<\/span> contentType; \n stream.CopyTo(context.Response.OutputStream);\n }\n <\/span>\/\/<\/span> Close the Response to send it to the client.\n <\/span>\/\/<\/span> }<\/span>\n<\/span>}\n<\/span><\/div><\/pre>\n

    <\/div>\n

    If the resource can\u2019t be found as an Embedded Resource, it tries a proxy. <\/p>\n

    I built a tiny router for the web services so that I could easily plug-n-play new functionality:<\/p>\n

    \n
    public<\/span> <\/span>virtual<\/span> <\/span>object<\/span> Route(<\/span>string<\/span> path, dynamic data)\n{\n <\/span>if<\/span> (path.StartsWith(<\/span>"<\/span>\/<\/span>"<\/span>)) { path <\/span>=<\/span> path.Substring(<\/span>1<\/span>); }\n var paths <\/span>=<\/span> path.Split(<\/span>'<\/span>\/<\/span>'<\/span>);\n\n <\/span>if<\/span> (paths.Length <\/span>><\/span> <\/span>0<\/span>)\n {\n var controllerName <\/span>=<\/span> paths[<\/span>0<\/span>];\n var controllerType <\/span>=<\/span> Type.GetType(<\/span>string<\/span>.Format(<\/span>"<\/span>SnugUp.Controller.{0}Controller<\/span>"<\/span>, controllerName), <\/span>false<\/span>, <\/span>true<\/span>);\n <\/span>if<\/span> (controllerType <\/span>!=<\/span> <\/span>null<\/span>)\n {\n var instance <\/span>=<\/span> InitializeControllerInstance(controllerType);\n <\/span>\/\/<\/span> default?<\/span>\n<\/span> <\/span>if<\/span> (paths.Length <\/span>><\/span> <\/span>1<\/span>)\n {\n MethodInfo method <\/span>=<\/span> controllerType.GetMethod(paths[<\/span>1<\/span>]);\n <\/span>if<\/span> (method <\/span>!=<\/span> <\/span>null<\/span>)\n {\n <\/span>try<\/span>\n {\n <\/span>object<\/span> results <\/span>=<\/span> method.Invoke(instance, <\/span>new<\/span> <\/span>object<\/span>[] {data});\n <\/span>return<\/span> results;\n }\n <\/span>catch<\/span> (Exception ex)\n {\n Debug.WriteLine(ex);\n }\n }\n }\n }\n }\n <\/span>return<\/span> <\/span>null<\/span>;\n}<\/span><\/div><\/pre>\n

    <\/div>\n

    It just looks up a request dynamically, maps to a method, and calls it (the response is eventually serialized as JSON).<\/p>\n

    For UI, I\u2019m currently using Bootstrap (although that may be removed), Knockout.JS, and jQuery. I\u2019m doing a bit of trickery to make sure that only what\u2019s visible on the screen is loaded (even when the user scrolls up and down \u2026 it\u2019s pretty smart).  I\u2019ll likely post more about that in a future update.<\/p>\n

    The app is becoming larger\u2026 and bloated a bit when compared to my original needs. \"Smile\"<\/p>\n

     <\/p>\n

    \"image\"<\/p>\n

    It now can show the images from the gallery as well. \"Smile\"<\/p>\n

    \"image\"<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"

    I\u2019ve been doing some tinkering recently with SmugMug again. Through testing SnugUp for the past 4 years, I\u2019ve made quite the mess of my SmugMug account. Literally hundreds of poorly organized and often completely junk albums. I\u2019ve been wanting to clean it up, but SmugMug\u2019s UI for that is so obnoxiously slow and tedious that […]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spay_email":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true},"categories":[4,5,11],"tags":[62,66,17],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/pd5QIe-p9","jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":18,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/18","url_meta":{"origin":1559,"position":0},"title":"SnugUp – a SmugMug Mass Uploader","date":"March 19, 2008","format":false,"excerpt":"I put the finishing touches on a web page for a new application I just finished, SnugUp. It's only useful if you have a SmugMug account, which I'd highly recommend if you're serious about photos. Sign up here. It's written using .NET 3.5 -- all WPF (except for a file\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"image","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/image.axd?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":1524,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1524","url_meta":{"origin":1559,"position":1},"title":"Alternative to ApplicationSettings in .NET","date":"February 1, 2012","format":false,"excerpt":"After dealing with lost settings, an unclear upgrade path, and my own confusion surrounding the magic of Settings in a .NET client application, I decided to build my own. You\u2019re probably familiar with this UI in Visual Studio. It hasn\u2019t changed much since it was first created: A list of\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"image","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/wp-content\/uploads\/2012\/02\/image.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":646,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/646","url_meta":{"origin":1559,"position":2},"title":"Announcement: SnugUp updated","date":"January 8, 2009","format":false,"excerpt":"I created a medium sized .NET WPF based application a while back which helps keep a series of folders with images synchronized to SmugMug with one click of a button. I just made a few fixes in the past couple of days and uploaded a new version. Changes: Fixed error\u2026","rel":"","context":"In "General"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/wp-content\/uploads\/2009\/01\/image1.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":1570,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1570","url_meta":{"origin":1559,"position":3},"title":"JavaScript: isScrolledIntoView","date":"March 15, 2012","format":false,"excerpt":"I needed a simple way to detect when a placeholder DIV (that would contain an image) had scrolled into the current viewport of the browser. I\u2019ve seen a few solutions that worked, and a few that didn\u2019t (hello? test your code!). Here\u2019s my simple solution: function isScrolledIntoView(elem) { elem =\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"SmugMupBrowser-live","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/wp-content\/uploads\/2012\/03\/SmugMupBrowser-live.gif?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":1520,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1520","url_meta":{"origin":1559,"position":4},"title":"Announcing SnugUp version 2","date":"January 29, 2012","format":false,"excerpt":"More than a few years ago, I created SnugUp version 1, which is a handy way of synchronizing folders of images with SmugMug for Windows users. I\u2019ve made a number of changes in the last month based on some requests and the result is a significant update (yet the core\u2026","rel":"","context":"In "Software"","img":{"alt_text":"SNAGHTML1ffa6494","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/wp-content\/uploads\/2012\/01\/SNAGHTML1ffa6494.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":1449,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1449","url_meta":{"origin":1559,"position":5},"title":".NET API for Nest Thermostat","date":"January 9, 2012","format":false,"excerpt":"I just finished a preliminary read-only (think version 0.1) wrapper around the Nest Thermostat API that is used by their mobile phone and web applications. As Nest doesn\u2019t have a formal API yet, the code could break at any time and may not be suitable for any use. However, it\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"SNAGHTML88bff0b3","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/wp-content\/uploads\/2012\/01\/SNAGHTML88bff0b3_thumb.png?resize=350%2C200","width":350,"height":200},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts\/1559"}],"collection":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/comments?post=1559"}],"version-history":[{"count":1,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts\/1559\/revisions"}],"predecessor-version":[{"id":1560,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts\/1559\/revisions\/1560"}],"wp:attachment":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/media?parent=1559"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/categories?post=1559"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/tags?post=1559"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}