Scott Hanselman

Removing Dead Tracks (Duplicates that don't exist) from iTunes using C#

July 4, '10 Comments [36] Posted in Source Code | Tools
Sponsored By

image I hate iTunes on Windows with the heat of a thousand suns. It is a pox on my existence and it has cost me hours of pain and suffering. I'm sure it's all unicorns and spun sugar on MAC, but as a guy using Windows with dozens of gigabytes of music on a shared file server that I've ripped since the first CD I ever purchased, it's hell on earth. I could manage all my music with Zune (and I do have a Zune Pass for leasing music I don't want to buy) but there are iDevices in the house and iTunes is what it takes.

Did I mention it sucks? It's slow, frustrating, and is effectively just a giant listbox that exists only to mess up my music library and occasionally try to sneak Safari and QuickTime onto my machines. But enough about Satan, let's yank some dead tracks, shall we?

At some point, I ended up with hundreds of dead tracks and/or duplicates. I ended up with "LL Cool J - I Need Love 1.mp3" and "LL Cool J - I Need Love 2.mp3" along side the original. It was taking up many gigs of duplicate space. I searched for *1.mp3 and *2.mp3, etc, and deleted the dupes on disk.

However, this then left me with a big iTunes database that THINKS it has music, even though the file on disk is long gone. Now, as a napalm-style solution, you CAN delete your iTunes library completely and re-add it. It'll add just files that exist, but you will lose any edits, artwork, etc, you may have added. This solution wasn't cool for me so I say, nay nay.

There's a MILLION stupid little shareware apps that purport to fix duplicates and remove dead tracks. In my case, removing dupes was easy, but yanking dead tracks isn't worth me spending money.

Instead, I sat down tonight and decided to write a script in C# that said something like "foreach track, does that track exist where the system thinks it should be? No? Delete it."

A little googling with Bing, however, brought me to a three year old post at ScarTech where Shawn (shame he's stopped blogging) did the code for me! Yay. Here's a bit of his code, using the iTunes COM SDK. His stuff was written using Visual Studio Express 2008, but I upgraded to 2010. Also, I'm on 64-bit and his COM code expects x86, so I changed the project properties from AnyCPU to x86 and it worked great.

//create a reference to iTunes
iTunesAppClass iTunes = new iTunesAppClass();
//get a reference to the collection of all tracks
IITTrackCollection tracks = iTunes.LibraryPlaylist.Tracks;
for (int i = trackCount; i > 0; i–)
{
IITTrack track = tracks[i];
if (track.Kind == ITTrackKind.ITTrackKindFile)
{
IITFileOrCDTrack fileTrack = (IITFileOrCDTrack)track;
//if the file doesn’t exist, we’ll delete it from iTunes
if (fileTrack.Location == String.Empty || !System.IO.File.Exists(fileTrack.Location))
{
fileTrack.Delete();
}
}
}

Here's before:

1

So I ran it...

iTunes COM Sample Screenshot 

...and here's the afterparty:

image

Looks like it checked 7219 tracks and removed 570 dead ones. Yum.

I'd also encourage you to check out the second part of Shawn's iTunes and C# tutorial series where he creates a duplicate finder that knows to keep the higher bitrate song.

You can download Shawn's code here but as he hasn't blogged in a LONG time, I've mirrored it at SkyDrive. Big thanks to Shawn at ScarTech for saving me my evening and for tidying up my iTunes.

Good stuff, the internets.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

The Weekly Source Code 54 - Can't have Multiple Attributes of the Same Type when using a TypeDescriptor

July 3, '10 Comments [5] Posted in ASP.NET Dynamic Data | Source Code
Sponsored By

When I was in a China a few weeks back, I had a nice chat with a fellow named Zhe Wang who was using ASP.NET Dynamic Data to create a website. He made his own Custom Attribute to apply to his entities and effectively made an authorization scheme so that certain users could access some actions but not others.

In this little example, EntityAuthorize is Zhe's own custom attribute with a few properties.

namespace MyModels.Metadata
{
[EntityAuthorize(Roles="Administrators", AllowedEntityActions=EntityActions.List | EntityActions.Edit)]
[EntityAuthorize(Users="Administrator", DeniedEntityActions=EntityActions.Edit)]
public partial class SurveyMetaData
{
string Address { get; set; }
string Content { get; set; }
string Something { get; set; }
EntityCollection<OtherStuff> OtherThings { get; set; }
}
}

In this example, he's got two attributes stacked up on the same class. There's nothing wrong with this, by the way.

Later on, when it came time to fetch these attributes and take action on them, something weird happened. He was only getting one of the attributes when he fetched them via MetaTable.Attributes.OfType<T>() which is the usual way in Dynamic Data. In frustration he tried to get them using GetCustomAttributes which is slow and not much fun. Here's his clever hack (note: this won't be needed as we'll solve this another way).

public static bool FilterVisible(this MetaTable table, EntityActions action)
{
var usn = HttpContext.Current.User.Identity.Name;
var roles = Roles.GetRolesForUser(usn);
var attrs = table.Attributes.OfType<EntityAuthorizeAttribute>()
.Where
(
//try to get the attributes we need using the DynamicData metatable,
// but we're only getting one of the attributes of this type.
).Union
(
//get it ourselves the slow way using GetCustomAttributes and it works.
table.Attributes.OfType<MetadataTypeAttribute>()
.Select(t => Attribute.GetCustomAttributes(t.MetadataClassType))
.SelectMany(col => col).OfType<EntityAuthorizeAttribute>()
);

var allow = attrs.Any(a => a.AllowedEntityActions.HasFlag(action));

var deny = attrs.Any(a => a.DeniedEntityActions.HasFlag(action));

return allow && (!deny);
}

How does MetaTable.Attributes get populated? Via the a standard System.ComponentModel.TypeDescriptor provider that does all the work of getting the attributes, merging them together and putting them in a collection. However, the TypeDescriptor is kind of goofy in this case due to a weird behavior deep in the system.

Here's a little reproduction application to show the behavior where the TypeDescriptor fails to get both attributes. The TypeDescriptor API has some subtle differences with the standard reflection API.

class Program {
static void Main(string[] args) {
var prop = TypeDescriptor.GetProperties(typeof(Product))["Num"];
foreach (Attribute attrib in prop.Attributes) {
Console.WriteLine(attrib.GetType());
}
}
}

public class Product {
[TestPermission("foo1", "bar1")]
[TestPermission("foo2", "bar2")]
public int Num { get; set; }
}

[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class TestPermissionAttribute : Attribute {
public TestPermissionAttribute(string role, string user) { }
}

Notice the explicit AllowMultiple = true in the AttributeUsage attribute. However, deep in Attribute (yes, System.Attribute) there's a virtual property called TypeID and the value of that property becomes a key in the hashtable that the TypeDescriptor uses to remove duplicates. Inside of Attribute, we see:

public virtual object TypeId
{
get
{
return base.GetType();
}
}

Which probably should be something like this, however it's not (psudocode):

public virtual TypeID {
get {
if (IsAllowMultiple()) return this;
return GetType();
}
}

Since it's not really this way, but it IS virtual, you can certainly change your implementation of TypeID to return this, like, ahem, this:

[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class TestPermissionAttribute : Attribute {
public TestPermissionAttribute(string role, string user) { }

public override object TypeId { get { return this; } }
}

However, in this naive example, this object has no state. Notice the parameters are not stored, so "this" would be the same object. Add a few fields so the objects are actually different as they now hold some states, and the sample works returning this as the TypeID:

using System;
using System.ComponentModel;
class Program
{
static void Main(string[] args)
{
var prop = TypeDescriptor.GetProperties(typeof(Product))["Num"];
foreach (Attribute attrib in prop.Attributes)
{
Console.WriteLine(attrib.GetType());
}
Console.ReadLine();
}
}

public class Product
{
[TestPermission("foo1", "bar1")]
[TestPermission("foo2", "bar2")]
public int Num { get; set; }
}

[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class TestPermissionAttribute : Attribute
{
public TestPermissionAttribute(string role, string user) { Role = role; User = user; }

private string Role;
private string User;

public override object TypeId { get { return this; } }
}

So, the moral is, if you're using TypeDescriptors to get CustomAttributes and you want to have multiple attributes of the same type, make sure you:

  • Override TypeId to return this
  • Make sure AllowMultiple = true
  • Make sure your object has some fields (i.e. state)

Thanks to Zhe Wang and David Ebbo for the samples and their help.

I hope the two people who made it this far into the post and the one person who would ever hit this edge case appreciates this tale of obscurity.

If you like, follow me on Twitter: @shanselman

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Installing, Configuring and Using Windows Server AppFabric and the "Velocity" Memory Cache in 10 minutes

July 1, '10 Comments [39] Posted in AppFabric | ASP.NET
Sponsored By

A few weeks back I blogged about the Windows Server AppFabric launch (AppFabric is Microsoft's "Application Server") and a number of folks had questions about how to install and configure the "Velocity" memory cache. It used to be kind of confusing during the betas but it's really easy now that it's released.

Here's the comment:

Have you tried to setup a appfabric (velocity) instance ? I suggest you try & even do a blog post, maybe under the scenario of using it like a memcache for dasblog. I would love to know how to setup it up, it's crazy hard for what it is.

No problem, happy to help. I won't do it for dasblog, but I'll give you easy examples that'll take about 10 minutes.

Get and Install AppFabric

You can go to http://msdn.com/appfabric and download it directly or just do it with the Web Platform Installer.

Run the installer and select AppFabric Cache. If you're on Windows 7, you'll want to install the IIS 7 Manager for Remote Administration which is a little plugin that lets you manage remote IIS servers from your Windows 7 machine.

NOTE: You can also an automated/unattended installation as well via SETUP /i CACHINGSERVICE to just get caching.

The configuration tool will pop up, and walk you through a small wizard. You can setup AppFabric Hosting Services for Monitoring and Workflow Persistence, but since I'm just doing Caching, I'll skip it.Windows Server AppFabric Setup Wizard

The Velocity Caching Service needs to know where to get its configuration and it can get it from one of two places - either a database or an XML file on a share. If you use the XML file on a share, you'll need to make sure the service account has access to the share, etc. I'll use a database. The config wizard can make it for you as well. Click Next then Finish up the configuration.

Windows Server AppFabric Caching Service configuration Store

Configuring the Configuration Database...

Windows Server AppFabric Configuration Wizard

Ok, let's start it up and poke around.

Start and Administer your Memory Cluster from PowerShell

Now what? Go to the Start Menu and type in Caching. You'll have an item called "Caching Administration Windows PowerShell." This is where you can connect to the cache, check out what's going on, make new caches, etc. Run it as Administrator.

Caching Administration Windows PowerShell

If you type "get-command *cache*" you'll see all the different commands available for cache management. I typed start-cachecluster.

C:\> Start-CacheCluster

HostName : CachePort      Service Name            Service Status Version Info
--------------------      ------------            -------------- ------------
HANSELMAN-W500:22233      AppFabricCachingService UP             1 [1,1][1,1]

Cool, it's up and running. If you look in the config database (or the XML file if you chose that) you'll see that I have one machine in my memory cluster. I could have lots and lots, and if I had Windows Server Enterprise I would also have high-availability if one of the nodes went down.

I download the AppFabric Caching Samples and opened the CacheSampleWebApp in Visual Studio. Immediately we notice the two new references we don't usually see in a web application, Microsoft.ApplicationServer.Caching.Core and .Client.

image

Remember that for security everything is locked down by default, so you'll need to grant access to the cache for whatever user you'll be using to access it. I'm running as "ScottHa" so I'll run

Grant-CacheAllowedClientAccount scottha

...and you should do the same for whatever account your IIS is running as.

Use Your Memory Cache from ASP.NET

Remember that you can chop up your memory caches into logical buckets (partitions) and a memory cluster can serve more than one application, if you wanted.

Your cache can be hooked up in the web.config or from code (however you like). Here's a code example helper method where the sample does this manually. This data could come from wherever you like, you just need to tell it a machine to talk to and the portnumber. It'll automatically connect to the

Caches can also be partitioned. For example, I'm using a named cache called "default" but I could have multiple logically segmented areas like "shoppingcart" and "productcatalog" if I wanted.

using Microsoft.ApplicationServer.Caching;
using System.Collections.Generic;

public class CacheUtil
{
private static DataCacheFactory _factory = null;
private static DataCache _cache = null;

public static DataCache GetCache()
{
if (_cache != null)
return _cache;

//Define Array for 1 Cache Host
List<DataCacheServerEndpoint> servers = new List<DataCacheServerEndpoint>(1);

//Specify Cache Host Details
// Parameter 1 = host name
// Parameter 2 = cache port number
servers.Add(new DataCacheServerEndpoint("mymachine", 22233));

//Create cache configuration
DataCacheFactoryConfiguration configuration = new DataCacheFactoryConfiguration();

//Set the cache host(s)
configuration.Servers = servers;

//Set default properties for local cache (local cache disabled)
configuration.LocalCacheProperties = new DataCacheLocalCacheProperties();

//Disable tracing to avoid informational/verbose messages on the web page
DataCacheClientLogManager.ChangeLogLevel(System.Diagnostics.TraceLevel.Off);

//Pass configuration settings to cacheFactory constructor
_factory = new DataCacheFactory(configuration);

//Get reference to named cache called "default"
_cache = _factory.GetCache("default");

return _cache;
}
}

Once your cache is setup, it's trivial to use.

m_cache.Add(orderid, order);

and

Order order = (Order)m_cache.Get(orderid);

or updating an existing object:

m_cache.Put(orderid, order);

Check your Caching Statistics

So after adding a bunch of items to the cache, then requesting a bunch back I can go into PowerShell and see what's going on:

C:\> get-cache

CacheName            [Host]
                     Regions
---------            ---------------
default              [HANSELMAN-W500:22233]
                     Default_Region_0103(Primary)

C:\> Get-CacheStatistics default

Size         : 2493
ItemCount    : 5
RegionCount  : 5
RequestCount : 17
MissCount    : 3

You can use Performance Monitor as there is an imperial buttload of different Performance Counters Available. As I mentioned, you can make different partitions, like "default" or "poopypants" and check the stats on each of those separate, or the cache as a whole:

AppFabric Velocity Caching in PerfMon

And of course, I can recycle my webserver, start it up again and fetch an order and it's still there. You've effectively got a big, partitionable distributed (and optionally highly available) hashtable across multiple machines.

Diagram Explaining what AppFabric looks like as an architecture

Replacing ASP.NET Session State with AppFabric Caching

If you want, in ASP.NET 4 you can also swap out the default in-memory Session State Provider for AppFabric via your web.config. Here's an example web.config.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

<!--configSections must be the FIRST element -->
<configSections>
<!-- required to read the <dataCacheClient> element -->
<section name="dataCacheClient"
type="Microsoft.ApplicationServer.Caching.DataCacheClientSection,
Microsoft.ApplicationServer.Caching.Core, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"
allowLocation="true"
allowDefinition="Everywhere"/>
</configSections>

<!-- cache client -->
<dataCacheClient>
<!-- cache host(s) -->
<hosts>
<host
name="CacheServer1"
cachePort="22233"/>
</hosts>
</dataCacheClient>

<system.web>
<sessionState mode="Custom" customProvider="AppFabricCacheSessionStoreProvider">
<providers>
<!-- specify the named cache for session data -->
<add
name="AppFabricCacheSessionStoreProvider"
type="Microsoft.ApplicationServer.Caching.DataCacheSessionStoreProvider"
cacheName="poopylands"
sharedId="MySharedApp"/>
</providers>
</sessionState>
</system.web>
</configuration>

Resources and Links

Here's a recent AppFabric caching slidedeck from Ron Jacobs I found useful. More links below. Microsoft Windows Server AppFabric Slides at SlideShare.

As with all things, a little abstraction goes a long way. If you have an existing caching strategy (via EntLib, or whatever) you can almost certainly swap out your internal storage for AppFabric Caching.

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

C++ killed my grandpappy - Is C++ hard and where are the C++ coders hiding?

June 29, '10 Comments [52] Posted in Source Code
Sponsored By

After learning BASIC and ASM, for a job I started coding in C, when Hello World on Windows was 92 lines of code. (Apparently Hello World is easier now, says Pete Brown)

One of my first big projects was an app called Foolproof that kept kids in high school from breaking into their school's lab computers. It had a C core engine, a TurboVision DOS component and a 16-bit Windows C++ App. I did the DOS, Win16 and later the Win32 API all in C++. Later I did some VB3, then Delphi, then worked at Nike doing Java (right when RMI was starting, and HotJava when we wrote once and debugged everywhere) until I finally ended up in managed code, mostly doing C#.

Some folks don't know Microsoft has a free C++ Express Edition of Visual Studio 2010 and that it's one of the most used free versions out there. Someone is coding C++, and they are doing a lot of it.

Are you coding in C++, Dear Reader? What are you doing?

Most "managed people" cringe at the idea of using C++, thinking it's perhaps a language of the past and that it's not even possible to write Windows Apps in C++ without a Master's Degree. ;)

However, I just noticed there's a new project called Hilo with source to show people how to use the shiny new things in Windows 7 from C++ and how to make modern Windows applications that have animations and magical stuff that managed people assume belongs only to them.

The first drop of "Hilo" is apparently the first of many, all moving towards building a larger application. This first sample is a content browser (touch enabled!) for looking at videos and photos (yeah, yeah, I know) but you could totally use it to browse other stuff or use it as underlayment for your own app. This from the Windows Team Blog:

The main technologies used in Hilo: Direct2D, Windows Animation Manager, Windows Touch, Libraries and the Shell API.

  • Direct2D: For high performance and high-quality rendering of graphics.
  • Windows Animation Manager: To give the application a unique personality and to improve the user experience.
  • Windows Touch: To provide a more natural user interface through which the user can just “point” and “do”.
  • Libraries: To enable the application to provide users with a single, coherent view of their files.
  • Shell API: To navigate the images, to optimize by using the pre-built thumbnail cache and also to provide icons from actual content.

Even if you're not going to start coding C++, it's always a good idea to "see how the other half lives." For me, it's a return to what I thought I knew, and a way to look at C++ with fresh eyes.

What surprised me was that this app was written against Windows itself, with no frameworks. I always assumed you kind of needed MFC or some other library to be productive.

I'm not sure why I remember it this way, but C++ always made me think of regular expressions, and I assumed my code would **be &all like->this.you~know? You do need to know more about Windows, to be sure, as there is likely a decade of managed programmers who have forgotten what a message pump is, but it wasn't as scary as I had remembered.

Hilo Browser

Just download C++ Express, then the Hilo source, and build it. It has some cool effects with the carousel, like inertia when it spins, scaling, Z-ordering, hardware graphics acceleration. Now all they need is a designer! ;)

As an interesting example, it uses Direct 2D and has an animation for the orbit of the carousel. I removed all the if (SUCCEEDED(hr)) code from this snippet below because that's still the one thing about C++ on Windows that irks me. Don't judge then, judge me for yanking it for brevity.

This source is extremely defensive, dutifully checking HResults at every step. I mean, seriously, if it doesn't Succeed, what are you going to do about it? If (NOTSCREWED(hr)), you know.

For this source, the salient point is that there's some nice high-level Animation constructs like timers, transitions and storyboards.  For some wrongheaded reason I thought it'd be a pile of math so I was pleasantly surprised.

HRESULT OrbitAnimation::Setup(D2D1_ELLIPSE targetEllipse, double targetOpacity, double duration)
{
HRESULT hr = S_OK;

// Animation objects
ComPtr<IUIAnimationManager> animationManager;
ComPtr<IUIAnimationTimer> animationTimer;
ComPtr<IUIAnimationTransitionLibrary> transitionLibrary;
ComPtr<IUIAnimationStoryboard> storyboard;

// Transition objects
ComPtr<IUIAnimationTransition> centerXTransition;
ComPtr<IUIAnimationTransition> centerYTransition;
ComPtr<IUIAnimationTransition> radiusXTransition;
ComPtr<IUIAnimationTransition> radiusYTransition;
ComPtr<IUIAnimationTransition> opacityTransition;

// Initialize animation variables if necessary
if (nullptr == m_centerX)
{
hr = Initialize(targetEllipse, targetOpacity);
}

// Retrieve animation objects
hr = AnimationUtility::GetAnimationManager(&animationManager);
hr = AnimationUtility::GetTransitionLibrary(&transitionLibrary);

hr = AnimationUtility::GetAnimationTimer(&animationTimer);

// Initialize storyboard
hr = animationManager->CreateStoryboard(&storyboard);

// Create one transition for each orbit variable
hr = transitionLibrary->CreateLinearTransition(duration, targetEllipse.point.x, &centerXTransition);
hr = storyboard->AddTransition(m_centerX, centerXTransition);

hr = transitionLibrary->CreateLinearTransition(duration, targetEllipse.point.y, &centerYTransition);
hr = storyboard->AddTransition(m_centerY, centerYTransition);

hr = transitionLibrary->CreateLinearTransition(duration, targetEllipse.radiusX, &radiusXTransition);
hr = storyboard->AddTransition(m_radiusX, radiusXTransition);

hr = transitionLibrary->CreateLinearTransition(duration, targetEllipse.radiusY, &radiusYTransition);
hr = storyboard->AddTransition(m_radiusY, radiusYTransition);

hr = transitionLibrary->CreateLinearTransition(duration, targetOpacity, &opacityTransition);
hr = storyboard->AddTransition(m_opacity, opacityTransition);

hr = AnimationUtility::ScheduleStoryboard(storyboard);

return hr;
}

I was also pleased to see that intellisense worked nicely for everything typed as well as -> deferenced pointers, as seen in the screenshot below. Call me an intellicrack addict or weak-minded but it does speed things up once you know what you're looking for. The C++ Express SKU was a small download as well.

C++ Express Screenshot of Code

I'm not saying it's all puppies and cotton candy, but it's not the post-apocalyptic-Where-is-John-Connor-wasteland I thought it would be. I'll be giving C++ another look as I aim to learn (or relearn) another programming language this year (along with Scala). I'll report back as this application gets bigger and more interesting.

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

The Weekly Source Code 53 - "Get'er Done" Edition - XML in the left hand becomes HTTP POSTs in the right hand

June 26, '10 Comments [13] Posted in Open Source | Source Code
Sponsored By

I wrote some code tonight in about ten minutes with a "Get'er Done" attitude. We all do that (I hope). It's one-time code that will solve a one-time problem. As such, it isn't always pretty, but it often is interesting.

I've got this silly website called OverheardAtHome that is a collection of silly quotes and stories that my kids (and your kids) have said around the house. It was running on DasBlog (just like this blog) for the last year or more, but the sheer workflow of populating the site was getting tiredsome. DasBlog isn't setup for screening external submissions and promoting them to posts, and I wasn't really interested in extending DasBlog in that way.

Instead, I needed to move OverheardAtHome to a hosted blogging solution, preferably a nice free one as it doesn't make any money. It's just a hobby. I like Tumblr so I figured I put it there. Tumblr has a very basic HTTP API and their native UI supports User Submissions, so it seemed like a win.

DasBlog stores its content not in a database, but rather in an XML file per day. I've got a few hundred XML files that make up the whole of the content on OverheardAtHome and it's very basic stuff.

Here's what I wrote, using the Tumblr API from CodePlex written by (I believe) Jeremy "madkidd" Hodges, who is also a Developer on Graffiti CMS, coincidentally. It's a nice little abstraction on top of HttpWebRequest that uses a little HttpHelper class from "rakker."

I figured I could use PowerShell or something script-like, but this was very fast to write. There's no error handling, but interestingly (or not), there were no errors in hundreds of posts.

using System;
using System.Linq;
using System.IO;
using System.Xml.Linq;
using System.Threading;

namespace TumblrAPI.ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Started...");

TumblrAPI.Authentication.Email = "myemail@notyours.com"; // Console.ReadLine();
TumblrAPI.Authentication.Password = "poopypants"; //Console.ReadLine();
Console.WriteLine(TumblrAPI.Authentication.Authenticate().ToString());

if (TumblrAPI.Authentication.Status == TumblrAPI.AuthenticationStatus.Valid) {
Console.WriteLine("Now make some posts...");

DirectoryInfo di = new DirectoryInfo(@"C:\overheardathome\xml");
//Get the DasBlog XML files, they are like <entry><title/><content/></entry> and stuff
FileSystemInfo[] files = di.GetFileSystemInfos("*.dayentry.xml");
var orderedFiles = files.OrderBy(f => f.Name);

XNamespace ns = "urn:newtelligence-com:dasblog:runtime:data";
foreach (FileSystemInfo file in orderedFiles)
{
XDocument xml = XDocument.Load(file.FullName);
var posts = from p in xml.Descendants(ns + "Entry") select p;

foreach (var post in posts)
{
TumblrAPI.Post.Text t = new TumblrAPI.Post.Text();
t.Title = (string)post.Element(ns + "Title");
t.Body = (string)post.Element(ns + "Content");
Thread.Sleep(500); //Tumblr will API limit me if I bash on them.
Console.WriteLine(" Response from text post: {0}", t.Publish());
}
}
}

Console.WriteLine("Done, press any key...");
Console.ReadLine();
}
}
}

Comments? What's a better pattern for left-hand/right-hand bulk crap like this, Dear Reader?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.