Ramblings of General Geekery

The Journey to Digital Comics: Reading Device

The first step in the journey to digital comics is to figure out what you’re going to read them on. These days, the answer is pretty much going to always be “a tablet”… but which one?

I had a quick look at the market back in late 2011 and here’s how I made up my mind. First, I focused on the main ~10 inch tablets of the market. This included, for instance, the Motorola Xoom, the Asus Transformer and the Apple iPad 2.

Orientation

Can you see the most obvious difference between the iPad and the other two?

I made it easy for you by showing the iPad in the vertical position, because that’s its natural position: the home button is at the bottom, and the front camera is at the top. In constrast, the other tablets’ natural position is horizontal, with their front camera and power/sleep buttons placed accordingly.

Since reading a comic-book happens almost always in portrait mode, I figured I’d might as well choose a device that’s also designed for that orientation, if possible.

Aspect Ratio

Another important spec for me was the aspect ratio. The iPad has a 4/3 ratio whereas pretty much every other tablet on the market has a widescreen ratio, most of the time 16/10 (which is the case for the Xoom and Transformer).

I don’t know about you but holding a widescreen device vertically just feels wrong to me – it’s too narrow and too high, like I’m reading a mutated book. It’s entirely subjective, of course, and you may not mind at all.

Display Size

Of course, there’s the matter of effective size of the display. Assuming a vertical orientation, here are the pixel densities and screen sizes of each tablet:

  • Xoom: 160ppi, 12.7cm x 20.3cm.
  • Transformer: 149ppi, 13.6cm x 21.8cm.
  • iPad 2: 132ppi, 14.7cm x 19.7cm.

A standard American comic-book is 17cm x 26cm, so no tablet comes even close to reproducing that real-world reading experience (I wish somebody would make a bigger tablet!), but you can still figure out which one gives the best reading experience.

Here is a comparison of all 3 tablets’ screens (black borders indicate parts of the screen that would not be covered by the comic-book page):

The Xoom and the Transformer have the same resolution, which is higher than the
iPad’s, and it’s obvious you’d get a finer picture on them. However, if you
scale the screens so that they match their relative physical world size, here’s
what you get:

Here you can see that a comic-book displayed on a Xoom would be pretty much the same size as on the iPad – sure, you would get a better picture thanks to the higher resolution, making reading captions slightly more possible, but not any more comfortable without zooming a bit. The Transformer is clearly better here: the displayed page is bigger and it’s got a higher resolution to back it up.

Final Choice

With digital comics as the only requirement for your choice of tablet, the Asus Transformer Prime is the winner for me. However, I also had some secondary requirements:

  • Help me get through my RSS feeds more efficiently.
  • Let me read my Instapaper queue in a comfortable way.

The sad thing is that Flipboard, on iPad, really blew my mind for reading RSS feeds, and there’s no good Instapaper client on Android yet. It was a tough choice, as I went back and forth for a long time between the iPad and the Transformer, but I picked the iPad in the end. I figured that the loss in the comic-book reading experience was not too bad compared to the significant advantages I would get on my other use-cases.

There was of course the issue of my control over the device – something that always annoys me with Apple products (I ditched my iPhone for a Galaxy S Captivate because of that). But given my requirements, it wasn’t such a problem: both digital comics and RSS feeds are managed on the cloud, and most other applications did a good job of working around Apple by supporting 3rd party services like Dropbox for storage and syncing. If you have secondary requirements involving heavier stuff like music or video and you don’t like to play (pay?) by Apple’s rules, you may again want to choose the Transformer.

Spending the money on the tablet was not too hard. I sold some of my collection (mostly single issue series I either planned to replace with TPBs, or get rid of altogether), and that alone pretty much paid for it. That’s how much comics surplus I have in my home, begging to get uncluttered through the sweet release of digital media!


The Journey to Digital Comics

You may have noticed that, a couple months ago, I bought myself an iPad 2 as an early Christmas present. This was the result of some market research based on a few requirements I had for my next big household change: transition from paper comics to digital comics.

The incentive to start reading digital comics was pretty obvious: after moving my music, movies, TV shows and books to the digital world, it was only a matter of time before I would do the same with my comics. Sure, just like I still like to own some books in physical form, I will still buy some of my comics at my friendly local store, but, realistically speaking, more than two thirds of my collection is just enjoyable stuff I don’t care so much about – I want to read it, but the book itself is not especially beautiful, and it’s not something that would impress my guess when they spot it on the shelves.

IMG_5680.jpg

For me, going digital is all about reclaiming space and uncluttering the house where it makes sense.

