A long while back I had written a quick sample when Silverlight introduced drag-and-drop into the framework. Then I decided to show dragging MP3 files into a Silverlight app and reading the metadata and album art. In order to accomplish this I had to read into the ID3 data from a Silverlight library. I found a few libraries but settled on TagLib# to do the job. I had to modify it a bit to get it working in Silverlight as the .NET profile wasn’t the same. Recently a surge of people have been emailing me for the code. I spent time searching and apparently I didn’t think the TagLib# modifications were that important because I never saved them anywhere! A conversation started on Twitter and I decided to devote some “20% time” to making these modifications and take it a step further and make it into a Portable Class Library (PCL). Here’s my journey…
Deciding to Fork
My first task was finding the source of truth for TagLib#. The main link on the Novell developer site was broken and stale. I found it on GitHub and started looking around. It really hadn’t been updated in a long while (after all, it really didn’t need to be fore core .NET framework) and the project is very stale. There is no open issues list on GitHub as you have to use Bugzilla, but that didn’t even look like it was getting much attention. I emailed the maintainer listed in the authors file in the repository and he indicated he’s not really the maintainer. This felt like a project kind of fizzling down (if not fizzled already).
In looking at the tests, the project structure, and taking into account that I may want to do some things different, I made the decision to fork rather than clone. I’m not totally married to the decision but I don’t think anyone is keeping the lights on to take a pull request either though. I forked the code and started with new projects and using Visual Studio 2013 as my tool of choice.
Using Shared Projects
At first I thought that I would be doing perhaps a few different flavors, portable and full .NET framework. Because I thought I would I decided to use the new Shared Project system in Visual Studio 2013 and the new Shared Project Reference Manager VS extension that allows me to add references from any project to shared code easily. This gives me the flexibility for the future and sets my project system up in advance. You’ll see in the end I haven’t actually needed to take that step and perhaps won’t even need the Shared Project anymore, but for now I’m keeping it as it does me no harm.
Once I moved the code over and set my target profile for PCL, I hit build. Whoa. About 140 compile errors. Immediately I thought that I didn’t want to spend the time. I took a look at the issues and quickly realized that the base code had, in fact, changed a bit from when I messed with it in Silverlight. I started making a list of the things that weren’t compiling as I was targeting .NET 4.5, Silverlight 5, Windows 8+, Windows Phone 8+, and iOS/Android (Xamarin). The biggest errors came from the fact that the library was still using XmlDocument and hadn’t moved to XLinq. Beyond that there were things like Serializable, ICloneable, ComVisible and File IO that weren’t going to work. I got really frustrated quickly and about gave up.
Working at Microsoft I am fortunate to have access to try more things and indeed I reached out to some folks for help. I was able to get some things working continued with XmlDocument, but it didn’t feel right and starting to think about releasing this updated library I just realized this wasn’t going to work. I remained frustrated.
Sometimes when you are frustrated you just want to vent to the world. We call that Twitter these days. I was pulling some hair out and posted a comment, which was quickly replied by a member of the .NET team with a bit of a touché comment.
I chuckled but I also knew that David and others were going to be the key to helping me find the fastest path here. I started emails with the PCL team, including David and Daniel, who are incredibly knowledgeable and responsive. I finally got most working and then my colleague and I started chatting about my frustrations. He worked on XLinq for a bit and basically told me to suck it up and do the conversions and that it wasn’t that bad. We walked through a few of the scenarios and indeed it really ended up all being isolated into one area that I could quickly scan through. I could now remove my dependency on XmlDocument and have no other dependencies for this portable library.
Hooray for helpful people! Even when you vent, the good peeps will still help out!
Changes to TagLib# for portability
After the full conversion, a few things remain. Right now I have #ifdef’d out come of the interfaces and attributes that weren’t working. Once I get to a point of porting all the tests over, I’ll decide if they are even needed. Perhaps the biggest change though for users of this lib will be the removal of the default string path of file access. In discussing with some folks, I could have tried to make a portable storage layer work, but it started to make less sense quickly to do that in the library and leave that simple task to the app developer. This provides flexibility for the app to do what they want without the library trying to work around how different platforms do their file IO routines. What that means is that the default way of reading a file’s tags changes from:
var tagFile = File.Create("ironlionzion.mp3");
var tags = tagFile.GetTag(TagTypes.Id3v2);
string album = tags.Album;
' file is a StorageFile
var fileStream = await file.OpenStreamForReadAsync();
var tagFile = File.Create(new StreamFileAbstraction(file.Name, fileStream, fileStream));
var tags = tagFile.GetTag(TagTypes.Id3v2);
string album = tags.Album;
in a simple case. Yes, you as the app author have to write a bit more code, but it puts you in control of ensuring the file location you are reading. You can see here that I did add my StreamFileAbstraction class to my fork by default, which was the key in the Silverlight port and is actually the key for WinRT as well. Any app developer can create their own IFileAbstraction implementation and substitute it in the ctor of the create functions and be ready to read. I actually did this in the test project to re-implement a LocalFileAbstraction for test purposes and used the System.IO.File classes to achieve that, which are available when running VS unit tests.
What started out as a frustrating exercise turned out to be helpful for me to better understand PCLs and hopefully add value to those who have been asking for this. As mentioned, this isn’t fully tested and still a ways to go, so if you use it please log bugs (and help fix them) to complete the implementation. I won’t be working on this full time of course, but do hope to get the test suite ported over as well. Here are some relevant links:
Hope this helps!