As a result, here’s my “Journey to Digital Comics”, in the tradition of Mike Vardy’s Journey to…” series:

  1. The Journey to Digital Comics: Reading Device
  2. The Journey to Digital Comics: US Comics Apps
  3. The Journey to Digital Comics: Manga Apps
  4. The Journey to Digital Comics: Pros and Cons
  5. The Journey to Digital Comics: On Ownership and Stuff
  6. The Journey to Digital Comics: Conclusion

Edit: it turns out there’s a lot more to say than I expected, so I expanded the series a bit.


Abandoning Remember The Milk

The milkify.com domain recently expired, and I didn’t really feel like renewing it. If you look at the (absence of) recent commits in either IronCow or Milkify, it’s easy to understand why: I’m not really working on those projects anymore. And that’s because I’m not using Remember The Milk anymore either.

To be fair, IronCow is pretty stable already – most of the latest changes were configuration management stuff and upgrades to Visual Studio 2010 – so it’s still usable. I’ll be available for bug fixes, of course, but if the guys at Remember The Milk add new features, I would probably not know about it and it wouldn’t be ported to IronCow unless somebody forks the project and submits a patch.

Milkify, however, won’t see any improvements unless somebody picks it up. It’s just not worth it to work on a tool I’m not using myself.

This means I get more time to focus on my new home projects like PieCrust and a couple of other bigger ones in progress.


Simple workaround for the iPad’s smart cover’s design flaws

I recently got an iPad (more on this later), and with it I got the much hyped smart cover.

IMG_4353.jpg

I loved the simplicity of it, and how quickly you can take it off and put it back on again… However, I quickly realized that I probably had more dust on the screen while using that thing than if I didn’t have any cover at all. Among the problems I had the infamous “dust lines” – those three lines you get on the screen at exactly the same spot as the cover’s folds.

The problem was pretty obvious: the natural way to fold the cover, and the way Apple advertises it, is as follows:

IMG_4357.jpg

But this results in the following situation:

IMG_4354.jpg

The iPad rests on the folded cover as expected, but the outer faces of that triangular stand are the faces that go against the screen when the cover is closed! They would therefore pick up dust and other particle directly from the table and all around, and put them back onto your screen as soon as you walk away… I don’t know where Steve Jobs used to go with his iPad, but my house is not always spotless clean, and neither are any restaurant tables or office desks that I want to put my iPad on.

An easy workaround for this problem is to fold the cover the other way:

IMG_4355.jpg

This way, the faces that go against the screen are actually inside the triangular stand, where they’re very unlikely to pick up any dust. This way of folding is a lot less intuitive, but once you get the hang of it, you can actually close and open it as fast as before. I haven’t had much dust on my screen ever since I started using this technique.

There’s just one big caveat to this workaround: it doesn’t work with the “stand up” mode.

IMG_4358.jpg

If you look closely at the picture above, you’ll notice what’s wrong: gravity alone can unfold the cover and make your iPad fall down on the table. In my case, the magnetic strength of the cover is barely enough to counteract the iPad’s weight and make it stand on its own, but the slightest touch can make it crumble down like a house of a couple of very expensive cards. However, I don’t care because I’ve never felt the need to use that position so far.


PHP is fucked up

You may be shocked by what I’m about to say but here it is: PHP is fucked up.

Well, duh

Oh, no. Wait. No you’re not shocked. You already knew it.

Today’s topic: namespaces.

PHP’s namespace implementation is fucked up. There’s really no way to say it nicely, and you will realize it 5 minutes after starting to use them, when most of your code breaks and you need to litter the beginning of your files with use statements and you really wonder what you gained in the process. It really comes down to the impossibility to import a whole namespace, and PHP’s inability to look into parent namespaces when a class is not found in the current one. Sure, I understand the first feature is actually very tricky in a dynamic language, and the second one may have been rejected because of performance reasons, but with that many downsides you have to wonder why namespaces were implemented at all. Pornel gives a good list of all those downsides in his article on PHP namespaces.

Sure, namespaces let us get rid of the PEAR naming conventions, and they work nicely with spl_autoload_register, but they also gave me my biggest WTF moment in recent history.

Consider the following code:

namespace Foo;

class BarException extends Exception
{
}

class Bar
{
	public function trySomethingSafe()
	{
		try
		{
			throw new BarException("Catch this!");
		}
		catch (Exception $e)
		{
			echo "Caught exception: ".$e->getMessage();
		}
	}
}

It defines a new exception type (FooException), and a class (Bar) that does something safe by wrapping the code of the function inside a try/catch statement.

Now run this from another file:

require_once 'foobar.php';

$b = new FooBar();
try
{
    $b->trySomethingSafe();
}
catch (Exception $e)
{
    echo "This should never show up, right?";
}

Well, guess what you get in your output window? Yep:

This should never show up, right?

It’s like the first try/catch block is not catching the exception! Surely it can’t be a bug in the PHP runtime, right?

Well, of course not. It’s the user’s fault. And that’s because PHP’s namespaces suck ass.

Look at the catch statement in trySomethingSafe: it’s meant to catch all types of exceptions by specifying the top parent Exception type, but this is actually not it. See, that code is inside namespace Foo, and I didn’t specify either a use Exception at the top of the file, nor did I use Exception’s full name, Exception. Yes, that leading backslash is really super important in this case, because it makes all the difference between what you meant (the standard Exception type) and what PHP understands (a completely imaginary FooException type).

To fix the problem, replace the catch clause in trySomethingSafe with this:

catch (Exception $e)

You will then get the expected result:

Caught exception: Catch this!

The tricky thing here is that PHP doesn’t seem to care that FooException doesn’t exist. It happily runs the script, gets the exception, which is of type FooBarException, figures out that the class name does not equal to FooException, and bubbles the exception up the call stack. It doesn’t stop to check that the exception type we want to catch actually exists.

So, yeah. You need to be careful with PHP namespaces, especially when you add them to a previously un-namespaced project.


From Archos to Amazon

As you probably already know, Amazon’s tablet, the Kindle Fire, was announced a few days ago. Priced at a pretty amazing $199, its purpose is more focused than your general usage tablet like the iPad or the Xoom: it’s specifically designed for consuming content like books, music and video (preferably through Amazon’s own services, of course). Although it will probably be possible to install some other apps (through Amazon’s AppStore) to do some email and chatting and gaming and what have you, it will probably be more limited than on those bigger tablets, and will likely be only advertised as the last bullet point on the list of specs, if at all.

Now you know what else was priced around $200 and was useful mostly for consuming content like books, music and video? Yep. The Archos 70 I got last year for $249 (I got it on a discount, it was normally at $279).

Oh sure, Archos may not have been advertising their product like that. They may have advertised it along the lines of:

The most awesome Android tablet ever! Do everything you want! Web browsing! Videos and music! Email!! Games!!! Video chat!!!! This is so awesome we’re running out of exclamation marks!!!!!

Being a long time Archos user, I’m used to their bullshit and I know how to read through it. It usually translates to:

It’s rubbish at everything, especially anything web-related, but it can read any fucking video format in the universe, doesn’t come with any bullshit iTunes-like sync program, and is so much cheaper than the competition.

And as I stated last time, that was just perfect: I only wanted a good portable media player, and maybe a nicer way to read my Instapaper clips. But, of course, everytime somebody saw my small cheap looking tablet, there was a good chance the discussion would go like this:

  • “What’s that?”
  • “My Archos 70. Android tablet. I use it mainly as a portable media player, though.”
  • “Why didn’t you get an iPad?”
  • “Too big. Not enough codec support. Too expensive.”
  • “Yeah but you can do so much more with it!”
  • “Don’t care.”
  • “Come on! Apple is so awesome! The iPad is so shiny!”
  • “Yeah, whatever.”

Well it looks like Amazon found a lot of people like me out there because the Kindle Fire’s product story is exactly what I was looking for a year ago. Well… apart for that one small detail about data freedom, because I’m not sure exactly how easy it will be to get your own content onto the device, without necessarily going through Amazon (especially since most of their content is not even available outside of the U.S. anyway).

At least I’m very happy to see someone try to move the market into a slightly new direction instead of spitting out confusing all-purpose Android tablets like everyone else.


MongoDB for Lucene and ASP.NET

My latest home project (yet to be announced) is using MongoDB for storage, which led me to write a couple of libraries you may be interested in if you use that kind of database. Keep reading for the details.

MongoDB directory for Lucene

MongoDB.Lucene is what you would expected given the name: a MongoDB backend for Lucene indexes.

It’s a simple port of the standard FSDirectory using Mongo’s GridFS storage. I’m sure there’s a lot of room for improvement (performance, storage efficiency, lock reliability), but it’s working well enough for small applications.

It’s available as a NuGet package.

MongoDB providers for ASP.NET

MongoDB.Web is a set of providers for ASP.NET:

  • Membership provider
  • Role provider
  • Profile provider (not fully-featured yet)

It also contains a TraceListener that stores log events in a MongoDB collection.

This one’s got a funny story behind it. I wrote it quickly to get going with the rest of my project, and came back to it later thinking I could clean it up and publish it on the NuGet Gallery… but I got a package ID already exists error because of this similar package also called MongoDB.Web. I guess I should have checked first…

Why did I keep my own version then? Well:

  • My providers use a bit less storage in the database.
  • Provider initialization (like getting the right connection string) has more options (useful when you want to run on AppHarbor for instance, where they have pretty limited configuration customization).

The other version is however fully-featured, and probably better tested.


The extension method trick

If you’ve been writing C# code lately you should be pretty familiar with extension methods: it’s a nice compiler feature added to C# 3.0 which lets you “attach” methods to an existing type.

So for instance, if you define the following extension method:

    static class ConsoleExtensions
    {
        public static void WriteReversed(this TextWriter writer, string message)
        {
            writer.WriteLine(new string(message.Reverse().ToArray()));
        }
    }

You can write:

    Console.Out.WriteReversed("hello world!");

You effectively “extended” the TextWriter type with a new WriteReversed method in which you get a pseudo-this reference. Before extension methods, these types of methods would have been in a ConsoleHelper class of some kind – a lot less discoverable for the clients of your library.

Now, the (not so-)peculiar thing about it is that the pseudo-this variable can be null. Indeed, since this whole feature is only syntactic sugar, as they say, it’s just a more elegant way to write the following, which is a plain old function call:

    ConsoleExtensions.WriteReversed(Console.Out, "hello world!");

But it means it would look like you’re calling a method on a null object without getting slapped in the face with the infamous NullReferenceException.

You just need to add some simple parameter validation code to your extension method:

    static class ConsoleExtensions
    {
        public static void WriteReversed(this TextWriter writer, string message)
        {
            if (writer != null)
                writer.WriteLine(new string(message.Reverse().ToArray()));
        }
    }
    TextWriter writer = null;             // woah, are you crazy!?
    writer.WriteReversed("hello world!"); // woah, this is crazy!!

This trick can be pretty useful if you have an API that aims to be as minimalist as possible for the user and null is a valid case for one of the objects exposed publicly. You can already handle this with various well known techniques like wrapping or the NullObject pattern, but the extension method is a nice new alternative that’s enough in some cases and requires a minimum of code.

I’ve seen that trick used only once so far, in the excellent MvcMiniProfiler:

    using (MiniProfiler.Current.Step("Getting the answer to the ultimate question"))
    {
        var result = DeepThought.Compute();
        ViewBag.UltimateAnswer = result;
    }

Here, it’s totally OK if you disabled profiling in your web application, which means MiniProfiler.Current would return null. That’s because the Step() method is an extension method that checks for a null value provided as “this”, and doesn’t do much in that case.

Sure, they could have abstracted the profiler behind an IProfiler interface and have the MiniProfiler.Current return a NullProfiler when profiling is disabled, or something like that, but that would have been a lot more code than just using an extension method.


Formatting mongo output

If you’re working with MongoDB’s mongo command line tool and you often see yourself reading through this:

> db.contactInfos.find({"source":ObjectId("4e39da93058cfe28a039b546")})

{ "_id" : ObjectId("4e6e9a9f058cfe01dc1bcded"), "source" :ObjectId("4e39da93
058cfe28a039b546"), "sourceId" : "754731", "sourceUserName" : "loudej", "ful
lName" : "Louis DeJardin", "description" : "A software guy on the ASP.NET te
am and author of Spark", "avatarUrl" : "http://a2.twimg.com/profile_images/1
161371928/DSC01367_normal.jpg", "websiteUrl" : "http://louis.dejardin.org",
"location" : "Kenmore, WA", "friendCount" : 97, "followerCount" : 1428 }
{ "_id" : ObjectId("4e6e9a9f058cfe01dc1bcdf3"), "source" : ObjectId("4e39da9
3058cfe28a039b546"), "sourceId" : "16925866", "sourceUserName" : "sebastienr
os", "fullName" : "Sébastien Ros", "description" : "", "avatarUrl" : "http:/
/a1.twimg.com/profile_images/1074060902/f71ee9e786d8340a5eb08dd6154c2095_1__
normal.jpeg", "websiteUrl" : "http://about.me/sebastienros", "location" : "S
eattle", "friendCount" : 18, "followerCount" : 142 }
{ "_id" : ObjectId("4e6e9a9f058cfe01dc1bcdf4"), "source" : ObjectId("4e39da9
3058cfe28a039b546"), "sourceId" : "768197", "sourceUserName" : "haacked", "f
ullName" : "Phil Haack", "description" : "This bio is not true.", "avatarUrl
" : "http://a2.twimg.com/profile_images/1315393964/image_normal.jpg", "websi
teUrl" : "http://haacked.com/", "location" : "Bellevue", "friendCount" : 225
, "followerCount" : 15947 }

…then you may want to add toArray() at the end of your line:

> db.contactInfos.find({"source":ObjectId("4e39da93058cfe28a039b546")}).toArray()

[
    {
        "_id" : ObjectId("4e6e9a9f058cfe01dc1bcded"),
        "source" : ObjectId("4e39da93058cfe28a039b546"),
        "sourceId" : "754731",
        "sourceUserName" : "loudej",
        "fullName" : "Louis DeJardin",
        "description" : "A software guy on the ASP.NET team and author of Spark",
        "avatarUrl" : "http://a2.twimg.com/profile_images/1161371928/DSC01367_normal.jpg",
        "websiteUrl" : "http://louis.dejardin.org",
        "location" : "Kenmore, WA",
        "friendCount" : 97,
        "followerCount" : 1428
    },

    {
        "_id" : ObjectId("4e6e9a9f058cfe01dc1bcdf3"),
        "source" : ObjectId("4e39da93058cfe28a039b546"),
        "sourceId" : "16925866",
        "sourceUserName" : "sebastienros",
        "fullName" : "Sébastien Ros",
        "description" : "",
        "avatarUrl" : "http://a1.twimg.com/profile_images/1074060902/f71ee9e786d8340a5eb08dd6154c2095_1__normal.jpeg",
        "websiteUrl" : "http://about.me/sebastienros",
        "location" : "Seattle",
        "friendCount" : 18,
        "followerCount" : 142
    },
    {
        "_id" : ObjectId("4e6e9a9f058cfe01dc1bcdf4"),
        "source" : ObjectId("4e39da93058cfe28a039b546"),
        "sourceId" : "768197",
        "sourceUserName" : "haacked",
        "fullName" : "Phil Haack",
        "description" : "This bio is not true.",
        "avatarUrl" : "http://a2.twimg.com/profile_images/1315393964/image_normal.jpg",
        "websiteUrl" : "http://haacked.com/",
        "location" : "Bellevue",
        "friendCount" : 225,
        "followerCount" : 15947
    }
]

Much more readable, isn’t it? The only problem is that mongo will, by default, return only the first 20 hits of a query (it will write “has more” at the bottom to indicate that, well, there’s more). The toArray() method, however, will get everything and print it out. This could mean printing a lot of stuff, so you may want to stick a limit(20) in between if you’re not sure:

> db.contactInfos.find({"source":ObjectId("4e39da93058cfe28a039b546")}).limit(20).toArray()

Don’t brick your ReadyNAS

I have a ReadyNAS NV+ at home to store most of my data and I’ve been pretty happy with it so far… except for one thing: although it’s running a flavor of Linux that you can access as root user (if you installed the EnableRootSSH add-on), you can’t do everything you would normally do with a Linux box.

File Server

First, like most pre-2010 consumer grade NASes, the NV+ runs on a sparc CPU, so there’s a lot of packages you don’t have access to unless you recompile them yourself. And that’s fine, if you know you’re going to waste your whole evening figuring out weird broken dependencies and compile errors. But, second, there’s some custom stuff in there, I don’t know what it is, but it basically prevents you from even upgrading to newer versions of some of the packages you do have access to. This means: don’t run apt-get upgrade on an NV+.

Let me repeat that: don’t run apt-get upgrade on an NV+. Ever.

What happens when you do it is that you lose SSH access, the web administration interface stops working, some of your network shares become inaccessible, and half of your left socks magically disappear. I know, I did it twice in the past (yes, I’m stupid like that).

In both cases, I was lucky enough to recover from my mistake by performing an OS reinstall. It keeps all the packages, add-ons and configuration settings you had before, and only resets the admin password to netgear1 or infrant1 (depending on the version of RAIDiator you had installed), so it almost works again right away afterwards. The downside is that if what fucked up your NAS was one of those add-ons or packages, you wouldn’t have any other option than to do a factory reset and recover your data from a backup (you at least have daily automated backups, right?). But in my case, I think it was one of the OS libraries (like glibc or something) that was causing the issue so that’s where I got lucky. Twice.

Those are the only problems I ever had with that box, so overall I’m still happy to own it. The X-RAID that comes with it makes life a lot easier (you can hot-swap disks, and you can mix different disk sizes), and the machine is small and pretty quiet (my external backup disks are louder). Unlike my media center PC, I wouldn’t have much fun trying to build my own NAS, I think.

…but DON’T RUN APT-GET UPGRADE!